Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| /api/v1/contacts | POST | Upsert a contact by email |
| /api/v1/contacts?email=… | GET | Fetch a contact with its tags |
| /api/v1/contacts/update | POST | Update an existing contact (404 if missing) |
| /api/v1/contacts/delete | POST | Delete a contact (idempotent) |
| /api/v1/contacts/tags/add | POST | Attach a tag (auto-created, idempotent) |
| /api/v1/contacts/tags/remove | POST | Detach a tag (idempotent) |
Upsert a contact
bash
curl -X POST https://www.ewyn.ai/api/v1/contacts \
-H "Authorization: Bearer $EWYN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"firstName": "Ada",
"tags": ["customer", "beta"]
}'Body fields:
| Field | Type | Required | Notes |
|---|---|---|---|
string | yes | Trimmed and lowercased server-side | |
| firstName | string | no | Overwrites the stored first name when provided |
| lastName | string | no | Overwrites the stored last name when provided |
| tags | string[] | no | Tag names to add — auto-created if missing; max 50 per call, 100 chars each |
Response:
json
{
"contact": {
"id": "ct_...",
"email": "user@example.com",
"firstName": "Ada",
"lastName": null,
"tags": [{ "id": "tag_...", "name": "customer", "color": "#..." }]
},
"created": true,
"suppressed": false
}Tag operations
bash
# attach
curl -X POST https://www.ewyn.ai/api/v1/contacts/tags/add \
-H "Authorization: Bearer $EWYN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "user@example.com", "tag": "vip" }'
# detach
curl -X POST https://www.ewyn.ai/api/v1/contacts/tags/remove \
-H "Authorization: Bearer $EWYN_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "user@example.com", "tag": "beta" }'tags/add returns the attached tag; tags/remove returns removed: false instead of failing when the tag wasn't attached. Both return 404 when the contact itself doesn't exist.
Semantics
- Merge, never erase — upserts only overwrite the fields you provide; tags merge additively
- Idempotent by design — deletes and tag-removals report
deleted/removed: falserather than erroring, so retries are always safe - Tag names are case-sensitive —
VIPandvipare different tags
Suppression is read-only here.
suppressed: true means the address unsubscribed, bounced, or complained. Contact data stays manageable, but send requests to that address are rejected with 422. There is no API to remove an address from the suppression list — that's deliberate.
Using TypeScript? The SDK contact methods wrap these endpoints one-to-one.