API referenceContacts

Add a handle to a contact

Attaches a new channel address to an existing contact and returns the created handle.

Endpoint
POST https://api.dairo.app/v1/contacts/{contactId}/handles

Attaches a new channel address to an existing contact and returns the created handle. channel and value are required for every channel except postal, value is capped at 500 characters (an email value must be an email address and is stored trimmed and lowercased), and isPrimary (default false) marks this as the default handle Dairo routes to for that channel. A postal handle instead requires a structured address in metadata (name, street, postalCode, city, and country required; line2 and state optional; country normalized to an ISO-3166 alpha-2 code or a country name), from which the one-line value is derived when omitted — see send a letter. Two conflicts return 409: a handle whose (channel, value) already exists in the project, and a second isPrimary handle for the same channel on this contact. An unknown channel returns 400 and an unknown contact returns 404. Requires the contacts:write scope.

Scope: contacts:write. Send the API key as a bearer token; Scopes lists what each one reaches.

Request

cURL
curl -X POST "https://api.dairo.app/v1/contacts/3fa85f64-5717-4562-b3fc-2c963f66afa6/handles" \
  -H "Authorization: Bearer $DAIRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "string",
    "value": "string"
  }'

Path parameters

NameTypeRequiredDescription
contactIdstringyesThe contact's ID.

Body

Required, application/json: ContactHandleInput.

FieldTypeRequiredDescription
channelstringyesemail / a2a / telegram / postal / …
valuestringyesThe channel address. For a postal handle this is the one-line formatted address (for display); the structured address goes in metadata. (max length 500)
labelstringno
isPrimarybooleanno(default false)
metadataobjectnoStructured extras. REQUIRED for a postal handle: { name, street, line2?, postalCode, city, state?, country } (see PostalAddress). name/street/postalCode/city/country are required; country is an ISO-3166 alpha-2 code or a country name.

Response

200 Success. Body: application/json, ContactHandleResponse.

FieldTypeRequiredDescription
contactHandleContactHandleyes
200
{
  "contactHandle": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "contactId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "projectId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "channel": "string",
    "value": "string",
    "label": "production",
    "isPrimary": true,
    "verified": true,
    "metadata": {},
    "createdAt": "2026-10-19T09:30:00Z",
    "updatedAt": "2026-10-19T09:30:00Z"
  }
}

Errors

Every error has the same body, {"error": {"type", "code", "message", "param"}}; branch on code. Errors lists every code.

StatusMeaning
400The request is malformed or a value is invalid (invalid_request, invalid_domain, idempotency_key_mismatch).
401The API key is missing or invalid.
403The key lacks the scope this endpoint needs (scope_missing) or may not do this (not_permitted).
404No such resource in this account (resource_not_found).
422The request is well-formed but a field failed validation (validation_failed).
429Rate limit, plan limit, budget or spend cap reached (rate_limited, plan_limit_reached, budget_exceeded, spend_cap_reached).
500An unexpected error; retry with backoff, then contact support (internal_error).

On this page