# API reference

Everything you can call in the cronai.app API. Paths start at the site's address, `https://www.cronai.app`, and requests and answers are JSON.

The machine-readable document: [/api/v1/openapi.json](/api/v1/openapi.json) (OpenAPI 3.1).

## Authentication

The API takes two credentials. The session cookie `better-auth.session_token` that signing in at /sign-in sets (on https its name carries the `__Secure-` prefix) works for every operation.

An API key works for the operations below marked as taking an API key, in the one workspace it belongs to. A signed-in person makes it on the account page, under API keys, which shows its secret once; an agent sends it as `Authorization: Bearer cronai_…`.

A key holds read scopes only for now (`workflows:read`, `runs:read` and `usage:read`), and reads only what the person who made it may read today.

Where an answer holds a webhook's address, a delivery's or a webhook step's, a key reads only its origin, the scheme and host (`https://hooks.example.com`), never its path or query, and a webhook step's header values as `[redacted]`; a session reads them in full.

A key stops working when it expires, when it is revoked, or when the person who made it leaves the workspace. A key lasts 90 days unless its maker chooses another lifetime, at most 365 days, and a workspace holds at most 5 keys that have not expired or been revoked. Making, listing and revoking keys takes a session.

A request with an `Authorization` header is authenticated by that header alone. A request without a credential, or with a key that is not valid, is answered 401. The published schemas need neither.

### Example call

A first call with an API key: `GET /api/v1/me` answers whose key it is, with its workspace and its scopes.

```sh
curl https://www.cronai.app/api/v1/me \
  -H "Authorization: Bearer cronai_…"
```

It answers 200 with JSON, here for a key with two scopes:

```json
{
  "kind": "api_key",
  "workspace": "A7mGeeUjHdTWXNYhLwbeufCG63EYdLlp",
  "scopes": [
    "workflows:read",
    "runs:read"
  ],
  "user": {
    "id": "LLTh1gLpwlhfadCbYTQNS00hC8h6OeBd",
    "name": "Ada Example",
    "email": "ada@example.com"
  },
  "key": {
    "id": "f9dcf1a3-acc2-43ed-8df6-4c2be893397b",
    "name": "Docs example",
    "prefix": "cronai_2SfWdIAD"
  }
}
```

In a path, `{ws}` is the workspace: the caller's active organization, as get_me answers it. Another workspace answers 404.

## Errors

When it refuses a request, an operation that needs a session says why in a problem document, `application/problem+json` (RFC 9457): a `code` and, for a request that is not valid, `issues` naming each field.

## Limits

Requests are limited per minute, counted in fixed windows.

| Counted | Requests per minute |
| --- | ---: |
| One API key | 20 |
| One workspace, across all its API keys | 50 |
| One signed-in person with a session, the pages of the app they open included | 120 |
| One client address, for requests without a valid credential | 5 |
| One client address, for the OpenAPI document and the published schemas | 60 |
| One client address and echo endpoint, for deliveries to an echo endpoint | 30 |
| One echo endpoint, for the service's own deliveries from a run, which are counted apart | 120 |

An IPv6 client address is counted per /56.

A request past a limit is answered 429 with the problem code `rate_limited` and a `Retry-After` header, the whole seconds until its window ends; its operation does not run.

An API key or a session is checked before the request is counted, since a valid one never counts against its address: a refused request that carries one still costs that check.

## Operations

Every operation's maturity is `alpha`, unless its line says otherwise. A name in braces is a path parameter. A line lists the operation's query parameters, headers and body fields, each optional unless marked required.

Their types, and the schema of each answer, are in the OpenAPI document under the operation's path and method: [/api/v1/openapi.json](/api/v1/openapi.json).

## Account

- `GET /api/v1/me`: Get me. Scope: none. Session or API key.

## Workflows

