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

# API v2

> Authentication, response codes, pagination, and the stability promise for the /v2 namespace

`/v2` is where new Sleekplan endpoints are built. It sits **alongside** `/v1` rather than replacing it: your existing integration keeps working unchanged, the same [API key](/docs/authentication/api-keys) authenticates both, and you can call either namespace from the same codebase.

## Authentication

`/v2` accepts **Bearer tokens only**. Unlike `/v1`, there is no Basic auth and no `?api_key=` query parameter.

```bash theme={"system"}
curl https://api.sleekplan.com/v2/groups \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Your key identifies the workspace, so **no workspace or product ID appears anywhere in a `/v2` URL**. Get your key from **Settings → Developer**.

A credential that is missing, malformed, or simply not an API key — an admin dashboard session token, for example — is rejected with `401`.

## Response envelope

Every response carries the same envelope as `/v1`: a `status` and a `data` object.

```json Success theme={"system"}
{
  "status": "success",
  "data": { "group_id": 17, "name": "Acme Corporation" }
}
```

```json Error theme={"system"}
{
  "status": "error",
  "data": { "key": "not_found", "message": "Group not found" }
}
```

Errors carry a machine-readable `data.key`. Branch on that rather than on the message, which is written for humans and may be reworded.

A validation error carries `data.errors` instead of a message, one entry per offending field:

```json 400 theme={"system"}
{
  "status": "error",
  "data": {
    "key": "validation_error",
    "errors": [
      { "field": "id", "message": "An id is required." }
    ]
  }
}
```

## Response codes

**Success**

| Status | When | Body |
| - | - | - |
| `200` | A read, or an update that was applied | The resource, or a page of them |
| `201` | A resource was created | The created resource |
| `204` | A resource was deleted or unassigned | **Empty** — do not parse it |

**Errors**

| Status | `data.key` | Meaning |
| - | - | - |
| `400` | `validation_error` | A field is missing or malformed; `data.errors` lists them |
| `401` | `unauthorized` | Missing, malformed, or non-API-key credential |
| `403` | `forbidden` | The key's user lacks the permission for this action |
| `404` | `not_found` | No such resource **in this workspace** |
| `409` | `identifier_taken` | That identifier is already used in this workspace |
| `429` | `rate_limit` | Over the rate limit |
| `500` | `write_failed` | The write could not be completed — the resource is unchanged, so the call is safe to retry |
| `500` | `internal_error` | Something failed on our side that the endpoint does not name specifically |

> [!NOTE]
>
> `404` means "not in your workspace", which is the same answer you get for a resource that belongs to someone else. A `/v2` request can never reach another workspace's data, because the key decides which workspace it runs against.

### The one that is not a normal error

> [!WARNING]
>
> A feature your plan does not include answers with HTTP **`200`**, not `402` or `403` — and the key is in **`data.code`**, not `data.key`. Checking only the status code makes this read as a success.

```json 200 theme={"system"}
{
  "status": "error",
  "data": {
    "code": "upgrade_required",
    "message": "User groups is available on the Business plan and above.",
    "feature": "groups",
    "feature_name": "User groups",
    "required_plan": "business",
    "required_plan_name": "Business",
    "cta": "billing"
  }
}
```

Treat `status === "error"` as the reliable signal, then look at `data.key` or `data.code`.

## Pagination

List endpoints take `page` and `per_page` and return the page alongside the items.

| Parameter | Default | Notes |
| - | - | - |
| `page` | `0` | **Zero-based.** The first page is `0`, not `1` |
| `per_page` | `30` | Maximum `100`; anything higher is clamped rather than rejected |

```json theme={"system"}
{
  "status": "success",
  "data": {
    "items": [],
    "has_more": true,
    "page": 0
  }
}
```

`page` echoes the page you asked for. Keep requesting the next one while `has_more` is `true` — there is **no total count**, so do not compute a page count.

## Rate limits

**300 requests per 60 seconds per workspace**, independent of plan. Exceeding it returns `429` with `"key": "rate_limit"`.

## Stability

> [!NOTE]
>
> Changes to `/v2` are **additive**. We may add endpoints, optional parameters, and response fields, but we do not remove or rename existing ones, change a field's type, or change a status code.

Write your integration to **ignore response fields it does not recognise**, and new fields will never break it. There is no version to pin and no upgrade to schedule — a breaking change would arrive as a new namespace, not as a change to this one.

## Endpoints

Every `/v2` endpoint, with its parameters, request bodies, and responses, is in the reference under **Endpoints (v2)**.

> **[Groups](/api-reference/group/list-groups)**
>
> Companies, accounts, and teams, and who belongs to them.

> **[Documents](/api-reference/document/list-documents)**
>
> Plans, specs, research — the thinking behind your feedback.

> **[API keys](/docs/authentication/api-keys)**
>
> Create, use, and rotate your key.


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