> For the complete documentation index, see [llms.txt](https://info.soccerfootball.info/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://info.soccerfootball.info/websocket/commands.md).

# Commands

## Client Commands

This document describes the commands that clients can send to the WebSocket server.

### Command Format

All commands are JSON objects with an `action` field:

```json
{
  "action": "command_name",
  ...additional fields
}
```

### Available Commands

| Command     | Description                |
| ----------- | -------------------------- |
| subscribe   | Subscribe to a channel     |
| unsubscribe | Unsubscribe from a channel |
| ping        | Keep connection alive      |

***

### Subscribe

Subscribe to a channel to start receiving events.

#### Request

```json
{
  "action": "subscribe",
  "channel": "MAIN",
  "scope": "all"
}
```

#### Parameters

<table><thead><tr><th width="108.60009765625">Field</th><th width="106.5999755859375">Type</th><th width="114">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>action</code></td><td>string</td><td>Yes</td><td>Must be <code>"subscribe"</code></td></tr><tr><td><code>channel</code></td><td>string</td><td>Yes</td><td>Channel name (MAIN, MAIN_REALTIME, STATS, ODDS, STATS_REALTIME, ODDS_REALTIME)</td></tr><tr><td><code>scope</code></td><td>string</td><td>Yes</td><td>Subscription scope: <code>"all"</code>, <code>"match"</code>, or <code>"league"</code></td></tr><tr><td><code>id</code></td><td>string</td><td>Conditional</td><td>Required when scope is <code>"match"</code> or <code>"league"</code></td></tr></tbody></table>

#### Success Response

```json
{
  "type": "subscribed",
  "channel": "MAIN",
  "scope": "all",
  "id": null,
  "topicKey": "MAIN:all",
  "message": "Successfully subscribed"
}
```

When subscribing with a specific scope:

```json
{
  "type": "subscribed",
  "channel": "STATS",
  "scope": "match",
  "id": "12345",
  "topicKey": "STATS:match:12345",
  "message": "Successfully subscribed"
}
```

#### Error Response

```json
{
  "type": "error",
  "code": "CHANNEL_NOT_ALLOWED",
  "message": "Channel STATS_REALTIME is not available for your plan"
}
```

#### Error Codes

| Code                  | Description                            |
| --------------------- | -------------------------------------- |
| `CHANNEL_NOT_ALLOWED` | Your plan doesn't include this channel |
| `SUBSCRIPTION_LIMIT`  | Maximum subscriptions reached          |
| `INVALID_CHANNEL`     | Channel name is not valid              |
| `INVALID_SCOPE`       | Scope format is not valid              |

#### Examples

```json
// Subscribe to all main events
{"action": "subscribe", "channel": "MAIN", "scope": "all"}

// Subscribe to stats for a specific match
{"action": "subscribe", "channel": "STATS", "scope": "match", "id": "12345"}

// Subscribe to odds for an entire league
{"action": "subscribe", "channel": "ODDS", "scope": "league", "id": "67890"}

// Subscribe to real-time stats updates (MEGA plan only)
{"action": "subscribe", "channel": "STATS_REALTIME", "scope": "match", "id": "12345"}
```

***

### Unsubscribe

Unsubscribe from a channel to stop receiving events.

#### Request

```json
{
  "action": "unsubscribe",
  "channel": "MAIN",
  "scope": "all"
}
```

#### Parameters

<table><thead><tr><th width="112.5999755859375">Field</th><th width="125.4000244140625">Type</th><th width="111">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>action</code></td><td>string</td><td>Yes</td><td>Must be <code>"unsubscribe"</code></td></tr><tr><td><code>channel</code></td><td>string</td><td>Yes</td><td>Channel name to unsubscribe from</td></tr><tr><td><code>scope</code></td><td>string</td><td>Yes</td><td>Scope to unsubscribe from: <code>"all"</code>, <code>"match"</code>, or <code>"league"</code></td></tr><tr><td><code>id</code></td><td>string</td><td>Conditional</td><td>Required when scope is <code>"match"</code> or <code>"league"</code></td></tr></tbody></table>

#### Success Response

```json
{
  "type": "unsubscribed",
  "channel": "MAIN",
  "scope": "all",
  "id": null,
  "topicKey": "MAIN:all",
  "message": "Successfully unsubscribed"
}
```

#### Error Response

```json
{
  "type": "error",
  "code": "NOT_SUBSCRIBED",
  "message": "Not subscribed to this channel/scope"
}
```

#### Notes

* Unsubscribing frees up a subscription slot
* You must specify the exact channel and scope used when subscribing
* Unsubscribing from a non-existent subscription returns an error but doesn't affect other subscriptions

***

### Ping

Send a ping to keep the connection alive and verify connectivity.

#### Request

```json
{
  "action": "ping"
}
```

#### Response

```json
{
  "type": "pong",
  "ts": 1705312800000
}
```

#### Response Fields

| Field  | Type    | Description                      |
| ------ | ------- | -------------------------------- |
| `type` | string  | Always `"pong"`                  |
| `ts`   | integer | Server timestamp in milliseconds |

#### Best Practices

* Send a ping every 30 seconds to maintain the connection
* Use the response timestamp to measure latency
* If no pong is received within 10 seconds, consider the connection dead and reconnect

#### Example Implementation

```javascript
// JavaScript ping implementation
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ action: 'ping' }));
  }
}, 30000);
```

```python
# Python ping implementation
async def keep_alive(ws):
    while True:
        await asyncio.sleep(30)
        await ws.send(json.dumps({"action": "ping"}))
```

***

### Invalid Command Handling

If you send an unrecognized command, the server responds with:

```json
{
  "type": "error",
  "code": "INVALID_ACTION",
  "message": "Unknown action: foobar"
}
```

### Rate Limiting

* Commands are rate-limited to prevent abuse
* Maximum 10 commands per second per connection
* Exceeding the limit results in temporary throttling
