# API overview

> Where the HTTP API lives, how requests and responses are shaped, and how errors and pages work.

emit has one HTTP API, with JSON bodies. The SDKs and the CLI are built on it, so anything they do, you can do with `curl`.

## Base URL

Each cluster has its own address: its **endpoint**, shown on the cluster's **Overview** page in the dashboard. Every path in this reference follows it:

```http
POST https://<your cluster's endpoint>/v1/spaces/orders/jobs
```

The examples read it from `$EMIT_URL`, and an API token from `$EMIT_TOKEN`.

## Authentication

Send an API token in the `Authorization` header:

```bash
curl -s "$EMIT_URL/v1/spaces/orders/jobs?limit=10" -H "Authorization: Bearer $EMIT_TOKEN"
```

See [Authentication](/docs/api/authentication) for scopes, and for making and revoking tokens.

## Requests and responses

- Request bodies are JSON: send `Content-Type: application/json`.
- Field names are camelCase: `maxAttempts`, `createdAtMs`.
- Times are milliseconds. A name ending in `Ms` is a duration (`timeoutMs`), and one ending in `AtMs` is a Unix time (`createdAtMs`).
- Numbers that can be larger than 32 bits, such as times, come back as **strings**: `"createdAtMs": "1790726132737"`. You can send them as numbers or strings.
- Statuses and other fixed values are upper case: `"status": "COMPLETED"`, `"scopes": ["CREATE", "READ"]`. Send them in upper case too: `?status=FAILED`.
- Your own data (a payload, a result, an event, a signal's data) goes in and comes back as the JSON you sent. Its keys may come back in a different order.
- A field emit doesn't know is refused with `400`.

## Batches

The calls that change jobs take many at once. Creating 500 jobs in one request is far faster than 500 requests. Send an array, or an object with the list under its name, or a single object for a batch of one:

```json
[{ "name": "resize-image", "payload": { "imageId": "img_881" } }, { "name": "resize-image", "payload": { "imageId": "img_882" } }]
```

The answer has one entry per item, in the order you sent them. Each one is a result or a rejection of its own, so check every entry, not only the HTTP status:

```json
{ "outcomes": [{ "status": "CANCELLED" }, { "rejection": { "reason": "ALREADY_FINISHED", "message": "" } }] }
```

## Errors

A request that's refused as a whole gets an HTTP error status and a JSON body:

```json
{ "code": "PermissionDenied", "message": "the token's scopes do not allow this", "retryAfterMs": null }
```

| Status | `code` | What it means |
| --- | --- | --- |
| `400` | `InvalidArgument` | The request breaks a rule. `message` says which. |
| `401` | `Unauthenticated` | No token, or a token emit doesn't know. |
| `403` | `PermissionDenied` | The token lacks the scope, or isn't for this space. |
| `404` | `NotFound` | No such job. A space that doesn't exist yet reads as an empty one with the default settings. |
| `409` | `FailedPrecondition` | The request doesn't fit the current state, such as asking for a link to an attempt that's over. |
| `429` | `ResourceExhausted` | emit is busy: a limit on jobs in flight was reached. Wait `retryAfterMs` (also in the `Retry-After` header, in seconds) and send the same request again. |
| `500`, `503` | `Internal`, `Unavailable` | Something went wrong on emit's side. Retry with a backoff. |

Retrying is safe for `429` and `5xx`. For creates, give each job a `key` so that a retry can't make it twice. See [When emit is busy](/docs/concepts/jobs#when-emit-is-busy).

## Pagination

Lists are newest first. Ask for up to 1,000 at a time with `limit`. The answer's `nextPage` is where the next page starts: pass it back as `page`. An empty `nextPage` means there's nothing more. From emit 0.6 a page can be shorter than `limit`, even empty, and still have a `nextPage`: keep asking until `nextPage` comes back empty, and never stop at a short page.

```bash
curl -s "$EMIT_URL/v1/spaces/orders/jobs?name=send-receipt&limit=100" -H "Authorization: Bearer $EMIT_TOKEN"
# {"jobs":[...],"nextPage":"01M3QSRN3703HA2Z5PZY8WPRQB"}

curl -s "$EMIT_URL/v1/spaces/orders/jobs?name=send-receipt&limit=100&page=01M3QSRN3703HA2Z5PZY8WPRQB" \
  -H "Authorization: Bearer $EMIT_TOKEN"
```

## Streams

Following a job returns a stream: server-sent events by default, or a WebSocket if you ask for an upgrade. Each message is one JSON event. See [Follow a job](/docs/api/jobs#follow-a-job).

## Reference

:::cards {cols=2}
:::card Jobs {href="/docs/api/jobs"}
Create, read, list, follow and cancel jobs.
:::
:::card Workers {href="/docs/api/workers"}
Claim jobs and report on them.
:::
:::card Workflow runs {href="/docs/api/runs"}
Start runs, send signals, read a run's tree.
:::
:::card Spaces {href="/docs/api/spaces"}
Settings and counts.
:::
:::
