Pharos
API reference.md

Contacts

Reading and editing individual contacts. For keeping a whole audience in sync from your application, use POST /api/v1/contacts/sync instead — it is idempotent, takes up to 5,000 contacts at a time, and respects suppressions.

Every endpoint here names its project with projectId (slug or prj_… id). See API conventions.

GET /api/contacts

List contacts with their subscription status. Filtering happens server-side.

Scope: contacts:read

Parameter In Type Notes
projectId query string Required.
limit query integer Max rows returned.
search query string Case-insensitive substring match on email.
status query enum subscribed, unsubscribed, or no list.
curl "https://pharosbase.com/api/contacts?projectId=acme-app&status=subscribed&limit=50" \
  -H "Authorization: Bearer $PHAROS_API_KEY"
{
  "contacts": [
    {
      "id": "cnt_…",
      "email": "reader@example.com",
      "firstName": "Sam",
      "lastName": null,
      "language": "en",
      "tags": ["beta"],
      "source": "website_signup",
      "status": "subscribed",
      "createdAt": "2026-07-14T09:12:00.000Z"
    }
  ],
  "total": 4821,
  "matched": 4102,
  "returned": 50
}

total is the whole project. matched is how many rows the filters selected. returned is how many came back after limit. The three differ on purpose — a count you can trust is worth more than a page you have to reassemble.

status is derived from list subscriptions, not stored on the contact. no list means the contact belongs to no list at all.

POST /api/contacts

Create one contact. Auto-subscribes it to the project's default list if there is one. For more than a handful, use the sync endpoint.

Scope: contacts:write

Field Type Notes
projectId string Required.
email string Required. Normalised to lowercase.
firstName string
lastName string
language enum en or pt-BR. Defaults to en.
tags string[]

201:

{ "id": "cnt_…", "subscribed": true }

subscribed reports whether a default list existed to subscribe to.

PATCH /api/contacts/{id}

Update a contact. Only the fields you pass change.

Scope: contacts:write

Field Type Notes
email string
firstName string | null
lastName string | null
language enum en or pt-BR.
tags string[] Replaces the whole list — include existing tags to keep them.
{ "success": true }

Tags are actions, not annotations. Adding one can fire a tag_added automation and start a drip sequence. Read the current tags first, and add to them rather than overwriting.

Passing no recognised field returns 400. An id in another project returns 404.

DELETE /api/contacts/{id}

Delete a contact and its subscriptions.

Scope: contacts:write

{ "success": true }

This is a hard delete, and it is not a substitute for unsubscribing: deleting a contact discards the record that they opted out, so a later sync can re-add them. To stop mailing someone, unsubscribe them.

PATCH /api/contacts/{id}/subscriptions/{listId}

Change one contact's status on one list — the granular consent operation.

Scope: contacts:write

Field Type Notes
status enum subscribed, unsubscribed or pending.
{ "success": true }

Consent is per list, so this is the endpoint to use when someone opts out of one thing and not everything.

POST /api/contacts/import

Bulk import of CSV-shaped rows onto one list. Backs the dashboard's import screen.

Scope: contacts:write

Field Type Notes
projectId string Required.
contacts object[] Required. Email plus optional name, language, tags.
listSlug string Target list. Defaults to the project's default list.
defaultLanguage string Language for rows that do not carry one. Falls back to pt-BR.

The target list is created if it does not exist.

{
  "success": true,
  "imported": 812,
  "updated": 104,
  "skipped": 3,
  "skippedUnsubscribed": 27,
  "total": 946,
  "listName": "Newsletter"
}

skippedUnsubscribed counts rows left alone because the contact had explicitly opted out. A spreadsheet upload does not reverse an opt-out.

Prefer POST /api/v1/contacts/sync for anything recurring: it is versioned, expresses desired state so retries are free, and protects hard bounces and complaints as well as opt-outs.

GET /api/contacts/export

Export contacts as CSV.

Scope: contacts:read

Parameter In Type Notes
projectId query string Required.
ids query string Comma-separated contact ids. Omit to export everything.

Returns text/csv as a file attachment, not JSON — one row per contact, with columns email, first_name, last_name, language, status, tags, source, created_at. Tags are pipe-separated inside their cell. A contact on no list exports with status no list.

curl "https://pharosbase.com/api/contacts/export?projectId=acme-app" \
  -H "Authorization: Bearer $PHAROS_API_KEY" -o contacts.csv