{
  "openapi": "3.0.3",
  "info": {
    "title": "Harmony Public API",
    "version": "1.0.0-beta.1"
  },
  "servers": [
    {
      "url": "https://api.harmony.ai",
      "description": "Production (US)"
    },
    {
      "url": "https://api.eu.harmony.ai",
      "description": "Production (EU)"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "calls"
    },
    {
      "name": "contacts"
    },
    {
      "name": "workflows"
    }
  ],
  "paths": {
    "/api/v1/calls/{id}/transcript": {
      "get": {
        "operationId": "getCallTranscript",
        "summary": "Get a call transcript",
        "description": "Returns utterances in conversation order for a Harmony call ID. The utterances array is empty when the report is not yet available or the call has no transcript.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "in": "path",
            "name": "id",
            "required": true,
            "description": "Harmony's call ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "utterances": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "speaker": {
                            "description": "Who spoke: assistant for the Voice agent, user for the contact.",
                            "nullable": true,
                            "type": "string"
                          },
                          "content": {
                            "description": "What the speaker said.",
                            "nullable": true,
                            "type": "string"
                          },
                          "timestamp": {
                            "description": "When the utterance was spoken.",
                            "nullable": true,
                            "type": "string"
                          }
                        },
                        "additionalProperties": false
                      },
                      "description": "The call's utterances in conversation order."
                    }
                  },
                  "required": [
                    "utterances"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          }
        },
        "tags": [
          "calls"
        ]
      }
    },
    "/api/v1/contacts": {
      "get": {
        "operationId": "listContacts",
        "summary": "List contacts",
        "description": "Returns one page of the Account's contacts, filtered by the query parameters.",
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "new",
                    "invalid",
                    "paused",
                    "unanswered",
                    "unreachable",
                    "connecting",
                    "answered",
                    "contact_verified",
                    "disqualified",
                    "qualified",
                    "converted",
                    "dont_call",
                    "dnc_blocked",
                    "not_assigned",
                    "assigned",
                    "not_answered",
                    "not_reached",
                    "reached",
                    "dnc"
                  ]
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "new",
                      "invalid",
                      "paused",
                      "unanswered",
                      "unreachable",
                      "connecting",
                      "answered",
                      "contact_verified",
                      "disqualified",
                      "qualified",
                      "converted",
                      "dont_call",
                      "dnc_blocked",
                      "not_assigned",
                      "assigned",
                      "not_answered",
                      "not_reached",
                      "reached",
                      "dnc"
                    ]
                  }
                }
              ]
            },
            "in": "query",
            "name": "status",
            "required": false,
            "description": "Only contacts in one of these statuses."
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "hubspot",
                    "salesforce",
                    "api",
                    "csv",
                    "manual",
                    "monday",
                    "oneai"
                  ]
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "hubspot",
                      "salesforce",
                      "api",
                      "csv",
                      "manual",
                      "monday",
                      "oneai"
                    ]
                  }
                }
              ]
            },
            "in": "query",
            "name": "sourceKind",
            "required": false,
            "description": "Only contacts from one of these sources."
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            "in": "query",
            "name": "addedSince",
            "required": false,
            "description": "Only contacts created at or after this time."
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            "in": "query",
            "name": "addedBefore",
            "required": false,
            "description": "Only contacts created before this time."
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "in": "query",
            "name": "assignedWorkflowId",
            "required": false,
            "description": "Only contacts last enrolled in this workflow, including those whose enrollment has ended."
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "in": "query",
            "name": "searchTerm",
            "required": false,
            "description": "Matches the start of a first or last name, or digits in the phone number. Each of the first ten words must match, and later words are ignored. Does not match external ID."
          },
          {
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "in": "query",
            "name": "limit",
            "required": false,
            "description": "Page size."
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "nextToken",
            "required": false,
            "description": "The nextToken from the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                            "description": "The contact's Harmony ID."
                          },
                          "accountId": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                            "description": "The Account that owns the contact."
                          },
                          "firstName": {
                            "nullable": true,
                            "description": "The contact's first name.",
                            "type": "string"
                          },
                          "lastName": {
                            "nullable": true,
                            "description": "The contact's last name.",
                            "type": "string"
                          },
                          "fullName": {
                            "nullable": true,
                            "description": "The contact's full name: the text sent as fullName, or firstName and lastName joined when none was sent.",
                            "type": "string"
                          },
                          "phone": {
                            "nullable": true,
                            "description": "The contact's phone number, normalized to E.164 on write.",
                            "type": "string"
                          },
                          "location": {
                            "nullable": true,
                            "description": "The country, or state or province, the contact is in, as an ISO 3166 code such as DE or US-CA. Null when none was given and the Account has no default location.",
                            "type": "string"
                          },
                          "locationDefaulted": {
                            "type": "boolean",
                            "description": "True when location is the Account's default location rather than one given for the contact. A defaulted location follows the Account's current default on each write."
                          },
                          "timezone": {
                            "nullable": true,
                            "description": "The contact's IANA time zone, such as America/New_York. When null, calls use the Voice agent's time zone.",
                            "type": "string"
                          },
                          "timezoneInferred": {
                            "type": "boolean",
                            "description": "True when timezone was inferred rather than given. An inferred timezone is inferred again on each write."
                          },
                          "language": {
                            "nullable": true,
                            "description": "The language the contact speaks, as a locale such as he-IL. An answered call updates it to the language the call ended in. Null when none is known, and calls then start in the Voice agent's default language.",
                            "type": "string"
                          },
                          "status": {
                            "nullable": true,
                            "description": "The contact's engagement status. Harmony sets not_assigned, assigned, not_answered, not_reached, reached, disqualified, qualified, converted, or dnc (do not call); a call result never moves it earlier in that list. paused suspends the contact's queued calls, and connecting restarts its call sequences, including finished ones. A paused contact can still be enrolled. Other values are legacy.",
                            "type": "string",
                            "enum": [
                              "new",
                              "invalid",
                              "paused",
                              "unanswered",
                              "unreachable",
                              "connecting",
                              "answered",
                              "contact_verified",
                              "disqualified",
                              "qualified",
                              "converted",
                              "dont_call",
                              "dnc_blocked",
                              "not_assigned",
                              "assigned",
                              "not_answered",
                              "not_reached",
                              "reached",
                              "dnc"
                            ]
                          },
                          "sourceKind": {
                            "type": "string",
                            "enum": [
                              "hubspot",
                              "salesforce",
                              "api",
                              "csv",
                              "manual",
                              "monday",
                              "oneai"
                            ],
                            "description": "Where the contact came from: a CRM (hubspot, salesforce, monday), the API, a CSV import, manual entry in Harmony, or oneai when Harmony created it from an inbound call."
                          },
                          "externalId": {
                            "nullable": true,
                            "description": "The contact's ID in its source. Set to the contact's Harmony ID when it was created without one.",
                            "type": "string"
                          },
                          "customFields": {
                            "nullable": true,
                            "description": "Account-defined values for the contact, keyed by contact field key.",
                            "type": "object",
                            "additionalProperties": {}
                          },
                          "priority": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Dial order of the contact's queued calls, from 0 to 1000. Higher is called first."
                          },
                          "assignedWorkflowId": {
                            "nullable": true,
                            "description": "The workflow the contact was last enrolled in. It stays set after that enrollment ends, and is null if the contact was never enrolled.",
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "callCount": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991,
                            "description": "Number of call outcomes recorded for the contact since it was last enrolled without reEnroll. An outcome is not counted when its call started at or before lastCallAt."
                          },
                          "maxAttempts": {
                            "nullable": true,
                            "description": "The Voice agent's configured attempt limit, copied at enrollment. Callbacks can exceed it.",
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          },
                          "lastCallAt": {
                            "nullable": true,
                            "description": "When the latest call with the contact started. Enrolling the contact in a workflow clears it, except on re-enrollment.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          },
                          "nextCallAt": {
                            "nullable": true,
                            "description": "The next attempt time reported for the call that started at lastCallAt. Null when that report had none, or after an enrollment starts or ends or a do-not-call block. Later reschedules are not reflected.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          },
                          "createdAt": {
                            "description": "When the contact was added, or the createdAt sent by the upsert that created it.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          },
                          "updatedAt": {
                            "description": "When the contact was last changed.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          }
                        },
                        "required": [
                          "id",
                          "accountId",
                          "firstName",
                          "lastName",
                          "fullName",
                          "phone",
                          "location",
                          "locationDefaulted",
                          "timezone",
                          "timezoneInferred",
                          "language",
                          "status",
                          "sourceKind",
                          "externalId",
                          "customFields",
                          "priority",
                          "assignedWorkflowId",
                          "callCount",
                          "maxAttempts",
                          "lastCallAt",
                          "nextCallAt",
                          "createdAt",
                          "updatedAt"
                        ],
                        "additionalProperties": false
                      },
                      "description": "One page of contacts, newest first."
                    },
                    "nextToken": {
                      "description": "Pass as nextToken to fetch the next page. Absent on the last page.",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          }
        },
        "tags": [
          "contacts"
        ]
      }
    },
    "/api/v1/contacts/bulk": {
      "post": {
        "operationId": "bulkUpsertContacts",
        "summary": "Import contacts",
        "description": "Queues up to 1,000 contacts for creation or update, optionally enrolling them into a workflow. Contacts that cannot be saved are not queued and are returned in rejected with their reason.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "assignmentOptions": {
                    "type": "object",
                    "properties": {
                      "workflowId": {
                        "type": "string",
                        "format": "uuid",
                        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                      },
                      "reEnroll": {
                        "type": "boolean",
                        "description": "Enrolls the contact into the workflow again even if that workflow already ran for it. Must be sent together with enforceCallNow."
                      },
                      "enforceCallNow": {
                        "type": "boolean",
                        "description": "Dials immediately instead of waiting for the account's working-hours window. Must be sent together with reEnroll."
                      }
                    }
                  },
                  "contacts": {
                    "minItems": 1,
                    "maxItems": 1000,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "firstName": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 128
                        },
                        "lastName": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 128
                        },
                        "fullName": {
                          "description": "Sent alone, it is split into firstName and lastName, dropping middle names, titles, and suffixes. Sent with only one of them, it fills the other. Without it, the full name is built from firstName and lastName.",
                          "nullable": true,
                          "type": "string",
                          "maxLength": 256
                        },
                        "phone": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 64,
                          "pattern": "^(\\+|00)?[\\d\\s().-]*\\d[\\d\\s().-]*$"
                        },
                        "location": {
                          "description": "The country, or state or province, the contact is in: an ISO 3166 code such as DE or US-CA, or a name such as Germany or California. A value that cannot be read is stored as empty. Empty takes the account's default location.",
                          "nullable": true,
                          "type": "string",
                          "maxLength": 64
                        },
                        "timezone": {
                          "nullable": true,
                          "type": "string",
                          "maxLength": 64
                        },
                        "language": {
                          "description": "The language the contact speaks: one of en-US, en-GB, he-IL, fr-FR, de-DE, es-ES, es-MX, es-US, or es-419, or a value read as one of them, such as he, Hebrew, or es-AR. A value that cannot be read is ignored. An upsert that matches an existing contact sets it only when the contact has none.",
                          "nullable": true,
                          "type": "string",
                          "maxLength": 64
                        },
                        "status": {
                          "description": "Replaces the stored status when the upsert matches an existing contact, which keeps its status when this is omitted or null. A new contact starts as not_assigned when this is omitted, and with no status when it is null. A blacklisted phone is stored as dnc whatever status is sent.",
                          "nullable": true,
                          "type": "string",
                          "enum": [
                            "new",
                            "invalid",
                            "paused",
                            "unanswered",
                            "unreachable",
                            "connecting",
                            "answered",
                            "contact_verified",
                            "disqualified",
                            "qualified",
                            "converted",
                            "dont_call",
                            "dnc_blocked",
                            "not_assigned",
                            "assigned",
                            "not_answered",
                            "not_reached",
                            "reached",
                            "dnc"
                          ]
                        },
                        "sourceKind": {
                          "type": "string",
                          "enum": [
                            "hubspot",
                            "salesforce",
                            "api",
                            "csv",
                            "manual",
                            "monday",
                            "oneai"
                          ]
                        },
                        "externalId": {
                          "nullable": true,
                          "description": "When null, defaults to the contact's ID if the upsert creates a contact, and an existing contact keeps its stored external ID.",
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 256
                        },
                        "customFields": {
                          "description": "Merged into the contact's stored custom fields when the upsert matches an existing contact. Keys not sent keep their value, and a key sent as null is removed.",
                          "nullable": true,
                          "type": "object",
                          "additionalProperties": {}
                        },
                        "priority": {
                          "description": "Higher-priority contacts are called first. New contacts default to 0.",
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 1000
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time",
                          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                        }
                      },
                      "required": [
                        "sourceKind",
                        "externalId"
                      ]
                    }
                  }
                },
                "required": [
                  "contacts"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accepted": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "rejected": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "reason": {
                            "type": "string",
                            "enum": [
                              "missing-dedup-field",
                              "dedup-key-too-long",
                              "invalid-phone"
                            ]
                          },
                          "index": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 9007199254740991
                          }
                        },
                        "required": [
                          "reason",
                          "index"
                        ],
                        "additionalProperties": false
                      },
                      "description": "Contacts that were not queued, each with its zero-based position in contacts."
                    }
                  },
                  "required": [
                    "accepted",
                    "rejected"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          }
        },
        "tags": [
          "contacts"
        ]
      }
    },
    "/api/v1/contacts/bulk-update": {
      "post": {
        "operationId": "bulkUpdateContacts",
        "summary": "Update contacts by filter",
        "description": "Applies the same changes to every contact that matches all filter conditions, such as setting a status on the contacts with a given custom field value. Contacts whose change would conflict with another contact's dedup key are skipped.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filter": {
                    "type": "object",
                    "properties": {
                      "firstName": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 128
                      },
                      "lastName": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 128
                      },
                      "phone": {
                        "type": "string",
                        "format": "e164",
                        "pattern": "^\\+[1-9]\\d{6,14}$"
                      },
                      "sourceKind": {
                        "type": "string",
                        "enum": [
                          "hubspot",
                          "salesforce",
                          "api",
                          "csv",
                          "manual",
                          "monday",
                          "oneai"
                        ],
                        "description": "Only contacts from this source. Must be sent together with externalId."
                      },
                      "externalId": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 256,
                        "description": "Only contacts with this external ID. Must be sent together with sourceKind."
                      },
                      "customFields": {
                        "description": "Only contacts whose custom field `key` equals `value`, for every entry.",
                        "minItems": 1,
                        "maxItems": 5,
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "key": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 128
                            },
                            "value": {
                              "type": "string",
                              "maxLength": 1024
                            }
                          },
                          "required": [
                            "key",
                            "value"
                          ]
                        }
                      }
                    }
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "firstName": {
                        "nullable": true,
                        "type": "string",
                        "maxLength": 128
                      },
                      "lastName": {
                        "nullable": true,
                        "type": "string",
                        "maxLength": 128
                      },
                      "fullName": {
                        "description": "Sent alone, it is split into firstName and lastName, dropping middle names, titles, and suffixes. Sent with only one of them, it fills the other. Without it, the full name is built from firstName and lastName.",
                        "nullable": true,
                        "type": "string",
                        "maxLength": 256
                      },
                      "phone": {
                        "nullable": true,
                        "type": "string",
                        "maxLength": 64,
                        "pattern": "^(\\+|00)?[\\d\\s().-]*\\d[\\d\\s().-]*$"
                      },
                      "location": {
                        "description": "The country, or state or province, the contact is in: an ISO 3166 code such as DE or US-CA, or a name such as Germany or California. A value that cannot be read is stored as empty. Empty takes the account's default location.",
                        "nullable": true,
                        "type": "string",
                        "maxLength": 64
                      },
                      "timezone": {
                        "nullable": true,
                        "type": "string",
                        "maxLength": 64
                      },
                      "language": {
                        "description": "The language the contact speaks: one of en-US, en-GB, he-IL, fr-FR, de-DE, es-ES, es-MX, es-US, or es-419, or a value read as one of them, such as he, Hebrew, or es-AR. A value that cannot be read is ignored. An upsert that matches an existing contact sets it only when the contact has none.",
                        "nullable": true,
                        "type": "string",
                        "maxLength": 64
                      },
                      "status": {
                        "nullable": true,
                        "type": "string",
                        "enum": [
                          "new",
                          "invalid",
                          "paused",
                          "unanswered",
                          "unreachable",
                          "connecting",
                          "answered",
                          "contact_verified",
                          "disqualified",
                          "qualified",
                          "converted",
                          "dont_call",
                          "dnc_blocked",
                          "not_assigned",
                          "assigned",
                          "not_answered",
                          "not_reached",
                          "reached",
                          "dnc"
                        ]
                      },
                      "sourceKind": {
                        "type": "string",
                        "enum": [
                          "hubspot",
                          "salesforce",
                          "api",
                          "csv",
                          "manual",
                          "monday",
                          "oneai"
                        ]
                      },
                      "externalId": {
                        "nullable": true,
                        "type": "string",
                        "maxLength": 256
                      },
                      "customFields": {
                        "description": "Merged into the contact's stored custom fields. Keys not sent keep their value, a key sent as null is removed, and null removes every custom field.",
                        "nullable": true,
                        "type": "object",
                        "additionalProperties": {}
                      },
                      "priority": {
                        "description": "Higher-priority contacts are called first. New contacts default to 0.",
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 1000
                      }
                    }
                  }
                },
                "required": [
                  "filter",
                  "data"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          }
        },
        "tags": [
          "contacts"
        ]
      }
    },
    "/api/v1/contacts/csv": {
      "get": {
        "operationId": "listContactsCsv",
        "summary": "List contacts as CSV",
        "description": "Returns one page of the Account's contacts as CSV, filtered by the query parameters. The next page's token is in the `x-next-token` response header.",
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "new",
                    "invalid",
                    "paused",
                    "unanswered",
                    "unreachable",
                    "connecting",
                    "answered",
                    "contact_verified",
                    "disqualified",
                    "qualified",
                    "converted",
                    "dont_call",
                    "dnc_blocked",
                    "not_assigned",
                    "assigned",
                    "not_answered",
                    "not_reached",
                    "reached",
                    "dnc"
                  ]
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "new",
                      "invalid",
                      "paused",
                      "unanswered",
                      "unreachable",
                      "connecting",
                      "answered",
                      "contact_verified",
                      "disqualified",
                      "qualified",
                      "converted",
                      "dont_call",
                      "dnc_blocked",
                      "not_assigned",
                      "assigned",
                      "not_answered",
                      "not_reached",
                      "reached",
                      "dnc"
                    ]
                  }
                }
              ]
            },
            "in": "query",
            "name": "status",
            "required": false,
            "description": "Only contacts in one of these statuses."
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "hubspot",
                    "salesforce",
                    "api",
                    "csv",
                    "manual",
                    "monday",
                    "oneai"
                  ]
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "hubspot",
                      "salesforce",
                      "api",
                      "csv",
                      "manual",
                      "monday",
                      "oneai"
                    ]
                  }
                }
              ]
            },
            "in": "query",
            "name": "sourceKind",
            "required": false,
            "description": "Only contacts from one of these sources."
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            "in": "query",
            "name": "addedSince",
            "required": false,
            "description": "Only contacts created at or after this time."
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            "in": "query",
            "name": "addedBefore",
            "required": false,
            "description": "Only contacts created before this time."
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "in": "query",
            "name": "assignedWorkflowId",
            "required": false,
            "description": "Only contacts last enrolled in this workflow, including those whose enrollment has ended."
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "in": "query",
            "name": "searchTerm",
            "required": false,
            "description": "Matches the start of a first or last name, or digits in the phone number. Each of the first ten words must match, and later words are ignored. Does not match external ID."
          },
          {
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "in": "query",
            "name": "limit",
            "required": false,
            "description": "Page size."
          },
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "nextToken",
            "required": false,
            "description": "The nextToken from the previous page."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "headers": {
              "x-next-token": {
                "schema": {
                  "type": "string"
                },
                "description": "Pass as nextToken to fetch the next page. Absent on the last page."
              }
            },
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "description": "A header row, then one row per contact. `customFields` is a JSON string."
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          }
        },
        "tags": [
          "contacts"
        ]
      }
    },
    "/api/v1/contacts/source/{sourceKind}/external-id/{externalId}": {
      "get": {
        "operationId": "getContactByExternalId",
        "summary": "Get a contact by external ID",
        "description": "Returns the contact with this external ID from this source. Returns 409 with the matching contacts when more than one has it.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "hubspot",
                "salesforce",
                "api",
                "csv",
                "manual",
                "monday",
                "oneai"
              ]
            },
            "in": "path",
            "name": "sourceKind",
            "required": true,
            "description": "The source the contact came from."
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            },
            "in": "path",
            "name": "externalId",
            "required": true,
            "description": "The contact's ID in that source. Matching is case- and accent-sensitive and ignores trailing spaces."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The contact's Harmony ID."
                    },
                    "accountId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The Account that owns the contact."
                    },
                    "firstName": {
                      "nullable": true,
                      "description": "The contact's first name.",
                      "type": "string"
                    },
                    "lastName": {
                      "nullable": true,
                      "description": "The contact's last name.",
                      "type": "string"
                    },
                    "fullName": {
                      "nullable": true,
                      "description": "The contact's full name: the text sent as fullName, or firstName and lastName joined when none was sent.",
                      "type": "string"
                    },
                    "phone": {
                      "nullable": true,
                      "description": "The contact's phone number, normalized to E.164 on write.",
                      "type": "string"
                    },
                    "location": {
                      "nullable": true,
                      "description": "The country, or state or province, the contact is in, as an ISO 3166 code such as DE or US-CA. Null when none was given and the Account has no default location.",
                      "type": "string"
                    },
                    "locationDefaulted": {
                      "type": "boolean",
                      "description": "True when location is the Account's default location rather than one given for the contact. A defaulted location follows the Account's current default on each write."
                    },
                    "timezone": {
                      "nullable": true,
                      "description": "The contact's IANA time zone, such as America/New_York. When null, calls use the Voice agent's time zone.",
                      "type": "string"
                    },
                    "timezoneInferred": {
                      "type": "boolean",
                      "description": "True when timezone was inferred rather than given. An inferred timezone is inferred again on each write."
                    },
                    "language": {
                      "nullable": true,
                      "description": "The language the contact speaks, as a locale such as he-IL. An answered call updates it to the language the call ended in. Null when none is known, and calls then start in the Voice agent's default language.",
                      "type": "string"
                    },
                    "status": {
                      "nullable": true,
                      "description": "The contact's engagement status. Harmony sets not_assigned, assigned, not_answered, not_reached, reached, disqualified, qualified, converted, or dnc (do not call); a call result never moves it earlier in that list. paused suspends the contact's queued calls, and connecting restarts its call sequences, including finished ones. A paused contact can still be enrolled. Other values are legacy.",
                      "type": "string",
                      "enum": [
                        "new",
                        "invalid",
                        "paused",
                        "unanswered",
                        "unreachable",
                        "connecting",
                        "answered",
                        "contact_verified",
                        "disqualified",
                        "qualified",
                        "converted",
                        "dont_call",
                        "dnc_blocked",
                        "not_assigned",
                        "assigned",
                        "not_answered",
                        "not_reached",
                        "reached",
                        "dnc"
                      ]
                    },
                    "sourceKind": {
                      "type": "string",
                      "enum": [
                        "hubspot",
                        "salesforce",
                        "api",
                        "csv",
                        "manual",
                        "monday",
                        "oneai"
                      ],
                      "description": "Where the contact came from: a CRM (hubspot, salesforce, monday), the API, a CSV import, manual entry in Harmony, or oneai when Harmony created it from an inbound call."
                    },
                    "externalId": {
                      "nullable": true,
                      "description": "The contact's ID in its source. Set to the contact's Harmony ID when it was created without one.",
                      "type": "string"
                    },
                    "customFields": {
                      "nullable": true,
                      "description": "Account-defined values for the contact, keyed by contact field key.",
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "priority": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Dial order of the contact's queued calls, from 0 to 1000. Higher is called first."
                    },
                    "assignedWorkflowId": {
                      "nullable": true,
                      "description": "The workflow the contact was last enrolled in. It stays set after that enrollment ends, and is null if the contact was never enrolled.",
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "callCount": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Number of call outcomes recorded for the contact since it was last enrolled without reEnroll. An outcome is not counted when its call started at or before lastCallAt."
                    },
                    "maxAttempts": {
                      "nullable": true,
                      "description": "The Voice agent's configured attempt limit, copied at enrollment. Callbacks can exceed it.",
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "lastCallAt": {
                      "nullable": true,
                      "description": "When the latest call with the contact started. Enrolling the contact in a workflow clears it, except on re-enrollment.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "nextCallAt": {
                      "nullable": true,
                      "description": "The next attempt time reported for the call that started at lastCallAt. Null when that report had none, or after an enrollment starts or ends or a do-not-call block. Later reschedules are not reflected.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "createdAt": {
                      "description": "When the contact was added, or the createdAt sent by the upsert that created it.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "updatedAt": {
                      "description": "When the contact was last changed.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    }
                  },
                  "required": [
                    "id",
                    "accountId",
                    "firstName",
                    "lastName",
                    "fullName",
                    "phone",
                    "location",
                    "locationDefaulted",
                    "timezone",
                    "timezoneInferred",
                    "language",
                    "status",
                    "sourceKind",
                    "externalId",
                    "customFields",
                    "priority",
                    "assignedWorkflowId",
                    "callCount",
                    "maxAttempts",
                    "lastCallAt",
                    "nextCallAt",
                    "createdAt",
                    "updatedAt"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "409": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PublicError"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "contacts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                                "description": "The contact's Harmony ID."
                              },
                              "accountId": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                                "description": "The Account that owns the contact."
                              },
                              "firstName": {
                                "nullable": true,
                                "description": "The contact's first name.",
                                "type": "string"
                              },
                              "lastName": {
                                "nullable": true,
                                "description": "The contact's last name.",
                                "type": "string"
                              },
                              "fullName": {
                                "nullable": true,
                                "description": "The contact's full name: the text sent as fullName, or firstName and lastName joined when none was sent.",
                                "type": "string"
                              },
                              "phone": {
                                "nullable": true,
                                "description": "The contact's phone number, normalized to E.164 on write.",
                                "type": "string"
                              },
                              "location": {
                                "nullable": true,
                                "description": "The country, or state or province, the contact is in, as an ISO 3166 code such as DE or US-CA. Null when none was given and the Account has no default location.",
                                "type": "string"
                              },
                              "locationDefaulted": {
                                "type": "boolean",
                                "description": "True when location is the Account's default location rather than one given for the contact. A defaulted location follows the Account's current default on each write."
                              },
                              "timezone": {
                                "nullable": true,
                                "description": "The contact's IANA time zone, such as America/New_York. When null, calls use the Voice agent's time zone.",
                                "type": "string"
                              },
                              "timezoneInferred": {
                                "type": "boolean",
                                "description": "True when timezone was inferred rather than given. An inferred timezone is inferred again on each write."
                              },
                              "language": {
                                "nullable": true,
                                "description": "The language the contact speaks, as a locale such as he-IL. An answered call updates it to the language the call ended in. Null when none is known, and calls then start in the Voice agent's default language.",
                                "type": "string"
                              },
                              "status": {
                                "nullable": true,
                                "description": "The contact's engagement status. Harmony sets not_assigned, assigned, not_answered, not_reached, reached, disqualified, qualified, converted, or dnc (do not call); a call result never moves it earlier in that list. paused suspends the contact's queued calls, and connecting restarts its call sequences, including finished ones. A paused contact can still be enrolled. Other values are legacy.",
                                "type": "string",
                                "enum": [
                                  "new",
                                  "invalid",
                                  "paused",
                                  "unanswered",
                                  "unreachable",
                                  "connecting",
                                  "answered",
                                  "contact_verified",
                                  "disqualified",
                                  "qualified",
                                  "converted",
                                  "dont_call",
                                  "dnc_blocked",
                                  "not_assigned",
                                  "assigned",
                                  "not_answered",
                                  "not_reached",
                                  "reached",
                                  "dnc"
                                ]
                              },
                              "sourceKind": {
                                "type": "string",
                                "enum": [
                                  "hubspot",
                                  "salesforce",
                                  "api",
                                  "csv",
                                  "manual",
                                  "monday",
                                  "oneai"
                                ],
                                "description": "Where the contact came from: a CRM (hubspot, salesforce, monday), the API, a CSV import, manual entry in Harmony, or oneai when Harmony created it from an inbound call."
                              },
                              "externalId": {
                                "nullable": true,
                                "description": "The contact's ID in its source. Set to the contact's Harmony ID when it was created without one.",
                                "type": "string"
                              },
                              "customFields": {
                                "nullable": true,
                                "description": "Account-defined values for the contact, keyed by contact field key.",
                                "type": "object",
                                "additionalProperties": {}
                              },
                              "priority": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Dial order of the contact's queued calls, from 0 to 1000. Higher is called first."
                              },
                              "assignedWorkflowId": {
                                "nullable": true,
                                "description": "The workflow the contact was last enrolled in. It stays set after that enrollment ends, and is null if the contact was never enrolled.",
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              "callCount": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Number of call outcomes recorded for the contact since it was last enrolled without reEnroll. An outcome is not counted when its call started at or before lastCallAt."
                              },
                              "maxAttempts": {
                                "nullable": true,
                                "description": "The Voice agent's configured attempt limit, copied at enrollment. Callbacks can exceed it.",
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991
                              },
                              "lastCallAt": {
                                "nullable": true,
                                "description": "When the latest call with the contact started. Enrolling the contact in a workflow clears it, except on re-enrollment.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              },
                              "nextCallAt": {
                                "nullable": true,
                                "description": "The next attempt time reported for the call that started at lastCallAt. Null when that report had none, or after an enrollment starts or ends or a do-not-call block. Later reschedules are not reflected.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              },
                              "createdAt": {
                                "description": "When the contact was added, or the createdAt sent by the upsert that created it.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              },
                              "updatedAt": {
                                "description": "When the contact was last changed.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              }
                            },
                            "required": [
                              "id",
                              "accountId",
                              "firstName",
                              "lastName",
                              "fullName",
                              "phone",
                              "location",
                              "locationDefaulted",
                              "timezone",
                              "timezoneInferred",
                              "language",
                              "status",
                              "sourceKind",
                              "externalId",
                              "customFields",
                              "priority",
                              "assignedWorkflowId",
                              "callCount",
                              "maxAttempts",
                              "lastCallAt",
                              "nextCallAt",
                              "createdAt",
                              "updatedAt"
                            ],
                            "additionalProperties": false
                          },
                          "description": "The contacts from this source with this external ID, up to 100."
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "tags": [
          "contacts"
        ]
      },
      "patch": {
        "operationId": "updateContactByExternalId",
        "summary": "Update a contact by external ID",
        "description": "Changes only the fields sent on the contact with this external ID from this source, and returns the contact. Returns 409 with the matching contacts, and updates none of them, when more than one has it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 128
                  },
                  "lastName": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 128
                  },
                  "fullName": {
                    "description": "Sent alone, it is split into firstName and lastName, dropping middle names, titles, and suffixes. Sent with only one of them, it fills the other. Without it, the full name is built from firstName and lastName.",
                    "nullable": true,
                    "type": "string",
                    "maxLength": 256
                  },
                  "phone": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 64,
                    "pattern": "^(\\+|00)?[\\d\\s().-]*\\d[\\d\\s().-]*$"
                  },
                  "location": {
                    "description": "The country, or state or province, the contact is in: an ISO 3166 code such as DE or US-CA, or a name such as Germany or California. A value that cannot be read is stored as empty. Empty takes the account's default location.",
                    "nullable": true,
                    "type": "string",
                    "maxLength": 64
                  },
                  "timezone": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 64
                  },
                  "status": {
                    "nullable": true,
                    "type": "string",
                    "enum": [
                      "new",
                      "invalid",
                      "paused",
                      "unanswered",
                      "unreachable",
                      "connecting",
                      "answered",
                      "contact_verified",
                      "disqualified",
                      "qualified",
                      "converted",
                      "dont_call",
                      "dnc_blocked",
                      "not_assigned",
                      "assigned",
                      "not_answered",
                      "not_reached",
                      "reached",
                      "dnc"
                    ]
                  },
                  "sourceKind": {
                    "type": "string",
                    "enum": [
                      "hubspot",
                      "salesforce",
                      "api",
                      "csv",
                      "manual",
                      "monday",
                      "oneai"
                    ]
                  },
                  "externalId": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 256
                  },
                  "customFields": {
                    "description": "Merged into the contact's stored custom fields. Keys not sent keep their value, a key sent as null is removed, and null removes every custom field.",
                    "nullable": true,
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "priority": {
                    "description": "Higher-priority contacts are called first. New contacts default to 0.",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "hubspot",
                "salesforce",
                "api",
                "csv",
                "manual",
                "monday",
                "oneai"
              ]
            },
            "in": "path",
            "name": "sourceKind",
            "required": true,
            "description": "The source the contact came from."
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            },
            "in": "path",
            "name": "externalId",
            "required": true,
            "description": "The contact's ID in that source. Matching is case- and accent-sensitive and ignores trailing spaces."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The contact's Harmony ID."
                    },
                    "accountId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The Account that owns the contact."
                    },
                    "firstName": {
                      "nullable": true,
                      "description": "The contact's first name.",
                      "type": "string"
                    },
                    "lastName": {
                      "nullable": true,
                      "description": "The contact's last name.",
                      "type": "string"
                    },
                    "fullName": {
                      "nullable": true,
                      "description": "The contact's full name: the text sent as fullName, or firstName and lastName joined when none was sent.",
                      "type": "string"
                    },
                    "phone": {
                      "nullable": true,
                      "description": "The contact's phone number, normalized to E.164 on write.",
                      "type": "string"
                    },
                    "location": {
                      "nullable": true,
                      "description": "The country, or state or province, the contact is in, as an ISO 3166 code such as DE or US-CA. Null when none was given and the Account has no default location.",
                      "type": "string"
                    },
                    "locationDefaulted": {
                      "type": "boolean",
                      "description": "True when location is the Account's default location rather than one given for the contact. A defaulted location follows the Account's current default on each write."
                    },
                    "timezone": {
                      "nullable": true,
                      "description": "The contact's IANA time zone, such as America/New_York. When null, calls use the Voice agent's time zone.",
                      "type": "string"
                    },
                    "timezoneInferred": {
                      "type": "boolean",
                      "description": "True when timezone was inferred rather than given. An inferred timezone is inferred again on each write."
                    },
                    "status": {
                      "nullable": true,
                      "description": "The contact's engagement status. Harmony sets not_assigned, assigned, not_answered, not_reached, reached, disqualified, qualified, converted, or dnc (do not call); a call result never moves it earlier in that list. paused suspends the contact's queued calls, and connecting restarts its call sequences, including finished ones. A paused contact can still be enrolled. Other values are legacy.",
                      "type": "string",
                      "enum": [
                        "new",
                        "invalid",
                        "paused",
                        "unanswered",
                        "unreachable",
                        "connecting",
                        "answered",
                        "contact_verified",
                        "disqualified",
                        "qualified",
                        "converted",
                        "dont_call",
                        "dnc_blocked",
                        "not_assigned",
                        "assigned",
                        "not_answered",
                        "not_reached",
                        "reached",
                        "dnc"
                      ]
                    },
                    "sourceKind": {
                      "type": "string",
                      "enum": [
                        "hubspot",
                        "salesforce",
                        "api",
                        "csv",
                        "manual",
                        "monday",
                        "oneai"
                      ],
                      "description": "Where the contact came from: a CRM (hubspot, salesforce, monday), the API, a CSV import, manual entry in Harmony, or oneai when Harmony created it from an inbound call."
                    },
                    "externalId": {
                      "nullable": true,
                      "description": "The contact's ID in its source. Set to the contact's Harmony ID when it was created without one.",
                      "type": "string"
                    },
                    "customFields": {
                      "nullable": true,
                      "description": "Account-defined values for the contact, keyed by contact field key.",
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "priority": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Dial order of the contact's queued calls, from 0 to 1000. Higher is called first."
                    },
                    "assignedWorkflowId": {
                      "nullable": true,
                      "description": "The workflow the contact was last enrolled in. It stays set after that enrollment ends, and is null if the contact was never enrolled.",
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "callCount": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Number of call outcomes recorded for the contact since it was last enrolled without reEnroll. An outcome is not counted when its call started at or before lastCallAt."
                    },
                    "maxAttempts": {
                      "nullable": true,
                      "description": "The Voice agent's configured attempt limit, copied at enrollment. Callbacks can exceed it.",
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "lastCallAt": {
                      "nullable": true,
                      "description": "When the latest call with the contact started. Enrolling the contact in a workflow clears it, except on re-enrollment.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "nextCallAt": {
                      "nullable": true,
                      "description": "The next attempt time reported for the call that started at lastCallAt. Null when that report had none, or after an enrollment starts or ends or a do-not-call block. Later reschedules are not reflected.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "createdAt": {
                      "description": "When the contact was added, or the createdAt sent by the upsert that created it.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "updatedAt": {
                      "description": "When the contact was last changed.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    }
                  },
                  "required": [
                    "id",
                    "accountId",
                    "firstName",
                    "lastName",
                    "fullName",
                    "phone",
                    "location",
                    "locationDefaulted",
                    "timezone",
                    "timezoneInferred",
                    "status",
                    "sourceKind",
                    "externalId",
                    "customFields",
                    "priority",
                    "assignedWorkflowId",
                    "callCount",
                    "maxAttempts",
                    "lastCallAt",
                    "nextCallAt",
                    "createdAt",
                    "updatedAt"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "409": {
            "description": "Several contacts from this source have this external ID, or the change would give the contact the same dedup field values as another contact.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PublicError"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "contacts": {
                          "description": "Sent when several contacts from this source have this external ID: those contacts, up to 100. None of them is updated.",
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                                "description": "The contact's Harmony ID."
                              },
                              "accountId": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                                "description": "The Account that owns the contact."
                              },
                              "firstName": {
                                "nullable": true,
                                "description": "The contact's first name.",
                                "type": "string"
                              },
                              "lastName": {
                                "nullable": true,
                                "description": "The contact's last name.",
                                "type": "string"
                              },
                              "fullName": {
                                "nullable": true,
                                "description": "The contact's full name: the text sent as fullName, or firstName and lastName joined when none was sent.",
                                "type": "string"
                              },
                              "phone": {
                                "nullable": true,
                                "description": "The contact's phone number, normalized to E.164 on write.",
                                "type": "string"
                              },
                              "location": {
                                "nullable": true,
                                "description": "The country, or state or province, the contact is in, as an ISO 3166 code such as DE or US-CA. Null when none was given and the Account has no default location.",
                                "type": "string"
                              },
                              "locationDefaulted": {
                                "type": "boolean",
                                "description": "True when location is the Account's default location rather than one given for the contact. A defaulted location follows the Account's current default on each write."
                              },
                              "timezone": {
                                "nullable": true,
                                "description": "The contact's IANA time zone, such as America/New_York. When null, calls use the Voice agent's time zone.",
                                "type": "string"
                              },
                              "timezoneInferred": {
                                "type": "boolean",
                                "description": "True when timezone was inferred rather than given. An inferred timezone is inferred again on each write."
                              },
                              "status": {
                                "nullable": true,
                                "description": "The contact's engagement status. Harmony sets not_assigned, assigned, not_answered, not_reached, reached, disqualified, qualified, converted, or dnc (do not call); a call result never moves it earlier in that list. paused suspends the contact's queued calls, and connecting restarts its call sequences, including finished ones. A paused contact can still be enrolled. Other values are legacy.",
                                "type": "string",
                                "enum": [
                                  "new",
                                  "invalid",
                                  "paused",
                                  "unanswered",
                                  "unreachable",
                                  "connecting",
                                  "answered",
                                  "contact_verified",
                                  "disqualified",
                                  "qualified",
                                  "converted",
                                  "dont_call",
                                  "dnc_blocked",
                                  "not_assigned",
                                  "assigned",
                                  "not_answered",
                                  "not_reached",
                                  "reached",
                                  "dnc"
                                ]
                              },
                              "sourceKind": {
                                "type": "string",
                                "enum": [
                                  "hubspot",
                                  "salesforce",
                                  "api",
                                  "csv",
                                  "manual",
                                  "monday",
                                  "oneai"
                                ],
                                "description": "Where the contact came from: a CRM (hubspot, salesforce, monday), the API, a CSV import, manual entry in Harmony, or oneai when Harmony created it from an inbound call."
                              },
                              "externalId": {
                                "nullable": true,
                                "description": "The contact's ID in its source. Set to the contact's Harmony ID when it was created without one.",
                                "type": "string"
                              },
                              "customFields": {
                                "nullable": true,
                                "description": "Account-defined values for the contact, keyed by contact field key.",
                                "type": "object",
                                "additionalProperties": {}
                              },
                              "priority": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Dial order of the contact's queued calls, from 0 to 1000. Higher is called first."
                              },
                              "assignedWorkflowId": {
                                "nullable": true,
                                "description": "The workflow the contact was last enrolled in. It stays set after that enrollment ends, and is null if the contact was never enrolled.",
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              "callCount": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Number of call outcomes recorded for the contact since it was last enrolled without reEnroll. An outcome is not counted when its call started at or before lastCallAt."
                              },
                              "maxAttempts": {
                                "nullable": true,
                                "description": "The Voice agent's configured attempt limit, copied at enrollment. Callbacks can exceed it.",
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991
                              },
                              "lastCallAt": {
                                "nullable": true,
                                "description": "When the latest call with the contact started. Enrolling the contact in a workflow clears it, except on re-enrollment.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              },
                              "nextCallAt": {
                                "nullable": true,
                                "description": "The next attempt time reported for the call that started at lastCallAt. Null when that report had none, or after an enrollment starts or ends or a do-not-call block. Later reschedules are not reflected.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              },
                              "createdAt": {
                                "description": "When the contact was added, or the createdAt sent by the upsert that created it.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              },
                              "updatedAt": {
                                "description": "When the contact was last changed.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              }
                            },
                            "required": [
                              "id",
                              "accountId",
                              "firstName",
                              "lastName",
                              "fullName",
                              "phone",
                              "location",
                              "locationDefaulted",
                              "timezone",
                              "timezoneInferred",
                              "status",
                              "sourceKind",
                              "externalId",
                              "customFields",
                              "priority",
                              "assignedWorkflowId",
                              "callCount",
                              "maxAttempts",
                              "lastCallAt",
                              "nextCallAt",
                              "createdAt",
                              "updatedAt"
                            ],
                            "additionalProperties": false
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "tags": [
          "contacts"
        ]
      }
    },
    "/api/v1/contacts/source/{sourceKind}/external-id/{externalId}/calls": {
      "get": {
        "operationId": "listContactCallsByExternalId",
        "summary": "List a contact's calls by external ID",
        "description": "Returns the agent calls of the contact with this external ID from this source, oldest first. Returns 409 with the matching contacts when more than one has it.",
        "parameters": [
          {
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            },
            "in": "query",
            "name": "limit",
            "required": false,
            "description": "Page size."
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "in": "query",
            "name": "nextToken",
            "required": false,
            "description": "The nextToken from the previous page."
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "hubspot",
                "salesforce",
                "api",
                "csv",
                "manual",
                "monday",
                "oneai"
              ]
            },
            "in": "path",
            "name": "sourceKind",
            "required": true,
            "description": "The source the contact came from."
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256
            },
            "in": "path",
            "name": "externalId",
            "required": true,
            "description": "The contact's ID in that source. Matching is case- and accent-sensitive and ignores trailing spaces."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                            "description": "Harmony's call ID."
                          },
                          "externalId": {
                            "nullable": true,
                            "description": "The source system's call ID. For OneAI calls, the OneAI chat ID.",
                            "type": "string"
                          },
                          "direction": {
                            "type": "string",
                            "enum": [
                              "outbound",
                              "inbound"
                            ],
                            "description": "outbound when the Voice agent called the contact, inbound when the contact called the agent."
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "queued",
                              "dialing",
                              "live",
                              "completed",
                              "failed",
                              "canceled"
                            ],
                            "description": "The call's state. Agent calls are recorded after they end, as completed or failed."
                          },
                          "outcome": {
                            "nullable": true,
                            "description": "The call result.",
                            "type": "string",
                            "enum": [
                              "new",
                              "neutral_value_call",
                              "unknown",
                              "busy",
                              "no_answer",
                              "invalid_number",
                              "carrier_blocked",
                              "wrong_language",
                              "wrong_number",
                              "do_not_call",
                              "voicemail",
                              "ivr",
                              "contact_verified",
                              "not_interested",
                              "disqualified",
                              "callback_requested",
                              "human_busy",
                              "qualified",
                              "converted",
                              "answered",
                              "identified"
                            ]
                          },
                          "attemptNumber": {
                            "type": "integer",
                            "minimum": -9007199254740991,
                            "maximum": 9007199254740991,
                            "description": "Which attempt in the Voice agent's call sequence this call was."
                          },
                          "agentId": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                            "description": "The Voice agent that handled the call."
                          },
                          "agentPhoneNumber": {
                            "nullable": true,
                            "description": "The Voice agent's phone number on the call.",
                            "type": "string"
                          },
                          "contactPhoneNumber": {
                            "nullable": true,
                            "description": "The contact's phone number on the call.",
                            "type": "string"
                          },
                          "startedAt": {
                            "nullable": true,
                            "description": "When the call started.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          },
                          "endedAt": {
                            "nullable": true,
                            "description": "When the call ended.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          },
                          "durationSeconds": {
                            "nullable": true,
                            "description": "Length of the call in seconds.",
                            "type": "integer",
                            "minimum": -9007199254740991,
                            "maximum": 9007199254740991
                          },
                          "dialedAt": {
                            "nullable": true,
                            "description": "When dialing started. Null until the call report is stored.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          },
                          "dialingDurationSeconds": {
                            "nullable": true,
                            "description": "Seconds spent ringing. Null until the call report is stored.",
                            "type": "number"
                          },
                          "endReason": {
                            "nullable": true,
                            "description": "Why the call ended, such as call_dropped. Null until the call report is stored.",
                            "type": "string"
                          },
                          "nextCallAt": {
                            "nullable": true,
                            "description": "The follow-up attempt this call scheduled. Null when none was scheduled or the call report is not stored yet.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          },
                          "collectedFields": {
                            "type": "object",
                            "additionalProperties": {
                              "type": "string"
                            },
                            "description": "Values the agent collected during the call, by field name."
                          }
                        },
                        "required": [
                          "id",
                          "externalId",
                          "direction",
                          "status",
                          "outcome",
                          "attemptNumber",
                          "agentId",
                          "agentPhoneNumber",
                          "contactPhoneNumber",
                          "startedAt",
                          "endedAt",
                          "durationSeconds",
                          "dialedAt",
                          "dialingDurationSeconds",
                          "endReason",
                          "nextCallAt",
                          "collectedFields"
                        ],
                        "additionalProperties": false
                      },
                      "description": "One page of the contact's Voice agent calls, oldest first."
                    },
                    "nextToken": {
                      "description": "Pass as nextToken to fetch the next page. Absent on the last page.",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "409": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PublicError"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "contacts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                                "description": "The contact's Harmony ID."
                              },
                              "accountId": {
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                                "description": "The Account that owns the contact."
                              },
                              "firstName": {
                                "nullable": true,
                                "description": "The contact's first name.",
                                "type": "string"
                              },
                              "lastName": {
                                "nullable": true,
                                "description": "The contact's last name.",
                                "type": "string"
                              },
                              "fullName": {
                                "nullable": true,
                                "description": "The contact's full name: the text sent as fullName, or firstName and lastName joined when none was sent.",
                                "type": "string"
                              },
                              "phone": {
                                "nullable": true,
                                "description": "The contact's phone number, normalized to E.164 on write.",
                                "type": "string"
                              },
                              "location": {
                                "nullable": true,
                                "description": "The country, or state or province, the contact is in, as an ISO 3166 code such as DE or US-CA. Null when none was given and the Account has no default location.",
                                "type": "string"
                              },
                              "locationDefaulted": {
                                "type": "boolean",
                                "description": "True when location is the Account's default location rather than one given for the contact. A defaulted location follows the Account's current default on each write."
                              },
                              "timezone": {
                                "nullable": true,
                                "description": "The contact's IANA time zone, such as America/New_York. When null, calls use the Voice agent's time zone.",
                                "type": "string"
                              },
                              "timezoneInferred": {
                                "type": "boolean",
                                "description": "True when timezone was inferred rather than given. An inferred timezone is inferred again on each write."
                              },
                              "language": {
                                "nullable": true,
                                "description": "The language the contact speaks, as a locale such as he-IL. An answered call updates it to the language the call ended in. Null when none is known, and calls then start in the Voice agent's default language.",
                                "type": "string"
                              },
                              "status": {
                                "nullable": true,
                                "description": "The contact's engagement status. Harmony sets not_assigned, assigned, not_answered, not_reached, reached, disqualified, qualified, converted, or dnc (do not call); a call result never moves it earlier in that list. paused suspends the contact's queued calls, and connecting restarts its call sequences, including finished ones. A paused contact can still be enrolled. Other values are legacy.",
                                "type": "string",
                                "enum": [
                                  "new",
                                  "invalid",
                                  "paused",
                                  "unanswered",
                                  "unreachable",
                                  "connecting",
                                  "answered",
                                  "contact_verified",
                                  "disqualified",
                                  "qualified",
                                  "converted",
                                  "dont_call",
                                  "dnc_blocked",
                                  "not_assigned",
                                  "assigned",
                                  "not_answered",
                                  "not_reached",
                                  "reached",
                                  "dnc"
                                ]
                              },
                              "sourceKind": {
                                "type": "string",
                                "enum": [
                                  "hubspot",
                                  "salesforce",
                                  "api",
                                  "csv",
                                  "manual",
                                  "monday",
                                  "oneai"
                                ],
                                "description": "Where the contact came from: a CRM (hubspot, salesforce, monday), the API, a CSV import, manual entry in Harmony, or oneai when Harmony created it from an inbound call."
                              },
                              "externalId": {
                                "nullable": true,
                                "description": "The contact's ID in its source. Set to the contact's Harmony ID when it was created without one.",
                                "type": "string"
                              },
                              "customFields": {
                                "nullable": true,
                                "description": "Account-defined values for the contact, keyed by contact field key.",
                                "type": "object",
                                "additionalProperties": {}
                              },
                              "priority": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Dial order of the contact's queued calls, from 0 to 1000. Higher is called first."
                              },
                              "assignedWorkflowId": {
                                "nullable": true,
                                "description": "The workflow the contact was last enrolled in. It stays set after that enrollment ends, and is null if the contact was never enrolled.",
                                "type": "string",
                                "format": "uuid",
                                "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                              },
                              "callCount": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991,
                                "description": "Number of call outcomes recorded for the contact since it was last enrolled without reEnroll. An outcome is not counted when its call started at or before lastCallAt."
                              },
                              "maxAttempts": {
                                "nullable": true,
                                "description": "The Voice agent's configured attempt limit, copied at enrollment. Callbacks can exceed it.",
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 9007199254740991
                              },
                              "lastCallAt": {
                                "nullable": true,
                                "description": "When the latest call with the contact started. Enrolling the contact in a workflow clears it, except on re-enrollment.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              },
                              "nextCallAt": {
                                "nullable": true,
                                "description": "The next attempt time reported for the call that started at lastCallAt. Null when that report had none, or after an enrollment starts or ends or a do-not-call block. Later reschedules are not reflected.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              },
                              "createdAt": {
                                "description": "When the contact was added, or the createdAt sent by the upsert that created it.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              },
                              "updatedAt": {
                                "description": "When the contact was last changed.",
                                "type": "string",
                                "format": "date-time",
                                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                              }
                            },
                            "required": [
                              "id",
                              "accountId",
                              "firstName",
                              "lastName",
                              "fullName",
                              "phone",
                              "location",
                              "locationDefaulted",
                              "timezone",
                              "timezoneInferred",
                              "language",
                              "status",
                              "sourceKind",
                              "externalId",
                              "customFields",
                              "priority",
                              "assignedWorkflowId",
                              "callCount",
                              "maxAttempts",
                              "lastCallAt",
                              "nextCallAt",
                              "createdAt",
                              "updatedAt"
                            ],
                            "additionalProperties": false
                          },
                          "description": "The contacts from this source with this external ID, up to 100."
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "tags": [
          "contacts"
        ]
      }
    },
    "/api/v1/contacts/upsert": {
      "put": {
        "operationId": "upsertContact",
        "summary": "Create or update a contact",
        "description": "Creates one contact, or updates the contact that matches its dedup fields, and returns it. Optionally enrolls the contact into a workflow after it is saved.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 128
                  },
                  "lastName": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 128
                  },
                  "fullName": {
                    "description": "Sent alone, it is split into firstName and lastName, dropping middle names, titles, and suffixes. Sent with only one of them, it fills the other. Without it, the full name is built from firstName and lastName.",
                    "nullable": true,
                    "type": "string",
                    "maxLength": 256
                  },
                  "phone": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 64,
                    "pattern": "^(\\+|00)?[\\d\\s().-]*\\d[\\d\\s().-]*$"
                  },
                  "location": {
                    "description": "The country, or state or province, the contact is in: an ISO 3166 code such as DE or US-CA, or a name such as Germany or California. A value that cannot be read is stored as empty. Empty takes the account's default location.",
                    "nullable": true,
                    "type": "string",
                    "maxLength": 64
                  },
                  "timezone": {
                    "nullable": true,
                    "type": "string",
                    "maxLength": 64
                  },
                  "language": {
                    "description": "The language the contact speaks: one of en-US, en-GB, he-IL, fr-FR, de-DE, es-ES, es-MX, es-US, or es-419, or a value read as one of them, such as he, Hebrew, or es-AR. A value that cannot be read is ignored. An upsert that matches an existing contact sets it only when the contact has none.",
                    "nullable": true,
                    "type": "string",
                    "maxLength": 64
                  },
                  "status": {
                    "description": "Replaces the stored status when the upsert matches an existing contact, which keeps its status when this is omitted or null. A new contact starts as not_assigned when this is omitted, and with no status when it is null. A blacklisted phone is stored as dnc whatever status is sent.",
                    "nullable": true,
                    "type": "string",
                    "enum": [
                      "new",
                      "invalid",
                      "paused",
                      "unanswered",
                      "unreachable",
                      "connecting",
                      "answered",
                      "contact_verified",
                      "disqualified",
                      "qualified",
                      "converted",
                      "dont_call",
                      "dnc_blocked",
                      "not_assigned",
                      "assigned",
                      "not_answered",
                      "not_reached",
                      "reached",
                      "dnc"
                    ]
                  },
                  "sourceKind": {
                    "type": "string",
                    "enum": [
                      "hubspot",
                      "salesforce",
                      "api",
                      "csv",
                      "manual",
                      "monday",
                      "oneai"
                    ]
                  },
                  "externalId": {
                    "nullable": true,
                    "description": "When null, defaults to the contact's ID if the upsert creates a contact, and an existing contact keeps its stored external ID.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "customFields": {
                    "description": "Merged into the contact's stored custom fields when the upsert matches an existing contact. Keys not sent keep their value, and a key sent as null is removed.",
                    "nullable": true,
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "priority": {
                    "description": "Higher-priority contacts are called first. New contacts default to 0.",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                  },
                  "assignmentOptions": {
                    "type": "object",
                    "properties": {
                      "workflowId": {
                        "type": "string",
                        "format": "uuid",
                        "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                      },
                      "reEnroll": {
                        "type": "boolean",
                        "description": "Enrolls the contact into the workflow again even if that workflow already ran for it. Must be sent together with enforceCallNow."
                      },
                      "enforceCallNow": {
                        "type": "boolean",
                        "description": "Dials immediately instead of waiting for the account's working-hours window. Must be sent together with reEnroll."
                      }
                    }
                  }
                },
                "required": [
                  "sourceKind",
                  "externalId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated the existing contact.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The contact's Harmony ID."
                    },
                    "accountId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The Account that owns the contact."
                    },
                    "firstName": {
                      "nullable": true,
                      "description": "The contact's first name.",
                      "type": "string"
                    },
                    "lastName": {
                      "nullable": true,
                      "description": "The contact's last name.",
                      "type": "string"
                    },
                    "fullName": {
                      "nullable": true,
                      "description": "The contact's full name: the text sent as fullName, or firstName and lastName joined when none was sent.",
                      "type": "string"
                    },
                    "phone": {
                      "nullable": true,
                      "description": "The contact's phone number, normalized to E.164 on write.",
                      "type": "string"
                    },
                    "location": {
                      "nullable": true,
                      "description": "The country, or state or province, the contact is in, as an ISO 3166 code such as DE or US-CA. Null when none was given and the Account has no default location.",
                      "type": "string"
                    },
                    "locationDefaulted": {
                      "type": "boolean",
                      "description": "True when location is the Account's default location rather than one given for the contact. A defaulted location follows the Account's current default on each write."
                    },
                    "timezone": {
                      "nullable": true,
                      "description": "The contact's IANA time zone, such as America/New_York. When null, calls use the Voice agent's time zone.",
                      "type": "string"
                    },
                    "timezoneInferred": {
                      "type": "boolean",
                      "description": "True when timezone was inferred rather than given. An inferred timezone is inferred again on each write."
                    },
                    "language": {
                      "nullable": true,
                      "description": "The language the contact speaks, as a locale such as he-IL. An answered call updates it to the language the call ended in. Null when none is known, and calls then start in the Voice agent's default language.",
                      "type": "string"
                    },
                    "status": {
                      "nullable": true,
                      "description": "The contact's engagement status. Harmony sets not_assigned, assigned, not_answered, not_reached, reached, disqualified, qualified, converted, or dnc (do not call); a call result never moves it earlier in that list. paused suspends the contact's queued calls, and connecting restarts its call sequences, including finished ones. A paused contact can still be enrolled. Other values are legacy.",
                      "type": "string",
                      "enum": [
                        "new",
                        "invalid",
                        "paused",
                        "unanswered",
                        "unreachable",
                        "connecting",
                        "answered",
                        "contact_verified",
                        "disqualified",
                        "qualified",
                        "converted",
                        "dont_call",
                        "dnc_blocked",
                        "not_assigned",
                        "assigned",
                        "not_answered",
                        "not_reached",
                        "reached",
                        "dnc"
                      ]
                    },
                    "sourceKind": {
                      "type": "string",
                      "enum": [
                        "hubspot",
                        "salesforce",
                        "api",
                        "csv",
                        "manual",
                        "monday",
                        "oneai"
                      ],
                      "description": "Where the contact came from: a CRM (hubspot, salesforce, monday), the API, a CSV import, manual entry in Harmony, or oneai when Harmony created it from an inbound call."
                    },
                    "externalId": {
                      "nullable": true,
                      "description": "The contact's ID in its source. Set to the contact's Harmony ID when it was created without one.",
                      "type": "string"
                    },
                    "customFields": {
                      "nullable": true,
                      "description": "Account-defined values for the contact, keyed by contact field key.",
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "priority": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Dial order of the contact's queued calls, from 0 to 1000. Higher is called first."
                    },
                    "assignedWorkflowId": {
                      "nullable": true,
                      "description": "The workflow the contact was last enrolled in. It stays set after that enrollment ends, and is null if the contact was never enrolled.",
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "callCount": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Number of call outcomes recorded for the contact since it was last enrolled without reEnroll. An outcome is not counted when its call started at or before lastCallAt."
                    },
                    "maxAttempts": {
                      "nullable": true,
                      "description": "The Voice agent's configured attempt limit, copied at enrollment. Callbacks can exceed it.",
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "lastCallAt": {
                      "nullable": true,
                      "description": "When the latest call with the contact started. Enrolling the contact in a workflow clears it, except on re-enrollment.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "nextCallAt": {
                      "nullable": true,
                      "description": "The next attempt time reported for the call that started at lastCallAt. Null when that report had none, or after an enrollment starts or ends or a do-not-call block. Later reschedules are not reflected.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "createdAt": {
                      "description": "When the contact was added, or the createdAt sent by the upsert that created it.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "updatedAt": {
                      "description": "When the contact was last changed.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    }
                  },
                  "required": [
                    "id",
                    "accountId",
                    "firstName",
                    "lastName",
                    "fullName",
                    "phone",
                    "location",
                    "locationDefaulted",
                    "timezone",
                    "timezoneInferred",
                    "language",
                    "status",
                    "sourceKind",
                    "externalId",
                    "customFields",
                    "priority",
                    "assignedWorkflowId",
                    "callCount",
                    "maxAttempts",
                    "lastCallAt",
                    "nextCallAt",
                    "createdAt",
                    "updatedAt"
                  ],
                  "additionalProperties": false,
                  "description": "Updated the existing contact."
                }
              }
            }
          },
          "201": {
            "description": "Created a new contact.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The contact's Harmony ID."
                    },
                    "accountId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The Account that owns the contact."
                    },
                    "firstName": {
                      "nullable": true,
                      "description": "The contact's first name.",
                      "type": "string"
                    },
                    "lastName": {
                      "nullable": true,
                      "description": "The contact's last name.",
                      "type": "string"
                    },
                    "fullName": {
                      "nullable": true,
                      "description": "The contact's full name: the text sent as fullName, or firstName and lastName joined when none was sent.",
                      "type": "string"
                    },
                    "phone": {
                      "nullable": true,
                      "description": "The contact's phone number, normalized to E.164 on write.",
                      "type": "string"
                    },
                    "location": {
                      "nullable": true,
                      "description": "The country, or state or province, the contact is in, as an ISO 3166 code such as DE or US-CA. Null when none was given and the Account has no default location.",
                      "type": "string"
                    },
                    "locationDefaulted": {
                      "type": "boolean",
                      "description": "True when location is the Account's default location rather than one given for the contact. A defaulted location follows the Account's current default on each write."
                    },
                    "timezone": {
                      "nullable": true,
                      "description": "The contact's IANA time zone, such as America/New_York. When null, calls use the Voice agent's time zone.",
                      "type": "string"
                    },
                    "timezoneInferred": {
                      "type": "boolean",
                      "description": "True when timezone was inferred rather than given. An inferred timezone is inferred again on each write."
                    },
                    "language": {
                      "nullable": true,
                      "description": "The language the contact speaks, as a locale such as he-IL. An answered call updates it to the language the call ended in. Null when none is known, and calls then start in the Voice agent's default language.",
                      "type": "string"
                    },
                    "status": {
                      "nullable": true,
                      "description": "The contact's engagement status. Harmony sets not_assigned, assigned, not_answered, not_reached, reached, disqualified, qualified, converted, or dnc (do not call); a call result never moves it earlier in that list. paused suspends the contact's queued calls, and connecting restarts its call sequences, including finished ones. A paused contact can still be enrolled. Other values are legacy.",
                      "type": "string",
                      "enum": [
                        "new",
                        "invalid",
                        "paused",
                        "unanswered",
                        "unreachable",
                        "connecting",
                        "answered",
                        "contact_verified",
                        "disqualified",
                        "qualified",
                        "converted",
                        "dont_call",
                        "dnc_blocked",
                        "not_assigned",
                        "assigned",
                        "not_answered",
                        "not_reached",
                        "reached",
                        "dnc"
                      ]
                    },
                    "sourceKind": {
                      "type": "string",
                      "enum": [
                        "hubspot",
                        "salesforce",
                        "api",
                        "csv",
                        "manual",
                        "monday",
                        "oneai"
                      ],
                      "description": "Where the contact came from: a CRM (hubspot, salesforce, monday), the API, a CSV import, manual entry in Harmony, or oneai when Harmony created it from an inbound call."
                    },
                    "externalId": {
                      "nullable": true,
                      "description": "The contact's ID in its source. Set to the contact's Harmony ID when it was created without one.",
                      "type": "string"
                    },
                    "customFields": {
                      "nullable": true,
                      "description": "Account-defined values for the contact, keyed by contact field key.",
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "priority": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Dial order of the contact's queued calls, from 0 to 1000. Higher is called first."
                    },
                    "assignedWorkflowId": {
                      "nullable": true,
                      "description": "The workflow the contact was last enrolled in. It stays set after that enrollment ends, and is null if the contact was never enrolled.",
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "callCount": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Number of call outcomes recorded for the contact since it was last enrolled without reEnroll. An outcome is not counted when its call started at or before lastCallAt."
                    },
                    "maxAttempts": {
                      "nullable": true,
                      "description": "The Voice agent's configured attempt limit, copied at enrollment. Callbacks can exceed it.",
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "lastCallAt": {
                      "nullable": true,
                      "description": "When the latest call with the contact started. Enrolling the contact in a workflow clears it, except on re-enrollment.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "nextCallAt": {
                      "nullable": true,
                      "description": "The next attempt time reported for the call that started at lastCallAt. Null when that report had none, or after an enrollment starts or ends or a do-not-call block. Later reschedules are not reflected.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "createdAt": {
                      "description": "When the contact was added, or the createdAt sent by the upsert that created it.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "updatedAt": {
                      "description": "When the contact was last changed.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    }
                  },
                  "required": [
                    "id",
                    "accountId",
                    "firstName",
                    "lastName",
                    "fullName",
                    "phone",
                    "location",
                    "locationDefaulted",
                    "timezone",
                    "timezoneInferred",
                    "language",
                    "status",
                    "sourceKind",
                    "externalId",
                    "customFields",
                    "priority",
                    "assignedWorkflowId",
                    "callCount",
                    "maxAttempts",
                    "lastCallAt",
                    "nextCallAt",
                    "createdAt",
                    "updatedAt"
                  ],
                  "additionalProperties": false,
                  "description": "Created a new contact."
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "422": {
            "description": "The contact cannot be saved: its phone is invalid, it has none of the Account's dedup fields, or those values are too long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          }
        },
        "tags": [
          "contacts"
        ]
      }
    },
    "/api/v1/contacts/{id}": {
      "get": {
        "operationId": "getContact",
        "summary": "Get a contact",
        "description": "Returns one contact by its Harmony contact ID.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "in": "path",
            "name": "id",
            "required": true,
            "description": "The contact's Harmony ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The contact's Harmony ID."
                    },
                    "accountId": {
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                      "description": "The Account that owns the contact."
                    },
                    "firstName": {
                      "nullable": true,
                      "description": "The contact's first name.",
                      "type": "string"
                    },
                    "lastName": {
                      "nullable": true,
                      "description": "The contact's last name.",
                      "type": "string"
                    },
                    "fullName": {
                      "nullable": true,
                      "description": "The contact's full name: the text sent as fullName, or firstName and lastName joined when none was sent.",
                      "type": "string"
                    },
                    "phone": {
                      "nullable": true,
                      "description": "The contact's phone number, normalized to E.164 on write.",
                      "type": "string"
                    },
                    "location": {
                      "nullable": true,
                      "description": "The country, or state or province, the contact is in, as an ISO 3166 code such as DE or US-CA. Null when none was given and the Account has no default location.",
                      "type": "string"
                    },
                    "locationDefaulted": {
                      "type": "boolean",
                      "description": "True when location is the Account's default location rather than one given for the contact. A defaulted location follows the Account's current default on each write."
                    },
                    "timezone": {
                      "nullable": true,
                      "description": "The contact's IANA time zone, such as America/New_York. When null, calls use the Voice agent's time zone.",
                      "type": "string"
                    },
                    "timezoneInferred": {
                      "type": "boolean",
                      "description": "True when timezone was inferred rather than given. An inferred timezone is inferred again on each write."
                    },
                    "language": {
                      "nullable": true,
                      "description": "The language the contact speaks, as a locale such as he-IL. An answered call updates it to the language the call ended in. Null when none is known, and calls then start in the Voice agent's default language.",
                      "type": "string"
                    },
                    "status": {
                      "nullable": true,
                      "description": "The contact's engagement status. Harmony sets not_assigned, assigned, not_answered, not_reached, reached, disqualified, qualified, converted, or dnc (do not call); a call result never moves it earlier in that list. paused suspends the contact's queued calls, and connecting restarts its call sequences, including finished ones. A paused contact can still be enrolled. Other values are legacy.",
                      "type": "string",
                      "enum": [
                        "new",
                        "invalid",
                        "paused",
                        "unanswered",
                        "unreachable",
                        "connecting",
                        "answered",
                        "contact_verified",
                        "disqualified",
                        "qualified",
                        "converted",
                        "dont_call",
                        "dnc_blocked",
                        "not_assigned",
                        "assigned",
                        "not_answered",
                        "not_reached",
                        "reached",
                        "dnc"
                      ]
                    },
                    "sourceKind": {
                      "type": "string",
                      "enum": [
                        "hubspot",
                        "salesforce",
                        "api",
                        "csv",
                        "manual",
                        "monday",
                        "oneai"
                      ],
                      "description": "Where the contact came from: a CRM (hubspot, salesforce, monday), the API, a CSV import, manual entry in Harmony, or oneai when Harmony created it from an inbound call."
                    },
                    "externalId": {
                      "nullable": true,
                      "description": "The contact's ID in its source. Set to the contact's Harmony ID when it was created without one.",
                      "type": "string"
                    },
                    "customFields": {
                      "nullable": true,
                      "description": "Account-defined values for the contact, keyed by contact field key.",
                      "type": "object",
                      "additionalProperties": {}
                    },
                    "priority": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Dial order of the contact's queued calls, from 0 to 1000. Higher is called first."
                    },
                    "assignedWorkflowId": {
                      "nullable": true,
                      "description": "The workflow the contact was last enrolled in. It stays set after that enrollment ends, and is null if the contact was never enrolled.",
                      "type": "string",
                      "format": "uuid",
                      "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                    },
                    "callCount": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991,
                      "description": "Number of call outcomes recorded for the contact since it was last enrolled without reEnroll. An outcome is not counted when its call started at or before lastCallAt."
                    },
                    "maxAttempts": {
                      "nullable": true,
                      "description": "The Voice agent's configured attempt limit, copied at enrollment. Callbacks can exceed it.",
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    },
                    "lastCallAt": {
                      "nullable": true,
                      "description": "When the latest call with the contact started. Enrolling the contact in a workflow clears it, except on re-enrollment.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "nextCallAt": {
                      "nullable": true,
                      "description": "The next attempt time reported for the call that started at lastCallAt. Null when that report had none, or after an enrollment starts or ends or a do-not-call block. Later reschedules are not reflected.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "createdAt": {
                      "description": "When the contact was added, or the createdAt sent by the upsert that created it.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    },
                    "updatedAt": {
                      "description": "When the contact was last changed.",
                      "type": "string",
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                    }
                  },
                  "required": [
                    "id",
                    "accountId",
                    "firstName",
                    "lastName",
                    "fullName",
                    "phone",
                    "location",
                    "locationDefaulted",
                    "timezone",
                    "timezoneInferred",
                    "language",
                    "status",
                    "sourceKind",
                    "externalId",
                    "customFields",
                    "priority",
                    "assignedWorkflowId",
                    "callCount",
                    "maxAttempts",
                    "lastCallAt",
                    "nextCallAt",
                    "createdAt",
                    "updatedAt"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "404": {
            "description": "The resource does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          }
        },
        "tags": [
          "contacts"
        ]
      }
    },
    "/api/v1/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "summary": "List workflows",
        "description": "Returns one page of the Account's workflows.",
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
            },
            "in": "query",
            "name": "nextToken",
            "required": false,
            "description": "The nextToken from the previous page."
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000
            },
            "in": "query",
            "name": "limit",
            "required": false,
            "description": "Page size. Defaults to 50."
          }
        ],
        "responses": {
          "200": {
            "description": "Default Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                            "description": "The workflow's ID. Send it as assignmentOptions.workflowId to enroll contacts in the workflow."
                          },
                          "accountId": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                            "description": "The Account that owns the workflow."
                          },
                          "creatorId": {
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
                            "description": "The Harmony user who created the workflow."
                          },
                          "parentType": {
                            "anyOf": [
                              {
                                "type": "string",
                                "enum": [
                                  "agent"
                                ]
                              },
                              {
                                "type": "string",
                                "nullable": true,
                                "enum": [
                                  null
                                ]
                              }
                            ],
                            "description": "agent when the workflow belongs to a Voice agent; otherwise null."
                          },
                          "parentRefId": {
                            "nullable": true,
                            "description": "The ID of the Voice agent the workflow belongs to; otherwise null.",
                            "type": "string",
                            "format": "uuid",
                            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                          },
                          "name": {
                            "type": "string",
                            "description": "The workflow's name. A Voice agent's workflow is named after the agent when the agent is created, and later agent renames do not change it."
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "draft",
                              "published",
                              "archived",
                              "paused"
                            ],
                            "description": "The stored workflow status. A Voice agent's workflow is created as draft, and the status does not affect whether the workflow runs."
                          },
                          "createdAt": {
                            "description": "When the workflow was created.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          },
                          "updatedAt": {
                            "description": "When the workflow was last changed.",
                            "type": "string",
                            "format": "date-time",
                            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d\\.\\d{3}(?:Z))$"
                          }
                        },
                        "required": [
                          "id",
                          "accountId",
                          "creatorId",
                          "parentType",
                          "parentRefId",
                          "name",
                          "status",
                          "createdAt",
                          "updatedAt"
                        ],
                        "additionalProperties": false
                      },
                      "description": "One page of the Account's workflows, oldest first."
                    },
                    "nextToken": {
                      "description": "Pass as nextToken to fetch the next page. Absent on the last page.",
                      "type": "string"
                    }
                  },
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The request is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicError"
                }
              }
            }
          }
        },
        "tags": [
          "workflows"
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A Harmony API key (`harmony_pk_…`), created in Settings > Developers."
      }
    },
    "schemas": {
      "PublicError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "The HTTP reason phrase."
          },
          "message": {
            "type": "string",
            "description": "A human-readable explanation."
          },
          "code": {
            "type": "string",
            "description": "A stable machine-readable error code, when one applies."
          },
          "requestId": {
            "type": "string",
            "description": "The request ID, also sent as `x-request-id`."
          }
        },
        "required": [
          "error",
          "message",
          "requestId"
        ]
      }
    }
  }
}
