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

# Authentication

> The Endstate API uses bearer tokens. There are two credential types: an API key for server-to-server requests and a session token for chip-scoped actions.

## Overview

Every request to the Endstate API must include an `Authorization: Bearer <token>` header. There are exactly two credential types — no publishable key exists.

| Credential    | Prefix         | Used by                                   | Sent as                              |
| ------------- | -------------- | ----------------------------------------- | ------------------------------------ |
| API key       | `end_sk_...`   | Your server                               | `Authorization: Bearer end_sk_...`   |
| Session token | `end_sess_...` | Your server or client, after verification | `Authorization: Bearer end_sess_...` |

***

## API key

API keys are long-lived and scoped to your organization. They are for server-to-server use only.

* Accepted on most `/v1/*` endpoints. Some actions require a session token instead — for example, creating a claim or a transfer — and `GET /v1/session-tokens/current` accepts only a session token.
* Returned once at creation — if you lose it, you must obtain a new one.
* Issued by Endstate during onboarding.
* Rotation and revocation are handled by Endstate — there is no self-serve
  key-management endpoint. Contact Endstate to rotate a key or to revoke a
  compromised one; a replacement is issued and the old key is disabled.

<Warning>
  Never expose your API key to a browser, mobile app, or any client-side
  environment. Treat it with the same care as a database password. If an API key
  is compromised, contact Endstate immediately.
</Warning>

### Example request

```bash theme={null}
curl https://api2.endstate.io/v1/units \
  -H "Authorization: Bearer end_sk_test_..."
```

```json theme={null}
{
  "units": [
    {
      "id": "8e1a7f50-90ab-4cde-f012-3456789abcde",
      "name": "Black Hoodie — Size M",
      "external_id": "sku-hoodie-black-m",
      "created_at": "2026-05-14T10:30:00.000Z"
    }
  ],
  "pagination": {
    "limit": 50,
    "has_more": false,
    "next_cursor": null
  }
}
```

***

## Session token

Session tokens are short-lived credentials issued when a chip is verified. They prove that a specific chip was physically tapped moments ago.

* Issued by `POST /v1/chips/{chip_id}` (chip verification).
* Default lifetime: **600 seconds**. Configurable per-request via the `ttl` parameter (60–3600 seconds).
* Scoped to exactly one chip, one unit, and one organization.
* Returned once — opaque and unrecoverable after issuance.
* Required to authorize unit-scoped actions on behalf of the user who tapped — for example, creating a claim (`POST /v1/units/{unit_id}/claims`) or a transfer (`POST /v1/units/{unit_id}/transfers`).
* The only credential accepted by `GET /v1/session-tokens/current`, which lets a client confirm the scope of its session without ever seeing your API key.

<Note>
  A session token authorizes unit-scoped actions on behalf of the user who
  tapped — including **claiming** a unit (handing it to a recipient). See
  [Claims](/concepts/claims).
</Note>

### Example request

After verifying a chip, pass the returned session token as the bearer credential to introspect it:

```bash theme={null}
curl https://api2.endstate.io/v1/session-tokens/current \
  -H "Authorization: Bearer end_sess_..."
```

```json theme={null}
{
  "chip_id": "ABCDEF0123",
  "unit_id": "8e1a7f50-90ab-4cde-f012-3456789abcde",
  "organization_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "expires_at": "2026-05-14T10:40:00.000Z"
}
```

***

## Telling them apart

The API identifies credential type by prefix before any other validation:

* `end_sk_...` — API key. Full organization access; server-only.
* `end_sess_...` — Session token. Single-chip scope; short-lived.

Each endpoint accepts a specific credential type, and sending the wrong one returns a `401` before any other validation. For example, an API key sent to `GET /v1/session-tokens/current` is rejected, as is a session token sent to an endpoint that requires an API key. Endpoints that accept either type — such as reading claim status — take whichever you send.

***

## Authentication errors

Branch on `error.code` in your error handling, not on the HTTP status or `message`.

| Code                               | HTTP | When                                                                                |
| ---------------------------------- | ---- | ----------------------------------------------------------------------------------- |
| `auth.unauthorized`                | 401  | Credential is missing, malformed, or the wrong type for this endpoint               |
| `auth.forbidden`                   | 403  | Credential is valid but does not have permission for the requested resource         |
| `session_token.invalid_or_expired` | 401  | Session token is unknown, expired, or has been revoked                              |
| `session_token.wrong_chip`         | 403  | Session token is valid but is bound to a different chip than the one in the request |

Cross-organization resource access returns **404** rather than 403 — the API deliberately does not reveal whether a resource exists in another organization. See [Errors](/conventions/errors) for the full error envelope format and code taxonomy.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Environments" icon="layers" href="/environments">
    The API base URL and how to build and test with test chips before going
    live.
  </Card>

  <Card title="Verify a unit" icon="scan-line" href="/guides/verify-a-unit">
    Walk through the end-to-end chip verification flow that issues a session
    token.
  </Card>
</CardGroup>
