I’ve been doing some client coding lately, and wanted to share some tips for anyone who wants to try writing their own client code or script.
Servers
When interacting with OGS, you will mainly interact with the Django server (https://online-go.com/api/...) and the websocket (wss://online-go.com/ws). You may also come across the termination API (https://online-go.com/termination-api) and the AI server (https://ai.online-go.com, wss://ai.online-go.com/)
Since there are many different servers, you need to pass a user JWT around to verify the user. You can see an example of this in the script below where the JWT comes from the login endpoint and is passed to the websocket.
Docs
IMO docs are sparse, and not always up-to-date, but the new WebSocket docs are pretty solid: protocol | goban
Otherwise, I’d avoid docs and look at the Network Tab directly (XHR and WS) to see what kind of traffic the official client is sending and receiving. Reading the source code for online-go.com, goban or any of the other open source clients is also a good resource.
What’s new?
- Raw websockets instead of socket.io. While the socket.io endpoint still exists (
wss://online-go.com/socket.io/), raw websockets are the official way to interact with OGS. This is documented at protocol | goban - No need for oauth*
- If you search the forums, you’ll see tens of questions about oauth. I believe it used to be necessary for certain endpoints. This is no longer necessary, and you can make a fully functional script without it.
- There is a use-case where oauth is still important: logging in without giving the client access to users’ passwords. See OAuth2 flow — using authorization grant code? - #4 by marinakai and Oauth2 How To
Examples
Short and sweet Python script (76 LOC)
#!/usr/bin/env python3
"""
Minimal OGS API demo - login and make a move
Requires: pip install requests websocket-client
"""
import json
import requests
import websocket
from string import ascii_lowercase
server_url = "https://online-go.com"
ws_url = "wss://online-go.com/ws"
session = requests.Session()
# Step 1: Login
username = input("Username: ")
password = input("Password: ")
login_result = session.post(f"{server_url}/api/v0/login", json={'username': username, 'password': password}).json()
jwt_token = login_result['user_jwt']
current_user_id = login_result['user']['id']
print(f"Logged in as {login_result['user']['username']} (ID: {current_user_id})")
# Step 2: Connect to WebSocket and authenticate
ws = websocket.create_connection(ws_url)
ws.send(json.dumps(['authenticate', {'jwt': jwt_token}]))
print("Connected and authenticated to WebSocket")
# Step 3: Get ongoing games
overview = session.get(f"{server_url}/api/v1/ui/overview").json()
games = overview['active_games']
print(f"\nFound {len(games)} active games:")
for i, game in enumerate(games):
print(f"{i+1}. Game {game['id']} - {game['black']['username']} vs {game['white']['username']}")
# Step 4: Select a game
game_idx = int(input("\nSelect game number: ")) - 1
game_id = games[game_idx]['id']
# Step 5: Get game state
game_state = session.get(f"{server_url}/termination-api/game/{game_id}/state").json()
print(f"\nGame {game_id}:")
# Print board state
board = game_state['board']
size = len(board)
sgf_coords = ascii_lowercase[:size]
print("\nBoard state:")
print(" " + " ".join(sgf_coords))
for i, row in enumerate(board):
row_label = sgf_coords[i]
print(f" {row_label} " + ' '.join('.' if cell == 0 else 'X' if cell == 1 else 'O' for cell in row))
# Check if it's our turn
player_to_move = game_state['player_to_move']
if player_to_move != current_user_id:
print(f"\nIt's not your turn. Waiting for opponent to move.")
ws.close()
exit(0)
# Step 6: Connect to game
ws.send(json.dumps(['game/connect', {'game_id': game_id}]))
# Step 7: Make a move
move = input("\nEnter move (e.g. 'ab' or '..' for pass): ")
move_msg = json.dumps(['game/move', {'game_id': game_id, 'move': move}])
ws.send(move_msg)
ws.close()
print("\nMove sent!")
Dart client implementation for WeiqiHub (more productionized, but still pretty simple): WeiqiHub/lib/game_client/ogs at main · ale64bit/WeiqiHub · GitHub