# Automations

Reading drip sequences and their runs. Authoring and editing automations is a
dashboard act — see [Automations](/docs/pharos-automations) for the concepts,
step shape and worked examples.

The asymmetry is deliberate. A multi-step sequence that mails people on a
schedule is exactly the kind of thing that should be built where a human can
see the whole shape of it, so the API surface reads and the dashboard writes.

## `GET /api/automations`

Every automation in the project.

**Scope:** `automations:read`

| Parameter | In | Type | Notes |
| --- | --- | --- | --- |
| `projectId` | query | string | **Required.** |

```json
{
  "automations": [
    {
      "id": "aut_…",
      "name": "Welcome drip",
      "trigger": "tag_added",
      "triggerConfig": "{\"tag\":\"signed-up\"}",
      "enabled": true,
      "stepCount": 3,
      "runCount": 1204,
      "createdAt": "2026-06-12T08:00:00.000Z"
    }
  ]
}
```

`triggerConfig` is a JSON string, not an object — parse it before reading the
tag.

## `GET /api/automations/{id}`

One automation in full: its steps in order, run counts by status, and the ten
most recent runs with any errors.

**Scope:** `automations:read`

```json
{
  "automation": {
    "id": "aut_…",
    "name": "Welcome drip",
    "trigger": "tag_added",
    "enabled": true,
    "createdAt": "2026-06-12T08:00:00.000Z"
  },
  "steps": [
    { "id": "stp_…", "position": 1, "delayHours": 0, "subject": "Welcome" }
  ],
  "runs": {
    "counts": { "pending": 12, "completed": 1180, "failed": 3 },
    "recent": [
      {
        "id": "run_…",
        "contactEmail": "reader@example.com",
        "status": "completed",
        "scheduledFor": "2026-07-30T09:00:00.000Z",
        "executedAt": "2026-07-30T09:00:11.000Z",
        "error": null
      }
    ]
  }
}
```

`runs.counts` is keyed by status across the automation's whole history;
`runs.recent` is the last ten. A run carrying an `error` is the first place to
look when a sequence stops mid-way.

## Triggering an automation

Automations fire on tags, so your application starts one by tagging a contact
rather than by naming a sequence. That indirection is the point: your app knows
the user did something, and the sequence attached to it can change without
redeploying the app.

Two ways to add the tag.

### `PATCH /api/contacts/{id}` — the API-key path

**Scope:** `contacts:write`. Tags are replaced wholesale, so read the current
list first and add to it. See
[Contacts](/docs/api-contacts#patch-apicontactsid).

This is the right choice for anything new.

### `POST /api/contacts/event` — the legacy webhook path

Predates project API keys and authenticates differently: a shared
`WEBHOOK_SECRET` passed as a **query parameter**, not a bearer token.

```bash
curl -X POST "https://pharosbase.com/api/contacts/event?secret=$WEBHOOK_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "acme-app",
    "email": "reader@example.com",
    "tag": "signed-up",
    "action": "add"
  }'
```

| Field | Type | Notes |
| --- | --- | --- |
| `projectId` | string | **Required.** |
| `email` | string | **Required.** Must already exist as a contact. |
| `tag` | string | **Required.** |
| `action` | enum | `add` (default) or `remove`. |

Responds `{ "added": true }`, `{ "removed": true }`, or `{ "noop": true }` when
the tag was already in the requested state. An unknown email returns `404`;
the endpoint does not create contacts.

The secret is one value shared across every project, and a secret in a URL
tends to end up in access logs and browser history in a way a header does not.
Prefer the API-key path for new integrations, and keep this one server-side.

