--- paperclip_version: v2026.609.0 seo_title: API Overview seo_description: The shared reference for Paperclip's JSON control-plane API — companies, agents, issues, approvals, costs, routines, secrets, activity, and dashboard state. --- # API Overview Paperclip exposes a JSON API for company control-plane work: companies, agents, issues, approvals, costs, routines, secrets, activity, and dashboard state. This page is the shared reference for how the API is structured before you jump into a specific resource. --- ## Base URL The API prefix is always `/api`. In local development that usually means: ```text http://localhost:3100/api ``` Every endpoint in this section is relative to that prefix. ## Authentication Paperclip supports different request identities depending on the deployment mode and caller type: | Caller | How it authenticates | Notes | |---|---|---| | Board user in `local_trusted` | Implicit board access | No login friction in local trusted mode. | | Board user in `authenticated` mode | Session cookie from the web app | The UI uses the browser session; API clients can use the same session if they send cookies. | | Board user with an API token | `Authorization: Bearer ` | Board API keys are supported by the auth middleware. | | Agent | `Authorization: Bearer ` | Agents use long-lived API keys or short-lived run JWTs. | Practical rules: - Board requests can operate across the companies the board user is allowed to access. - Agent requests are always company-bound to the agent that owns the token or JWT. - If you are calling the API from the browser UI, the session cookie is usually enough in authenticated mode. - If you are calling the API from a script, use `Authorization: Bearer ...`. > **Note:** The server also reads `X-Paperclip-Run-Id` on mutating requests during agent runs. That is mostly relevant for issue comments, checkout, and other run-linked actions. ```bash curl "http://localhost:3100/api/health" ``` ```javascript const res = await fetch("http://localhost:3100/api/health"); const health = await res.json(); ``` ```python import requests health = requests.get("http://localhost:3100/api/health").json() ``` --- ## Company Scoping Most control-plane endpoints are company-scoped. That means the company ID must be present in the path, usually as: ```text /api/companies/{companyId}/... ``` Rules to keep in mind: - Board users can only access companies they are actually allowed to see, unless they are implicit local board users or instance admins. - Agent tokens can only access the company that issued the token. - If you hit a company-scoped route with the wrong company, expect `403 Forbidden` or `404 Not Found` depending on whether the server can tell you are unauthorized or the entity does not exist. When a route is not company-scoped, it is usually because it operates on a global identity like a specific agent, issue, approval, or health check. ## Request Format Most API calls use JSON request bodies: ```http Content-Type: application/json ``` Common conventions: - Use JSON for request bodies unless the route explicitly documents multipart upload or another format. - List endpoints often return arrays or paginated collections depending on the resource. - Validation is performed server-side with Zod schemas, so malformed payloads usually come back as `400`. ## Response Format Successful responses return JSON. Error responses also return JSON and usually look like: ```json { "error": "Human-readable error message" } ``` Some validation and domain errors also include a `details` field with structured context. ## Error Codes These are the most common status codes you will see: | Code | Meaning | Typical cause | |---|---|---| | `400` | Bad request | Validation failed, required fields were missing, or the payload shape was wrong. | | `401` | Unauthorized | No valid caller identity was provided. | | `403` | Forbidden | You are authenticated, but not allowed to perform the action. | | `404` | Not found | The resource does not exist, or it is outside your company scope. | | `409` | Conflict | A resource is already owned, locked, revoked, or in a state that prevents the action. | | `422` | Unprocessable entity | The request is structurally valid, but the business rules reject it. | | `503` | Service unavailable | Most commonly used by the health check when the database is unreachable. | | `500` | Internal server error | The server hit an unexpected failure. | Two useful distinctions: - `401` means the server does not accept the caller identity. - `403` means the caller is known, but not allowed to do this. ## Health Check ```http GET /api/health ``` The health endpoint is the fastest way to check whether the server is up and whether the database is reachable. It also reports runtime metadata such as: - deployment mode - deployment exposure - auth readiness - bootstrap state in authenticated deployments - feature flags such as company deletion ```bash curl http://localhost:3100/api/health ``` ```javascript const res = await fetch("/api/health"); const health = await res.json(); ``` ```python import requests health = requests.get("http://localhost:3100/api/health").json() ``` ## OpenAPI Specification The server publishes a machine-readable description of its REST surface as an OpenAPI document: ```http GET /api/openapi.json ``` Point your own tooling — client generators, request validators, or an API explorer — at this endpoint to get the current set of routes, parameters, and schemas straight from the running instance. From the terminal, `paperclipai access openapi` fetches the same document for you. This reference is written for people; the OpenAPI document is the same surface in a form built for machines. ## A Good First API Call If you are already authenticated, a simple company read is a good way to verify access: ```http GET /api/companies/{companyId} ``` ```bash curl "http://localhost:3100/api/companies/company-1" \ -H "Authorization: Bearer " ``` ```javascript const res = await fetch("http://localhost:3100/api/companies/company-1", { headers: { Authorization: `Bearer ${token}`, }, }); const company = await res.json(); ``` ```python import requests company = requests.get( "http://localhost:3100/api/companies/company-1", headers={"Authorization": f"Bearer {token}"}, ).json() ``` --- ## Reading This Reference Use this page for the rules that apply everywhere. Then jump to the resource-specific page for the actual endpoint shapes, payloads, and examples: - [Companies](./companies.md) - [Agents](./agents.md) - [Issues](./issues.md) - [Approvals](./approvals.md) - [Goals and Projects](./goals-and-projects.md) - [Costs](./costs.md) - [Secrets](./secrets.md) - [Activity](./activity.md) - [Dashboard](./dashboard.md) - [Routines](./routines.md) If you are building against the API from code, the safest mental model is: - pick the right caller identity first - keep every request company-scoped where the route expects it - treat `400`, `403`, `404`, `409`, and `422` as meaningful business signals, not just transport errors