--- type: API title: Contacts API description: Contact CRUD plus idempotent upsert and delete keyed on email — the endpoints behind user synchronization. resource: https://euromailing.com/api/v1/contacts tags: [contacts, sync, api] timestamp: 2026-07-06T00:00:00Z --- # Contacts API Scope: `contacts:read` for reads, `contacts:write` for writes. Emails are normalized (trimmed, lowercased) server-side. ## Endpoints | Method & path | Purpose | |---------------|---------| | `GET /api/v1/contacts?q=&page=` | List/search (matches email, first/last name) | | `GET /api/v1/contacts/:id` | Fetch one | | `POST /api/v1/contacts` | Create (`{ "contact": { "email": …, "first_name": …, "last_name": …, "custom_fields": {} } }`) | | `PATCH /api/v1/contacts/:id` | Update | | `DELETE /api/v1/contacts/:id` | Delete by id | | `POST /api/v1/contacts/upsert` | **Idempotent create-or-update keyed on email** — preferred for syncing users. 201 when created, 200 when updated; response includes `"created": true/false`. | | `DELETE /api/v1/contacts/by_email` | Delete keyed on email (`{ "email": "…" }`); 404 when unknown. | ## Upsert example ```bash curl -X POST https://euromailing.com/api/v1/contacts/upsert \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{ "contact": { "email": "jan@example.com", "first_name": "Jan", "custom_fields": { "plan": "pro" } } }' ``` Typical sync semantics: on user create/update call `upsert`, on user deletion call `by_email` and treat a 404 as success. The `euromailing-rails` gem ships this as the `Euromailing::Syncable` concern — see [the Rails guide](../guides/rails-gem.md).