# API conventions

What is true of every endpoint, so the reference pages do not have to repeat
it.

These rules hold across the whole [API reference](/docs/api-projects).

## Base URL

```text
https://pharosbase.com
```

All requests are HTTPS. Request and response bodies are JSON; send
`Content-Type: application/json` on anything with a body.

## Two surfaces

**`/api/v1/*`** is the versioned public contract — contact sync,
transactional events, organizations and projects. These are the endpoints
external integrations should build on. Breaking changes get a new version
path.

**Project-scoped routes** (`/api/contacts`, `/api/lists`, `/api/campaigns`,
`/api/sends`, …) back the dashboard and the MCP tool surface, and accept the
same API keys. They are stable in practice and documented, but they are not
under the `v1` compatibility promise. If an equivalent `v1` endpoint exists,
prefer it.

## Identifying a project

Every project-scoped request names its project, by slug or by internal `prj_…`
id:

- **GET** — as a query parameter: `?projectId=acme-app`
- **POST / PATCH / DELETE** — as a `projectId` field in the JSON body

A project key ignores nothing here: the identifier must still resolve to the
key's own project, or the request fails.

## Status codes

| Code | Meaning |
| --- | --- |
| `200` | Success |
| `201` | Created |
| `400` | Malformed request — bad JSON, missing or invalid field |
| `401` | No credential, or a credential that is invalid, expired or revoked |
| `403` | Authenticated, but the key lacks a required scope |
| `404` | No such project or resource — **also** what you get for a project outside your key's reach |
| `409` | Conflict — most often a slug that is already taken |
| `429` | Rate limited; see `Retry-After` |
| `5xx` | Pharos-side failure; safe to retry with backoff |

Errors carry a single string:

```json
{ "error": "Missing required scope: campaigns:send" }
```

`403` names the scope you are missing, including the `a+b` form when an
endpoint requires several. `404` is deliberately indistinguishable between "no
such project" and "not yours" — see
[Authentication](/docs/authentication#the-three-credential-paths).

What each of these usually means in practice:
[Troubleshooting](/docs/troubleshooting#authentication-and-scopes).

## Idempotency

Where an operation could be retried, it is safe to retry:

- **`POST /api/v1/contacts/sync`** is idempotent by construction. It expresses
  desired state, so replaying the same payload changes nothing.
- **`POST /api/v1/events`** takes an explicit `idempotencyKey`, unique per
  project. A repeat of a key that already sent does not send again.

Build the key from something stable about the event — `order:1234:receipt`,
not a timestamp — so that a retry after a network failure collides on purpose.

## Rate limits

240 requests per minute per key, fixed windows. A `429` includes
`Retry-After` in seconds. Full detail in
[Authentication](/docs/authentication#rate-limits).

## Delivery is asynchronous, except when it is not

Anything that sends to a *user* is queued and retried on transient failure; a
`2xx` means accepted, not delivered. Watch the send log for the outcome.

The exception is `operational` transactional mail — alerts to your own team
inbox, usually with a human waiting. Those are delivered synchronously: `200`
with `status: "sent"` means the sending provider accepted it, and `502` with
`status: "failed"` means it did not go out. Surface that to the person instead
of acknowledging the send.

## Pagination

List endpoints take `limit` and return newest-first. Where a fuller cursor
contract exists it is documented on the endpoint. Do not assume an unbounded
list — ask for what you need.

## Timestamps and encoding

All timestamps are ISO 8601 in UTC (`2026-08-01T14:03:00.000Z`). Email
addresses are normalised to lowercase on write, so `Sam@Example.com` and
`sam@example.com` are one contact.

## Reading these docs as a machine

Every page is available as raw markdown by appending `.md` to its URL:

```text
https://pharosbase.com/docs/conventions.md
```

There is also [`/llms.txt`](/llms.txt) — the index — and
[`/llms-full.txt`](/llms-full.txt), which is every page concatenated into one
document. Agents connected over MCP get the same pages as
`pharos://docs/<slug>` resources.
