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

# API conventions

> The Pinework REST API lives under /api/v1, takes a bearer token, returns one error envelope with 9 types, and is pre-1.0, so routes can change.

These rules hold across the Pinework REST API. Each reference page lists its own routes and error codes. The API is pre-1.0. Routes, fields and codes can change without notice.

## Every path sits under /api/v1

The production base URL is `https://api.pinework.ai/api/v1`. A route listed as `/api/v1/tasks` lives at `https://api.pinework.ai/api/v1/tasks`.

The API has one version today, `2026-03`. You can send it in a `Pine-Version` header. Any other value returns 400 `unsupported_api_version`.

Each response carries a `Request-Id` header. Quote it when you report a problem.

## You sign in with a bearer token

Send your token in the `Authorization` header:

```bash theme={null}
curl "https://api.pinework.ai/api/v1/tasks?limit=5" \
  -H "Authorization: Bearer $TOKEN"
```

Pinework accepts these tokens:

| Token | Where it comes from | Acts as |
| - | - | - |
| Login token | `pinework login`, or signing in to the Pinework app | You, in the one workspace it is bound to |
| Dashboard session | Signing in to the dashboard | You |
| Agent token (a JWT) | Pinework issues one to each run | That agent, in its workspace |

A person can belong to several workspaces. With a dashboard session, the `Pinework-Workspace-Id` header picks one. Its value is the workspace UUID. A login token works only in its own workspace. Naming another workspace returns 403 `not_permitted`.

Some routes refuse an agent token, such as revealing a secret or answering an approval. Those return 403.

## Errors

Errors from current routes have this shape:

```json theme={null}
{
  "error": {
    "type": "conflict_error",
    "code": "secret.duplicate_name",
    "message": "A secret with this name already exists",
    "requestId": "req_7Hk2Lm9Qp4Rs1Tv8"
  }
}
```

| Field | Always present | Holds |
| - | - | - |
| `type` | Yes | One of the 9 types below. |
| `code` | Yes | A stable code to branch on, such as `secret.duplicate_name`. |
| `message` | Yes | A sentence for a person. Do not parse it. |
| `requestId` | Yes | The same value as the `Request-Id` header. |
| `param` | No | The field or header at fault. |
| `details` | No | Extra data. Validation errors list each bad field in `details.fields`. |
| `remediation` | No | A short name for the fix. |
| `docsUrl` | No | A page that explains the error. |
| `allowedValues` | No | The values that would have worked. |

| Type | HTTP status |
| - | - |
| `invalid_request_error` | 400, 413 or 422 |
| `authentication_error` | 401 |
| `authorization_error` | 403 |
| `not_found_error` | 404 |
| `conflict_error` | 409 |
| `idempotency_error` | 409 |
| `external_dependency_error` | 424 |
| `rate_limit_error` | 429 |
| `api_error` | 500, 503 or 504 |

These codes can come from any route:

| Code | HTTP | When | What to do |
| - | - | - | - |
| `validation_error` | 400 | The body or query failed its schema. | Fix each field in `details.fields`. |
| `payload_too_large` | 413 | The body is over the route's size limit. | Send less, or split it. |
| `auth_missing` | 401 | No `Authorization` header. | Send a bearer token. |
| `auth_invalid` | 401 | The token is unknown, expired or revoked. | Sign in again. |
| `auth_revoked` | 401 | The run that held this agent token was revoked. | Stop using the token. |
| `auth.agent_jwt_revoked` | 401 | The agent's tokens were reset. | Get a new token. |
| `not_permitted` | 403 | You are not a member of the workspace you named. | Check `Pinework-Workspace-Id`. |
| `http_exception` | varies | A route refused the call. The `message` says why. | Read the message. |
| `resource_not_found` | 404 | The id does not exist, or you cannot see it. | Check the id. |
| `not_found_or_denied` | 404 | Not found, or your access rules hide it. | Check the id and your access. |
| `route_not_found` | 404 | No route matches the method and path. | Check the path. |
| `invalid_id_format` | 400 | An id is not in a form the route reads. | Use the id form the page names. |
| `duplicate_resource` | 409 | A record with these values exists. | Change the unique value. |
| `invalid_status_transition` | 422 | The status change is not allowed. | Pick a status from `allowedValues`. |
| `transient_conflict` | 503 | A concurrent write got in the way. | Retry. |
| `database_unavailable` | 503 | The database is at capacity or dropped the link. | Retry shortly. |
| `query_timeout` | 504 | The query ran out of time. | Narrow the request and retry. |
| `internal_error` | 500 | An unexpected failure. | Retry, then report the `requestId`. |

