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

# Introduction

> Base URL, content types, pagination and the shape of every response.

One HTTP API, versioned at `/v1`, JSON in and JSON out.

```
https://api.userkit.dev/v1
```

Examples throughout these guides use `$API` for that base. The playground on each
endpoint page already points at it.

## Requests

`Content-Type: application/json` on anything with a body. Bodies are capped at
1 MiB; an oversized one fails at the reader rather than being buffered into memory
first.

Credentials ride `Authorization: Bearer`:

```bash theme={null}
curl -s $API/v1/session -H "Authorization: Bearer uk_st_…"
```

Nothing else is accepted. No query parameter — it would land in access logs and
browser history. No cookie — this API is not cookie-authenticated, which is also
why CORS never needs credentials.

The exception is the publishable key, which travels in the **body** of the
endpoints that accept it, alongside the data those endpoints need anyway.

See [Authentication](/en/api-reference/authentication).

## The active organization

Session-authenticated routes act on one organization. Which one comes from a
header:

```bash theme={null}
curl -s $API/v1/organization/members \
  -H "Authorization: Bearer uk_st_…" \
  -H "X-Organization-Id: org_4b1e…"
```

It **identifies**; it does not authorize. The membership JOIN is what authorizes, so
a code you are not a member of resolves nothing. Omitted, the session's default
organization answers.

## Responses

Success is a `2xx` with a JSON body, except `204`, which has none.

| Status | Used for                                      |
| ------ | --------------------------------------------- |
| `200`  | Read, or a write that returns state           |
| `201`  | Something was created                         |
| `202`  | Accepted — and deliberately says nothing more |
| `204`  | Done, nothing to say                          |

<Note>
  `202` is not "queued". On `forgot-password`, `signup` and the magic-link flows it
  means "accepted, and the answer would be the same either way" — the API does not
  reveal whether an address has an account.
</Note>

Errors are always the same envelope:

```json theme={null}
{ "error": { "code": "forbidden", "message": "your role does not allow this action" } }
```

Branch on `code`. See [Errors](/en/api-reference/errors).

## Timestamps

RFC 3339, UTC:

```json theme={null}
{ "created_at": "2026-07-29T12:00:00Z" }
```

A nullable timestamp is `null`, never an empty string or a zero date.

## Identifiers

UUIDs, except for two:

* **organization public code** — `org_<32 hex>`, what goes in URLs and the
  `X-Organization-Id` header;
* **credential prefixes** — `uk_sk_`, `uk_pk_`, `uk_st_`, `uk_ct_`, and the
  one-time token families.

The internal organization UUID never leaves the API on the surfaces the panel uses.

## Pagination

Collection reads take `limit` and `offset`:

```bash theme={null}
curl -s "$API/v1/contacts?limit=100&offset=200" \
  -H "Authorization: Bearer uk_sk_…"
```

|          |                                                             |
| -------- | ----------------------------------------------------------- |
| `limit`  | Default 50, maximum 200. Anything outside falls back to 50. |
| `offset` | Default 0.                                                  |

The cap is deliberate: enough for a screen, small enough that nobody accidentally
downloads their whole audience.

<Note>
  Out-of-range values are **not** an error — they fall back to the default. Do not
  rely on a `400` to catch a bad page size.
</Note>

## Environments

On the machine surface, the environment is the key's environment, resolved from the
stored row on every request. There is no parameter for it.

On the panel surface, reads take `?environment=live|test`, defaulting to `live`.

Both are covered in [Environments](/en/concepts/environments).

## Rate limits

Public and credential endpoints are throttled per IP; boot is additionally
throttled per publishable key. See [Rate limits](/en/api-reference/rate-limits).

## The surfaces

<CardGroup cols={2}>
  <Card title="Staff plane" icon="user">
    `uk_st_…`. Sign-in, account, organizations, members, roles, keys, and the panel
    view of contacts.
  </Card>

  <Card title="Machine surface" icon="server">
    `uk_sk_…`. `/v1/me` and `/v1/contacts` — your backend talking about your own
    users.
  </Card>

  <Card title="Customer plane" icon="globe">
    Publishable key in the body. `/v1/boot` and `/v1/contact-auth/*`, called from
    your pages.
  </Card>

  <Card title="Contact surface" icon="user-check">
    `uk_ct_…`. `/v1/contact/me` — one of your users reading their own record.
  </Card>
</CardGroup>
