---
url: https://docs-standcast.usefachada.dev/api/overview.md
---

# API overview

The Standcast API lets your systems read your organization's data and receive events. It is versioned under `/v1` and described by an OpenAPI 3.1 document.

* Base URL: `https://api.standcast.app`
* OpenAPI document: `https://api.standcast.app/openapi.json`
* Interactive reference, with every operation, parameter, example and error: [API reference](https://api.standcast.app/reference)

## What the API offers

* **Read organization data** with an [API key](/admin/api-keys):

  | Operation | Scope | Returns |
  | --- | --- | --- |
  | `GET /v1/org/teams` | `teams:read` | The teams in `standcast.yml`: name, time zone, workdays, times and members |
  | `GET /v1/org/dailies` | `dailies:read` | Confirmed dailies, filtered by `date` and `team` |
  | `GET /v1/org/dailies/{id}` | `dailies:read` | One daily |
  | `GET /v1/org/digests` | `digests:read` | Digest deliveries, with who did not send the daily, filtered by `team`, `from` and `to` |
  | `GET /v1/org/blockers` | `blockers:read` | Confirmed blockers, filtered by `date` and `team` |
  | `GET /health` | none | Service health |

* **Receive events** with [webhooks](/api/webhooks): `daily.submitted`, `blocker.opened` and `digest.sent`.

The API returns only what people confirmed and what already appears in the digest. It never returns drafts, suggestions, per-person metrics, rankings or aggregates.

## What the API does not offer

**You cannot send a daily through the public API.** AI agents send dailies through the [MCP server](/members/mcp) or the [Claude Code skill](/members/claude-code), and scripts through the [CLI](/members/cli).

Why: Standcast never sends a daily on someone's behalf without their explicit confirmation. That rule lives in one place, the MCP server and the skill, which show the person the daily and send it only after they confirm. An API key belongs to the organization, not to a person, so it cannot carry that confirmation. The routes the CLI uses to send dailies are internal, authenticated with a person's session, and not part of the public contract.

## Quick start

```bash
curl -H "Authorization: Bearer $STANDCAST_API_KEY" \
  https://api.standcast.app/v1/org/teams
```

```json
{
  "data": [
    {
      "name": "Alpha",
      "timezone": "America/Sao_Paulo",
      "workdays": ["mon", "tue", "wed", "thu", "fri"],
      "remindAt": "09:00",
      "digestAt": "10:00",
      "memberLogins": ["alice", "bob"]
    }
  ],
  "next_cursor": null
}
```

## Authentication

The public Standcast API authenticates with an **API key**. Send it as a bearer token on every request:

```http
GET /v1/{resource} HTTP/1.1
Host: your-standcast-host
Authorization: Bearer sck_xxxxxxxxxxxxxxxxxxxxxxxx
```

API keys are prefixed with `sck_`. Every operation under `/v1/org` requires one. Only `sck_` keys are accepted there: a session token (`sc_`) sent to `/v1/org` fails with `401 unauthorized`, and an API key sent to a session-only route fails the same way.

### Creating a key

Only an organization admin can create keys. An admin signs in with GitHub, opens the Standcast setup page of the organization and uses the **API keys** section: name the key, pick its scopes and create it. For a personal account, the account owner is the admin. Each key belongs to one organization and sees only that organization's data.

The full secret is shown once, when the key is created. Standcast stores only a hash and cannot show it again, so copy it right away. The list shows each key's prefix (the first 8 characters after `sck_`), scopes, creation date and last use, which is updated at most once per minute.

### Scopes

Grant the narrowest scope that works.

| Scope | Allows |
| --- | --- |
| `teams:read` | `GET /v1/org/teams` |
| `dailies:read` | `GET /v1/org/dailies` and `GET /v1/org/dailies/{id}` |
| `digests:read` | `GET /v1/org/digests` |
| `blockers:read` | `GET /v1/org/blockers` |

A request that needs a scope the key does not have fails with `403 insufficient_scope`. The error names the missing scope in `required_scope`:

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "api key lacks the dailies:read scope",
    "required_scope": "dailies:read"
  }
}
```

Missing, malformed, unknown and revoked keys fail with `401 unauthorized`. Each key is limited to 600 requests per minute (see Rate limits).

### Revoking a key

An admin can revoke a key at any time. Revocation takes effect immediately and later requests with that key fail with `401 unauthorized`. Rotate a key by creating the new one first, deploying it, then revoking the old one.

### Storing keys safely

* Keep keys in a secret manager or in environment variables, never in source control or client-side code.
* Use one key per integration so a leak can be revoked without breaking the others.
* Never log the `Authorization` header.
* If a key may have leaked, revoke it immediately.

### Session tokens (`sc_`)

Tokens starting with `sc_` are session tokens issued to the Standcast CLI, skill and MCP server after a GitHub login. They are not an integration method and the operations they unlock are not part of this reference.

## Errors

Every error response uses the same envelope:

```json
{ "error": { "code": "invalid_daily", "message": "invalid daily", "issues": [] } }
```

`code` is the stable contract: branch on it, not on `message`. `message` is always English and may change. `issues` is present only on validation errors and lists the failing fields. `required_scope` is present only on scope errors.

| Code | HTTP status | What to do |
| --- | --- | --- |
| `unauthorized` | 401 | The credential is missing, invalid, expired or revoked. Authenticate again or use a valid key. |
| `invalid_signature` | 401 | Only on the GitHub webhook receiver: the `X-Hub-Signature-256` header is missing or does not match the body. Nothing was processed. |
| `invalid_request` | 422 | The request is malformed. Check `issues` and fix the listed fields. A signed GitHub webhook body that is not JSON also returns this code. |
| `invalid_daily` | 422 | The daily payload failed validation. Check `issues` against the schema. |
| `org_not_found` | 404 | The organization does not exist or the Standcast app is not installed on it. Check the name and the installation. |
| `org_not_configured` | 409 | The organization has no valid `standcast.yml`. Add or fix the file in the `.github` repository, then retry. |
| `not_in_team` | 422 | The person is not listed in any team of this organization. Add them to a team in `standcast.yml`. |
| `forbidden` | 403 | The credential is valid but the person or organization is not allowed to perform this action. |
| `insufficient_scope` | 403 | The API key lacks the scope this operation needs. The error carries `required_scope`. Create a key with that scope. |
| `not_found` | 404 | The resource does not exist in the key's organization. |
| `suggestion_already_decided` | 409 | The suggested blocker was already decided the other way. Fetch the draft again; it no longer lists it. |
| `invalid_state` | 400 | An authorization link expired or was tampered with. Start the flow again. |
| `installation_pending` | 409 | The installation is still being processed. Retry in a few seconds. |
| `access_denied` | 403 | The person denied the authorization. Nothing was changed. |
| `slack_oauth_failed` | 502 | Slack rejected the connection. Try connecting again. |
| `linear_oauth_failed` | 502 | Linear rejected the connection. Try connecting again. |
| `payload_too_large` | 413 | Only on the GitHub webhook receiver: the body is larger than 1 MB. Nothing was processed. |
| `rate_limited` | 429 | Too many requests. Wait `Retry-After` seconds, then retry (see Rate limits). |
| `internal` | 500 | Unexpected server error. Retry with backoff and contact support if it persists. |

## Pagination

List operations use opaque cursors.

| Parameter | Where | Description |
| --- | --- | --- |
| `limit` | query | Page size, from 1 to 100. Defaults to 50. |
| `cursor` | query | The `next_cursor` of the previous page. Omit it to get the first page. |

A paginated response carries the items and a `next_cursor`:

```json
{ "data": [], "next_cursor": "eyJpZCI6MTIzfQ" }
```

When `next_cursor` is `null` there are no more pages. Treat the cursor as an opaque string: do not parse it, build it or store it for long. Keep the same filters while paginating.

An invalid `cursor` or a `limit` outside 1 to 100 fails with `422 invalid_request`. Dailies and digests are ordered newest first. For blockers, `limit` counts blockers, not dailies.

## Rate limits

Limits use a fixed 60-second window.

| Credential | Limit |
| --- | --- |
| Session token (`sc_`) | 120 requests per minute |
| API key (`sck_`) | 600 requests per minute |
| Unauthenticated requests | Counted per IP address |
| Starting a CLI session | 10 requests per minute per IP address |

Limits apply per credential, so one noisy integration does not use up another's budget.

### Headers

Rate-limited routes return these headers on every response:

| Header | Meaning |
| --- | --- |
| `RateLimit-Limit` | Requests allowed in the window. |
| `RateLimit-Remaining` | Requests left in the current window. |
| `RateLimit-Reset` | Seconds until the window resets. |

### When you exceed the limit

The response is `429` with code `rate_limited` and a `Retry-After` header holding the seconds to wait:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 23
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 23
```

Wait `Retry-After` seconds before retrying, and spread retries with jitter.

## Versioning

The API is versioned in the path. `/v1` is the current version. The `info.version` of the OpenAPI document is the Standcast release (for example `0.2.0`).

* **Public beta.** While Standcast is in 0.x, `/v1` can still change in incompatible ways. Such changes are listed in the changelog before they ship, with as much notice as possible. The 90-day notice below applies from 1.0.
* New fields may be added to responses at any time. Ignore fields you do not recognize.
* New optional request parameters and new operations may be added to `/v1` without notice.
* A change that breaks existing clients ships only in `/v2`.
* Before `/v1` behavior is removed or changed incompatibly, the change is announced in the changelog at least 90 days in advance.

Every release is listed in `docs/api/CHANGELOG.md`, under the same version as the [GitHub release](https://github.com/ImFelipeOliveira/standcast/releases).

See the [changelog](/changelog) for every release.