Some older routes still return a plain `{ "error": "<message>" }` or an `error` object with no `type`. Branch on the HTTP status first, then on `code` when it is there.

## Lists return pages

A list returns `object: "list"`, a `data` array and `hasMore`. Most lists default to 25 items and allow up to 100 with `limit`. List routes use one of three cursor styles:

| Send | To get the next page from | Examples |
| - | - | - |
| `startingAfter` (or `endingBefore`) | The `id` of the last item in `data` | Tasks, grants, learnings, wiki pages |
| `cursor` | `nextCursor` in the response | Secrets, connections, goals |
| `after` | `cursor` in the response | Agents |

Each reference page says which style its list uses. Stop when `hasMore` is false.

## Batch gets take up to 100 ids

Some list routes take `ids`, a comma-separated list of ids. They answer in the order you asked, with `null` for an id that is missing or hidden. These include `/api/v1/tasks`, `/api/v1/agents`, `/api/v1/runs`, `/api/v1/files`, `/api/v1/conversations` and `/api/v1/wiki/pages`.

* More than 100 ids returns 400 `too_many_ids`.
* `ids` with a filter or paging parameter returns 400 `ids_exclusive`.

## Idempotency keys make a retry safe

Send an `Idempotency-Key` header on a write you might retry. Some routes require it, such as creating or revoking a grant. A missing key there returns 400 `idempotency_invalid_key`.

* A key is 1 to 255 characters, with no spaces or control characters.
* Pinework keeps a key for 24 hours.
* A key is scoped to you, the workspace, the method and the path.
* A retry with the same key and body replays the first successful answer. The response carries `Idempotent-Replayed: true`.
* After a 4xx answer, the key is free again, so a retry runs anew.

| Code | HTTP | When | What to do |
| - | - | - | - |
| `idempotency_invalid_key` | 400 | The key is missing where required, empty, too long or has spaces. | Send a valid key. |
| `idempotency_key_reused` | 409 | You used the key for a different body. | Use a new key. |
| `idempotency_request_in_progress` | 409 | The first request with this key is still running. | Wait, then retry. |
| `idempotency_effect_unknown` | 409 | The first request failed partway. | Check the record, then retry with a new key. |

## Bodies have size limits

| Route | Limit |
| - | - |
| Most routes | 2 MiB |
| File upload and replace, and the MCP endpoint | 72 MiB |
| Skill create and bundle replace, plugin install and update | 24 MiB |
| Harness config import | 24 MiB |
| Wiki ingest | 12 MiB |
| Automation webhook | 1 MiB |

A larger body returns 413 `payload_too_large`.

## Some routes are rate limited

Sign-in routes count requests per minute from each IP address. An automation webhook takes 60 calls per hour. Over the limit returns 429 `rate_limit_exceeded`. The `Retry-After` header and `details.retryAfter` give the wait in seconds. If the limiter itself is down, the request fails closed with 503 `rate_limiter_unavailable`.

Sending a conversation message can return 429 `rate_limited` when the agent cannot take a turn yet. Retry later.

## The OpenAPI document needs sign-in

Pinework serves an OpenAPI document at `https://api.pinework.ai/api/openapi.json`. It needs the same bearer token as any route. Some routes describe their responses only in part. When it and a reference page disagree, trust the reference page.

<CardGroup cols={2}>
  <Card title="CLI conventions" href="/reference/cli">
    The CLI wraps these routes and prints JSON.
  </Card>

  <Card title="Task" href="/reference/task">
    A worked reference page with routes and codes.
  </Card>

  <Card title="Secret" href="/reference/secret">
    Routes a person must call, not an agent.
  </Card>

  <Card title="Connect your agent" href="/guides/connect-your-agent">
    Sign in from Claude Code, Codex or Cursor.
  </Card>
</CardGroup>


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