--- title: 'Developer API v1' slug: 'public-api-v1' category: 'Developers' order: 1 description: 'Authenticate a reviewed custom bot and read approved CADLY server data.' --- # 1 - Overview The CADLY Developer API lets an approved custom bot read operational data for one Discord server. Access is apply-only. A server owner or Discord administrator must submit the bot and its use case for CADLY staff review before a key can be generated. Base URL: ```text https://api.cadly.me/v1 ``` Interactive Swagger reference: [https://api.cadly.me/v1/docs](https://api.cadly.me/v1/docs) Enter the server API key to unlock the reference. The key is verified against its active grant, loaded into Swagger's Bearer authorization for interactive requests, and kept only in the current tab's memory. The raw OpenAPI document is available at `GET /v1/openapi.yaml` with the same Bearer authentication. Documentation validation and specification reads use their own edge protection and do not consume the server grant's normal request quota. Version 1 is read-only. It does not provide create, update, delete, command, export, archive, or cross-server endpoints. # 2 - Applying and managing a key Open **Settings > Danger Zone** and use the **Developer API** card. Members with **Manage Settings** can view the current application, key status, and rolling 24-hour allowance. Submitting or updating an application and generating, rotating, or revoking a key require the Discord server owner or an administrator. The application asks for the bot identity and contact, requested data scopes and ER:LC slots, what the bot does, why it needs the data, how it processes and stores responses, its request and rate-limit strategy, hosting, secure deletion, and initial synchronization. You can also provide a source repository. Review the attestations carefully before submitting. Each approval is bound to: - One Discord server - One reviewed bot and use case - Specific data scopes - Approved ER:LC server slots If reviewers request changes or reject an application, reopen the prefilled form, update the answers, and resubmit it for review. After approval, generate the key from the same card. The complete key is displayed once; later visits show only a masked identifier and the rolling 24-hour request allowance. Store the complete key in a server-side secret manager or environment variable. Never place it in browser code, a public repository, or Discord messages. Rotating a key immediately revokes the old key. CADLY does not provide a grace period. # 3 - Authentication Send the key as a bearer token: ```http Authorization: Bearer cadly_live_. ``` The key identifies its Discord server. Do not send a guild ID in the URL, query string, or request body. Available scopes are: | Scope | Data | | ------------------- | ------------------------------------------------------------------- | | `shifts.read` | Shift history, active shifts, statistics, and leaderboards | | `logs.read` | CADLY logs and their public context fields | | `erlc.read` | Cached ER:LC server snapshot | | `loa.read` | Leave of absence records | | `sessions.read` | Session history, details, voters, events, and stored metrics | | `applications.read` | Application definitions, aggregate metrics, and submission metadata | # 4 - Server slot header ER:LC, session, and shift endpoints are slot-specific. - Mono-server CADLY setup: omit `X-Server`. Sending it returns `400`. - Dual-server CADLY setup: send `X-Server: A` or `X-Server: B`. - The requested slot must be included in the grant. Logs, LOA, and application endpoints are guild-wide and do not use `X-Server`. # 5 - Responses, caching, and errors Successful responses use a `data` envelope: ```json { "data": {} } ``` Paginated responses also include: ```json { "data": [], "pagination": { "next_cursor": null, "has_more": false } } ``` Send the returned cursor as `?cursor=...`. Do not modify or interpret cursor values. Eligible `GET` responses include an `ETag`. Send it on the next request: ```http If-None-Match: "" ``` If the resource is unchanged, CADLY returns `304 Not Modified` with an empty body. A `304` counts toward request limits but does not consume a log or submission record budget. Errors use a stable code and a request ID: ```json { "error": { "code": "invalid_server_header", "message": "X-Server must be A or B." }, "request_id": "req_..." } ``` Unexpected `5xx` responses only return `internal_error` and `Something went wrong.` Internal exceptions, database details, and upstream responses are never included. Provide the request ID when contacting CADLY support. # 6 - Rate limits Limits are shared by the server grant. Creating or rotating another key does not provide another quota. | Limit | Allowance | | -------------------------------- | ------------------------- | | Token refill | 1 request every 5 seconds | | Burst | 2 requests | | Rolling minute | 10 requests | | Rolling 24 hours | 5,000 requests | | Returned CADLY logs | 500 per rolling hour | | Returned application submissions | 500 per rolling hour | Rate information is returned in `X-RateLimit-*` headers. A rejected request returns `429` and `Retry-After`. Repeated reads count again. When an hourly record budget is nearly exhausted, CADLY may return a shorter final page. Once the budget is empty, further reads of that record type return `record_budget_exceeded`. # 7 - Endpoints ## 7.1 - Shifts All shift endpoints require `shifts.read` and the slot rules described above. | Method and path | Description | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `GET /shifts` | Shift history. Supports `user_id`, `status`, `shift_type`, `from`, `to`, `cursor`, and `limit` up to 100. | | `GET /shifts/active` | Active and on-break shifts. | | `GET /shifts/{shift_id}` | One shift. | | `GET /shifts/members/{discord_id}/stats` | Shift totals for one Discord member. Supports `from` and `to`. | | `GET /shifts/leaderboard?window=30d` | Ranked totals for `7d`, `30d`, `90d`, or `all`. | ## 7.2 - Logs | Method and path | Description | | -------------------- | ---------------------------------------------------------------------------------------------------- | | `GET /logs` | Logs with `type`, `target_id`, `created_by`, `status`, `from`, `to`, `cursor`, and `limit` up to 50. | | `GET /logs/{log_id}` | One log. | The `context` object contains only documented public fields. Internal metadata and import-provider identifiers are omitted. ## 7.3 - ER:LC `GET /erlc` requires `erlc.read` and returns CADLY's existing cached snapshot. It never wakes a sleeping server or requests a refresh. The snapshot contains: - Server name, `OwnerId`, `CoOwnerIds`, player capacity, `JoinKey`, account verification setting, and team balance - Players and their team, callsign, location, permission, and wanted stars - Staff groups - Join, kill, command, moderator-call, and emergency-call data - Queue and vehicles - `cached_at` and `stale` cache metadata `JoinKey` is the player-visible in-game join code. It is not an API credential. CADLY's ER:LC API credential is never returned. When no cached snapshot exists, the endpoint returns a generic `503` response. ## 7.4 - Leave of absence | Method and path | Description | | ------------------- | ----------------------------------------------------------------------------------- | | `GET /loa` | LOA history with `user_id`, `status`, `from`, `to`, `cursor`, and `limit` up to 50. | | `GET /loa/active` | Active LOAs ordered by end time. | | `GET /loa/{loa_id}` | One complete LOA record. | ## 7.5 - Sessions All session endpoints require `sessions.read` and the slot rules described above. | Method and path | Description | | ------------------------------------ | --------------------------------------------------------------------------------------------- | | `GET /sessions/current` | Current state, timing, cached players and in-game staff, voters, and recent lifecycle events. | | `GET /sessions/history` | Session history with cursor pagination and a limit up to 50. | | `GET /sessions/{session_id}` | Stored lifecycle information, configuration snapshot, voters, and events. | | `GET /sessions/{session_id}/metrics` | Stored player, staff, queue, command, kill, and average-playtime samples. Limit up to 250. | No additional telemetry is collected for this API. ## 7.6 - Applications Only these application endpoints are available: | Method and path | Description | | ------------------------------------------------------- | ------------------------------------------------------------------------------ | | `GET /applications` | Application ID, public ID, name, description, open state, URL, and timestamps. | | `GET /applications/{application_id}/metrics?window=30d` | Counts for `7d`, `30d`, `90d`, or `all`. | | `GET /applications/{application_id}/submissions` | Submission identity, released status, and timestamps. Limit up to 25. | Submission answers, review notes, staged decisions, AI data, input telemetry, and internal settings are not returned. # 8 - Example ```bash curl https://api.cadly.me/v1/sessions/current \ -H "Authorization: Bearer $CADLY_API_KEY" \ -H "X-Server: A" ``` For a mono-server setup, remove the `X-Server` header.