Errors
Every error response has the same JSON body:
{
"error": "Unauthorized",
"message": "A valid API key is required.",
"code": "unauthenticated",
"requestId": "0196f0a3-1d2e-7f40-8a5b-6c7d8e9f0a1b"
}
erroris the HTTP status text.messageexplains the problem. Do not parse it; the wording can change.codeis a stable, machine-readable value, present on every error. Branch oncode, not onmessage.requestIdidentifies the request in Harmony's logs.
Some errors add fields that help you resolve them. The API reference lists them for each operation.
Every response, successful or not, also carries the request ID in the x-request-id header. Include it when you contact Harmony support about a failed request.
Codes
unauthenticated(401): the API key is missing, unknown, or revoked.forbidden(403): the API key's user has a role that does not allow the operation, such as a Viewer importing contacts or creating a webhook endpoint.not_found(404): the operation or the resource does not exist.internal(500): Harmony failed to handle the request.
Some operations return their own codes, listed below. Other errors use the status text as their code, such as bad_request for an invalid request body. New codes can be added, so handle codes you do not recognize by their HTTP status.
Status codes
400: the request is invalid. Fix it before sending it again.401,403,404: see the codes above.422onPUT /api/v1/contacts/upsert: the contact can't be saved, andcodesays why.invalid_phone: the phone can't be read as a number.missing_dedup_field: the contact has none of the fields your Account deduplicates by, which is phone unless your Account chose others.dedup_key_too_long: those field values are too long.409(conflict) on an external ID lookup: the lookup matched more than one contact. The body'scontactsfield lists them. To read one of them, use its HarmonyidwithGET /api/v1/contacts/{id}. The API can't list calls by Harmonyidyet, so give each contact a unique external ID in your source system.5xx: Harmony failed to handle the request. Retrying with a delay is safe for reads, and for imports withoutassignmentOptions.workflowId. Retrying an upsert is safe unless your Account deduplicates by external ID and you sentexternalId: null, which stores a duplicate, or you sentreEnrollandenforceCallNow, which calls the contact again. Otherwise the retry updates the contact the first attempt saved, and Harmony skips enrolling a contact the workflow already called. Don't retry an import that enrolls contacts automatically, since it can place two calls: first check what arrived withGET /api/v1/contacts?assignedWorkflowId=<workflowId>, then resend only the missing contacts.