# Contacts and events

> Contacts with attributes and your app's events through the Couryo API (POST /v1/contacts and POST /v1/events), idempotent, that start and end sequences. No charge per contact.

Source: https://couryo.com/en/docs/contacts

A **contact** is a person who gets your [sequences](https://couryo.com/en/docs/sequences.md), with attributes to personalize (`name`, `plan`, `timezone`). An **event** is something that happened in your app (`user.signed_up`, `order.paid`): it puts the contact in the sequences that start with that name and ends the ones waiting for it as conversion.

**No charge per contact.** Having 100 or 100,000 contacts costs the same: only the emails sent count in the plan quota.

## Send an event

```bash title="cURL"
curl https://api.couryo.com/v1/events \
  -H "Authorization: Bearer $COURYO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-ana-1042" \
  -d '{
    "contact": { "email": "ana@example.com", "attributes": { "name": "Ana", "plan": "free" } },
    "name": "user.signed_up",
    "properties": { "source": "site" }
  }'
```

```json title="Response 201"
{
  "id": "cev_8k2m4q9w1x7z3abc",
  "contact_id": "con_3n5p7r9t1v2x4zab",
  "name": "user.signed_up",
  "enrolled": [{ "sequence_id": "seq_q8w2e4r6t8y0u1io", "enrollment_id": "enr_a1s3d5f7g9h2j4kl" }],
  "converted": [],
  "created_at": "2026-10-08T18:30:00Z"
}
```

- The contact is created or updated by email (attributes are merged into the existing ones).
- `enrolled` lists the active sequences this event started. A contact enters a sequence by event **once**; to put it back, use the manual entry.
- `converted` lists the enrollments closed because this is their **conversion event**.
- `properties` are available in the sequence emails as `{{event.source}}`.
- Event name: letters, numbers, `.`, `_`, `:` and `-`, up to 100 characters.
- Scope: a `send` (or `admin`) key. A `ck_test_` key puts the contact in test mode: the sequence emails are captured and never delivered.

### Idempotency

Send `Idempotency-Key` with every event. The same key with the same body returns the same response, with `Idempotent-Replayed: true`, and creates no other event or enrollment. A different body with the same key returns `409 idempotency_conflict`. Beyond the usual 24 hours, the event keeps the key: repeating it later still returns the same event.

## Contacts

```bash title="Create or update (upsert)"
curl https://api.couryo.com/v1/contacts \
  -H "Authorization: Bearer $COURYO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "ana@example.com", "attributes": { "plan": "pro", "city": null } }'
```

- Returns `201` when it creates and `200` when it updates.
- Attributes are plain values (text, number, true or false). `null` removes the attribute.
- The `timezone` attribute (for example `America/New_York`) is used by waits "until HH:mm in the contact's time zone".
- `subscribed` is `false` when the address is on the [suppression list](https://couryo.com/en/docs/suppressions.md) for marketing (unsubscribe, bounce, complaint or manual). Unsubscribes always win: the contact gets no marketing sequences, and a running sequence ends right away.

| Method and path | Scope | What it does |
|---|---|---|
| `POST /v1/contacts` | `send` | creates or updates by email |
| `GET /v1/contacts` | `read` | lists, newest first; `q` searches part of the email, `limit` and `starting_after` paginate |
| `GET /v1/contacts/{id}` | `read` | one contact, by `id` (`con_...`) or by email |
| `PATCH /v1/contacts/{id}` | `admin` | changes the email or the attributes |
| `DELETE /v1/contacts/{id}` | `admin` | deletes the contact and its events; it leaves the sequences (suppression stays) |
| `POST /v1/events` | `send` | records an event of the contact |

## Through MCP

The `track_event` (scope `send`, with `idempotency_key`) and `upsert_contact` (scope `send`) tools do the same through the [MCP server](https://couryo.com/en/docs/mcp.md), so your AI agent can record sign-ups and purchases.
