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
projectIdfield 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/syncis idempotent by construction. It expresses desired state, so replaying the same payload changes nothing.POST /api/v1/eventstakes an explicitidempotencyKey, 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.