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

# Authentication

> Authenticate every ABConvert API request with a bearer token scoped to one shop.

Send your token as a bearer token in the `Authorization` header of every request.

```bash theme={null}
curl https://api.abconvert.io/v1/experiments \
  --header "Authorization: Bearer <token>"
```

The one exception is [`GET /exports/{id}/download`](/api-reference/exports/download-an-export-file), which takes no `Authorization` header: the signed link is the credential. Anyone holding a download link can read the data until the link expires, so treat it like the export it opens.

## Tokens

A token belongs to one shop and reaches that shop only. If you manage several stores, hold one token per store.

Create and revoke tokens in the ABConvert admin, under **Settings → MCP & API Access**.

<Warning>
  Treat a token like a password. Store it in your secret manager, never in
  client-side code or a repository. If a token leaks, revoke it in the
  ABConvert admin. Revocation takes effect immediately.
</Warning>

## Scopes

| Scope | Grants |
| - | - |
| `read_experiments` | List and retrieve tests, read results, and create, poll, and download exports |
| `write_experiments` | Everything `read_experiments` grants, plus create, update, lifecycle actions, and schedule changes |

`GET` requests need `read_experiments`, and so do [`POST /v1/experiments/{id}/results`](/api-reference/results/create-a-custom-result-query) and [`POST /v1/experiments/{id}/exports`](/api-reference/exports/create-an-export-job), which read data and change nothing about the test. Every other request needs `write_experiments`. Scope and rate-limit budget are separate questions: see [Rate limits](/api-reference/overview#rate-limits).

Write includes read, so a write token needs no separate read scope. New tokens default to read. Grant write only to integrations that manage tests.

## Authentication errors

| Status | `type` | `code` | Meaning |
| - | - | - | - |
| 401 | `authentication_error` | `missing_token` | The request has no `Authorization` header. |
| 401 | `authentication_error` | `invalid_token` | The token is malformed, doesn't exist, has expired, or names a shop ABConvert can no longer act for. |
| 401 | `authentication_error` | `token_revoked` | Someone revoked the token in the ABConvert admin. |
| 403 | `permission_error` | `insufficient_scope` | The token's scope doesn't allow this operation. |
| 403 | `permission_error` | `api_access_disabled` | API access is turned off for this shop. Can occur on any request. |

An expired token reads the same as one that never existed, so a sudden `invalid_token` on an OAuth connector token usually means "refresh, then retry." Uninstalling the app also invalidates the shop's tokens; reinstall to restore access.

Each one returns the API's standard error shape:

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "token_revoked",
    "message": "This token was revoked. Create a new one in Settings → MCP & API Access."
  }
}
```

`insufficient_scope` adds a `details` object naming the token's scope and the required one:

```json theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "This token has the `read_experiments` scope. This request needs `write_experiments`.",
    "details": { "required_scope": "write_experiments", "token_scope": "read_experiments" }
  }
}
```

A 403 can also mean your plan doesn't include the test type you asked for. Those errors share the `permission_error` type but carry different codes. See [Feature availability](/api-reference/overview#feature-availability).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.