Quickstart
Ten minutes from nothing to a delivered email. Every step is an HTTP call, so this works the same from a terminal, a server, or an agent.
1. Get an API key
Keys come in two kinds: per project (phk_…) and per organization (pha_…).
An organization key can address any project it owns and is the only one that
can create projects, so it is the right credential to bootstrap with.
Create one in the dashboard under Settings → API keys. Pick the scopes it needs; the agent preset selects everything except the two send scopes, which is the right default for anything a model will hold. Set an expiry if the key is for a fixed piece of work — anything from a day to ten years.
Issuing a key is an owner/admin action. A key with send scopes can mail your entire list, so it carries the same authority as the sending credential itself.
The raw key is shown once, at creation. Pharos stores only its SHA-256 hash
and there is no way to recover it — put it straight into your secret store as
PHAROS_API_KEY.
Self-hosted deployments can also mint keys from the CLI:
pnpm org-key:create <org-slug> "Acme integration" --agent
pnpm api-key:create <project-slug> "Acme production" contacts:write,events:write,events:send
2. Create a project
Skip this if your project already exists.
curl -X POST https://pharosbase.com/api/v1/projects \
-H "Authorization: Bearer $PHAROS_ORG_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Acme App", "sesFromEmail": "hi@acme.dev" }'
The slug is derived from the name and is globally unique — a collision returns
409. Project creation requires an organization key with projects:write; a
project key is pinned to a project that already exists, so it has nothing to
create.
3. Sync a contact
POST /api/v1/contacts/sync upserts contacts and their subscription state on
one list. It is idempotent: sending the same desired state twice changes
nothing.
curl -X POST https://pharosbase.com/api/v1/contacts/sync \
-H "Authorization: Bearer $PHAROS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "acme-app",
"listSlug": "newsletter",
"source": "website_signup",
"contacts": [{ "email": "reader@example.com", "subscribed": true }]
}'
Requires contacts:write. Up to 5,000 contacts per call. Hard bounces and
complaints are protected — a sync cannot resubscribe them, and the response
reports how many it refused as protectedSuppressions.
Full contract: Contact sync.
4. Preview a transactional email
POST /api/v1/events composes an email from your payload. Preview does not
deliver anything — it returns the composed subject, HTML and text so you (or
a reviewer) can look at it first.
curl -X POST https://pharosbase.com/api/v1/events \
-H "Authorization: Bearer $PHAROS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "acme-app",
"eventType": "welcome.completed",
"idempotencyKey": "welcome:user-42",
"notificationCategory": "product",
"deliveryMode": "preview",
"recipient": { "email": "reader@example.com", "firstName": "Sam" },
"subject": "Welcome to Acme",
"html": "<p>Glad you are here, Sam.</p>"
}'
Requires events:write — nothing more. This is the call an agent can make.
5. Send it
Same request, deliveryMode: "send".
-d '{ …, "deliveryMode": "send" }'
This one requires events:send, a scope agent keys do not carry. The event is
persisted under its (project, idempotencyKey) pair and queued for delivery,
so repeating the request does not send twice.
Every delivery is recorded in the project's send log with its delivery and
open state — visible at /<team>/<project>/sends in the dashboard or through
GET /api/sends.
Full contract: Transactional events.
6. Point an agent at it
claude mcp add pharos -s user -t http https://pharosbase.com/api/mcp \
-H "Authorization: Bearer $PHAROS_AGENT_KEY"
Nothing to install. The agent gets the same API you just used, bounded by the same key — including the part where it cannot send. See MCP server.
Next
- Sending & deliverability — connect your own Resend or SES account and turn on delivery tracking. Do this before a real campaign.
- Unsubscribes & suppression — what every list-bound template needs, and what protects your sending reputation.
- Sending a campaign — the broadcast loop, end to end.
- Core concepts — what a list, template, campaign and send actually are.
- Troubleshooting — when something does not behave.
- API conventions — errors, rate limits, identifiers.
- Automations — turn that welcome email into a drip sequence.