- `GET /api/v1/workspaces/{ws}/workflows`: List workflows. Scope: workflows:read. Session or API key.
- `POST /api/v1/workspaces/{ws}/workflows`: Create workflow. Scope: workflows:write. Session only. Header: `Idempotency-Key`. Body: `cron` (required), `delivery` (required), `name` (required), `prompt` (required), `runImmediately`, `systemPrompt`, `timezone` (required), `webSearch`.
- `GET /api/v1/workspaces/{ws}/workflows/{id}`: Get workflow. Scope: workflows:read. Session or API key.
- `PATCH /api/v1/workspaces/{ws}/workflows/{id}`: Update workflow. Scope: workflows:write. Session only. Headers: `If-Match` (required), `Idempotency-Key`. Body: `cron`, `delivery`, `name`, `prompt`, `systemPrompt`, `timezone`, `webSearch`.
- `DELETE /api/v1/workspaces/{ws}/workflows/{id}`: Delete workflow. Scope: workflows:write. Session only.
- `POST /api/v1/workspaces/{ws}/workflows/{id}/pause`: Pause workflow. Scope: workflows:write. Session only. Body: JSON, no fields named.
- `POST /api/v1/workspaces/{ws}/workflows/{id}/restore`: Restore revision. Scope: workflows:write. Session only. Header: `If-Match` (required). Body: `revision_id` (required).
- `POST /api/v1/workspaces/{ws}/workflows/{id}/resume`: Resume workflow. Scope: workflows:write. Session only. Body: JSON, no fields named.
- `GET /api/v1/workspaces/{ws}/workflows/{id}/revisions`: List revisions. Scope: workflows:read. Session or API key. Query: `after`, `limit`.
- `POST /api/v1/workspaces/{ws}/workflows/{id}/rotate-secret`: Rotate webhook secret. Scope: deliveries:write. Session only. Body: JSON, no fields named.
- `POST /api/v1/workspaces/{ws}/workflows/{id}/runs`: Run workflow. Scope: runs:trigger. Session only. Header: `Idempotency-Key`. Body: JSON, no fields named.
- `GET /api/v1/workspaces/{ws}/workflows/{id}/trends`: Get workflow trends. Scope: workflows:read. Session, or API key with runs:read too. Query: `from`, `to`.

## Runs

- `GET /api/v1/workspaces/{ws}/runs`: List runs. Scope: runs:read. Session or API key. Query: `workflow_id`, `since`, `after`, `limit`.
- `GET /api/v1/workspaces/{ws}/runs/compare`: Compare runs. Scope: runs:read. Session or API key. Query: `a` (required), `b` (required).
- `GET /api/v1/workspaces/{ws}/runs/{id}`: Get run. Scope: runs:read. Session or API key.
- `GET /api/v1/workspaces/{ws}/runs/{id}/provenance`: Get run provenance. Scope: runs:read. Session or API key.

## Echo endpoints

- `POST /api/v1/workspaces/{ws}/echo-endpoints`: Create echo endpoint. Scope: deliveries:write. Session only. Body: JSON, no fields named.
- `PATCH /api/v1/workspaces/{ws}/echo-endpoints/{token}`: Set echo endpoint secret. Scope: deliveries:write. Session only. Body: `secret` (required).
- `GET /api/v1/workspaces/{ws}/echo-endpoints/{token}/receipts`: List echo receipts. Scope: deliveries:read. Session only.

## Usage

- `GET /api/v1/workspaces/{ws}/usage`: Get usage. Scope: usage:read. Session or API key.

## Workspace status

- `GET /api/v1/workspaces/{ws}/status`: Get workspace status. Scope: workflows:read. Session, or API key with runs:read too.

## API keys

- `GET /api/v1/workspaces/{ws}/keys`: List api keys. Scope: none. Session only.
- `POST /api/v1/workspaces/{ws}/keys`: Create api key. Scope: none. Session only. Body: `expiresInDays`, `name` (required), `password` (required), `scopes` (required).
- `DELETE /api/v1/workspaces/{ws}/keys/{id}`: Revoke api key. Scope: none. Session only.

## Published schemas

- `GET /api/v1/schemas/{name}`: Get a published JSON Schema. Scope: none, no session needed. Maturity: none. Path: `{name}` is `workflow-v1.json` or `workflow-export-v1.json`.
