Pharos
Platform.md

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.

Base URL

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:

{ "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.

What each of these usually means in practice: Troubleshooting.

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.

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:

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

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