> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cabalspy.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Participants

> POST /v1/competitions/join and /leave, GET /v1/competitions/participants: enter a platform user into a competition, take them out, and list the participants.

With `enrollment: "opt_in"` you enter users one by one, for example when they press **Join** on your site. With `all_users` every platform user takes part automatically, see [Who takes part](https://docs.cabalspy.xyz/cabalspy-id/competitions.md#who-takes-part).

## Join

`POST /v1/competitions/join`

Enters one of your [platform users](https://docs.cabalspy.xyz/cabalspy-id/white-label.md). Answers **201** when the user joined, **200** with `"already": true` when they were already in. After the start, joining needs `late_join: true`.

**id** `string` *required*

The competition id.

**external\_id** `string` *required*

Your id of the user. Alternatively pass csid\_id.

```bash cURL theme={null}
curl -X POST "https://api-id.cabalspy.xyz/v1/competitions/join" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f", "external_id": "user_8412"}'
```

```python Python theme={null}
import requests

r = requests.post("https://api-id.cabalspy.xyz/v1/competitions/join",
                  json={"id": "9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f", "external_id": "user_8412"},
                  headers={"Authorization": "Bearer YOUR_KEY"})
print(r.status_code, r.json()["data"])
```

```javascript JavaScript theme={null}
const res = await fetch("https://api-id.cabalspy.xyz/v1/competitions/join", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({ id: "9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f", external_id: "user_8412" }),
});
const { data } = await res.json();
console.log(res.status, data.joined, data.already);
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "competition_id": "9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
    "external_id": "user_8412",
    "joined": true,
    "already": false
  },
  "meta": { "request_id": "req_5162c7cf7f87", "cached": false, "cache_age_seconds": 0, "version": "v1", "timestamp": "2026-10-26T08:04:11Z" }
}
```

## Leave

`POST /v1/competitions/leave`

Takes a user out of a competition that has not ended, also while it runs. Their entry is removed from the standings. With `enrollment: "all_users"` the user stays out until you [join](#join) them again. Returns `competition_id`, `external_id` and `left` (false when the user was not in).

**id** `string` *required*

The competition id.

**external\_id** `string` *required*

Your id of the user. Alternatively pass csid\_id.

```bash theme={null}
curl -X POST "https://api-id.cabalspy.xyz/v1/competitions/leave" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id": "9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f", "external_id": "user_8412"}'
```

## List participants

`GET /v1/competitions/participants`

The participants in the order they joined.

**id** `string` *required*

The competition id.

**limit** `integer` (default: 100)

Participants per page, 1 to 1,000.

**offset** `integer` (default: 0)

How many to skip. Instead of offset you can pass `pagination.next_cursor` as `cursor`; it is null on the last page.

```bash theme={null}
curl "https://api-id.cabalspy.xyz/v1/competitions/participants?id=9c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f&limit=100" \
  -H "Authorization: Bearer YOUR_KEY"
```

```json Response theme={null}
{
  "success": true,
  "data": [
    {
      "external_id": "user_8412",
      "csid_id": "3f9a1c2e-5b7d-4e8f-a1b2-c3d4e5f60718",
      "joined_at": "2026-10-26T08:04:11Z"
    }
  ],
  "pagination": { "limit": 100, "total": 1, "has_more": false, "next_cursor": null },
  "meta": { "request_id": "req_5162c7cf7f87", "cached": false, "cache_age_seconds": 0, "version": "v1", "timestamp": "2026-10-26T08:05:00Z" }
}
```

## Errors

| Status | Code | Meaning |
| - | - | - |
| 400 | missing\_parameter | id or external\_id missing. |
| 400 | invalid\_parameter | id, external\_id, limit or offset invalid. |
| 401 | missing\_api\_key | API key missing. |
| 403 | invalid\_api\_key | API key invalid or not enabled for CabalSpy ID. |
| 404 | competition\_not\_found | No competition with this id in your instance. |
| 404 | platform\_user\_not\_found | No user with this id in your instance. |
| 409 | competition\_closed | The competition already ended or was cancelled. |
| 409 | late\_join\_closed | Join: the competition runs and does not take new participants. |
| 409 | competition\_full | Join: max\_participants is reached. |
| 429 | rate\_limit\_exceeded | Rate limit exceeded. |

See [Errors](https://docs.cabalspy.xyz/cabalspy-id/errors.md) for the full list.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.