Pharos
Guides.md

Contact sync API

POST /api/v1/contacts/sync idempotently upserts contacts and their status on one list. It is intended for server-to-server integrations; API keys must never be exposed in browser code.

Authentication

Create a project-scoped key with the narrowest required scope:

pnpm api-key:create <project-slug> "Production contact sync" contacts:write

Add --expires-days <n> for a key that should self-revoke.

Send the resulting key once in the Authorization header:

Authorization: Bearer phk_...

The project in the request body must match the project that owns the key. An organization key (pha_...) also works and may address any project in its organization, by slug or id.

Key-authenticated requests are rate limited per key (fixed one-minute windows; see lib/rate-limit.ts). Over-budget requests get 429 with a Retry-After header — back off and retry rather than hammering.

Request

{
  "projectId": "bioloja",
  "listSlug": "newsletter",
  "source": "website_signup",
  "defaultLanguage": "pt-BR",
  "triggerAutomations": true,
  "contacts": [
    {
      "email": "reader@example.com",
      "subscribed": true,
      "subscribedAt": "2026-07-23T20:00:00.000Z",
      "unsubscribedAt": null
    }
  ]
}

The endpoint accepts between 1 and 5,000 contacts. Emails are normalized to lowercase. Repeating the same desired state is a no-op. Names, language, and tags are optional. Existing tags are preserved and incoming tags are merged.

Set triggerAutomations only for real-time user actions. Leave it false for imports and reconciliation jobs so historical contacts do not receive new-subscriber automations.

Hard-bounce and complaint suppressions are protected: a sync cannot accidentally resubscribe them. The response reports these as protectedSuppressions.

Response

{
  "success": true,
  "received": 1,
  "valid": 1,
  "created": 1,
  "updated": 0,
  "subscribed": 1,
  "unsubscribed": 0,
  "protectedSuppressions": 0
}

The legacy /api/contacts/sync endpoint remains available for internal clients using WEBHOOK_SECRET, but new integrations should use the versioned endpoint and project-scoped keys.