{
  "openapi": "3.2.1",
  "info": {
    "title": "Carepatron Public API",
    "version": "2026-10-02",
    "contact": {
      "name": "Carepatron API Support",
      "email": "justin@carepatron.com",
      "url": "https://www.carepatron.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.carepatron.com/terms-of-service"
    },
    "description": "Public API for Carepatron healthcare PMS/EHR platform.\n\nThis API provides programmatic access to core Carepatron functionality.\n"
  },
  "servers": [
    {
      "url": "https://api.carepatron.com",
      "description": "Carepatron Production API"
    }
  ],
  "tags": [
    {
      "name": "Contacts",
      "description": "Operations related to contacts"
    },
    {
      "name": "Email addresses",
      "parent": "Contacts",
      "description": "Operations related to a contact's email addresses"
    },
    {
      "name": "Phone numbers",
      "parent": "Contacts",
      "description": "Operations related to a contact's phone numbers"
    },
    {
      "name": "Addresses",
      "parent": "Contacts",
      "description": "Operations related to a contact's postal addresses"
    },
    {
      "name": "Custom fields",
      "parent": "Contacts",
      "description": "Operations related to contact custom field definitions and each contact's values"
    },
    {
      "name": "Diagnostics",
      "description": "Operations related to API health and status"
    }
  ],
  "paths": {
    "/ping": {
      "get": {
        "operationId": "ping",
        "tags": [
          "Diagnostics"
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "const": "pong"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Reachability ping",
        "description": "Anonymous reachability ping; returns the literal string `pong` to confirm the API is reachable."
      }
    },
    "/providers/{providerId}/secure-ping": {
      "get": {
        "operationId": "securePing",
        "tags": [
          "Diagnostics"
        ],
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Cache-Control": {
                "$ref": "#/components/headers/CacheControl"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "const": "pong"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Authenticated ping",
        "description": "Authenticated connectivity probe.\nReturns the literal string `pong` when the supplied `cp-api-key` is valid for the given provider\ntenant.\n"
      }
    },
    "/providers/{providerId}/contacts": {
      "get": {
        "operationId": "listContacts",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/Include"
          },
          {
            "$ref": "#/components/parameters/IncludeArchived"
          },
          {
            "$ref": "#/components/parameters/PaginationLimit"
          },
          {
            "$ref": "#/components/parameters/PaginationCursor"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactsListResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "List contacts",
        "description": "Returns one page of the workspace's contacts, most recently created first.\n\nThe listed set is active contacts, and `includeArchived=true` widens it to archived ones too, each\nflagged by `archived`.  Trashed contacts are never listed.  Like `include`, the flag is read afresh on\nevery page rather than carried in the cursor.\n\nPaging is by opaque cursor rather than by page number, so a contact created while you are\npaging cannot shift rows you have already read onto a page you have already passed.  Send the\nprevious response's `nextCursor` as `cursor` to continue, and omit `cursor` to start.\n`nextCursor` is null on the last page, and is the only end-of-listing signal: do not infer the\nend from the number of contacts a page holds.\n\nA cursor encodes a position in this ordering and nothing else.  Treat it as opaque: do not\nconstruct, parse or edit one.  A value this API did not issue is refused with 400 rather than\nsilently restarting from the first page, which would hand you rows you had already seen under a\n200.\n"
      },
      "post": {
        "operationId": "createContact",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactInput"
              }
            }
          },
          "description": "The contact to create, whole: its personal details, whether it is a client, its email addresses,\nphone numbers and postal addresses, and its custom field values.\n"
        },
        "responses": {
          "202": {
            "$ref": "#/components/responses/ContactAccepted"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Create a contact",
        "description": "Creates a contact and returns 202 Accepted with its identifier in the\nLocation header and the accepted contact in the body.\n\nThe request states the contact whole, and every member is required.\n`isClient` says whether the contact is one of the workspace's clients,\nand a collection holding nothing is `[]`.  A non-empty `emails`,\n`phoneNumbers` or `addresses` designates exactly one entry with\n`isPrimary: true`; one designating none or several is refused with `400`\nand the code `exactly_one_primary` at the collection.  `customFields`\nstates the fields you set - any subset of the workspace's - each naming a\nfield the workspace defines and holding a value that field accepts.\n\nThe response is the ACCEPTED representation: the fields you sent, plus\nthe identifiers this API assigned to the contact and to each entry of its\ncollections.  It is not a read of stored state, so `createdAtUtc` is when\nthe create was accepted, and each value is as accepted: an email address\nis stored trimmed and lower-cased, so a later read can serve it in a\ndifferent case, and a phone number is stored as you sent it.  Treat the\nLocation header as the contact's address and this body as confirmation of\nwhat was accepted at it; read the contact itself when you need its stored\nstate.\n\nThe identifier is final and the contact is durable, so there is nothing\nto poll for.  A read of the new contact may briefly return 404 even so:\nreads are served from a replica, and the write reaches it a moment after\nthe response.  Re-read after a short delay rather than treating the first\n404 as the contact not existing - this body already carries every field\nsuch a read would return.\n\nSend an Idempotency-Key, so that a resend of a create that completed is\nanswered with its first response rather than applied again.  A create that\ntimed out with `504` may still have been applied, and a resend of it is\nexecuted again, which can create a second contact.\n\nCreating a client (`isClient` true) runs the workspace's client-created\nworkflows as a create in Carepatron does, which can link the contact to a\nclient portal login, create a billing customer for it and send a portal\ninvitation under the practice's name.\n\nA workspace can send at most 100 contact create requests a UTC day through\nthe API by default, refused and failed ones included; the next answers `429`\nwith `create_budget_exceeded` until 00:00 UTC.\n"
      }
    },
    "/providers/{providerId}/contacts/custom-fields": {
      "get": {
        "operationId": "getContactCustomFieldDefinitions",
        "tags": [
          "Custom fields"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactCustomFieldDefinitionsResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "List the contact custom field definitions",
        "description": "Returns every custom field the workspace has defined on its contacts, in key order, with a\nvalidator for the set.\n\nA workspace defines its own fields in the Carepatron app; this API reads those definitions and\nnever writes one.  Read them before writing a value: the key is the workspace's own, and a\nchoice field accepts only the option ids listed here.\n\nA field whose type this version cannot represent is left out.  It still exists and still holds\nits value - every write through this API preserves it - and\n`/providers/{providerId}/contacts/custom-fields/{key}` says so rather than answering `404`.\n\nA workspace holding more definitions than the published maximum is refused with `409` rather\nthan served a shortened list, which would read as the whole set.  Each definition is still\nreadable one at a time.\n"
      }
    },
    "/providers/{providerId}/contacts/custom-fields/{key}": {
      "get": {
        "operationId": "getContactCustomFieldDefinition",
        "tags": [
          "Custom fields"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/CustomFieldKey"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactCustomFieldDefinitionResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Get one contact custom field definition",
        "description": "Returns one definition, addressed by the key the workspace gave it, with its own validator.\n\nA key the workspace has not defined is `404`.  A key it HAS defined, with a type this version\ncannot represent, is `409` - a `404` would send you off to create a second field beside the one\nyou already have.\n\nThe maximum set length is not consulted here, so a workspace over it can still read each of its\ndefinitions.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}": {
      "get": {
        "operationId": "getContact",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/Include"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Get a contact",
        "description": "Returns a single contact, archived or not: `archived` says which.  `include` adds its custom field\nvalues and any of its email addresses, phone numbers and postal addresses.\n\nAnswers 404 for a contact that does not exist, has been trashed, or belongs to another workspace.\nThe read is scoped to the workspace the API key was issued for, and those cases are deliberately\nindistinguishable - a caller cannot use this operation to learn that a contact exists somewhere it\ncannot reach.\n\nThe response carries no `ETag`: no write takes the contact whole, and each part a write does take\nserves its own validator at its own URL.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/personal-details": {
      "get": {
        "operationId": "getContactPersonalDetails",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactPersonalDetailsResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Get a contact's personal details",
        "description": "Returns the contact's name and date of birth, and the validator the personal-details write takes on\n`If-Match`.\n\nAnswers 404 on the same terms as `GET .../contacts/{contactId}`: for a contact that does not exist, has\nbeen trashed, or belongs to another workspace.\n"
      },
      "put": {
        "operationId": "updateContactPersonalDetails",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactPersonalDetailsInput"
              }
            }
          },
          "description": "The contact's name and date of birth, stated whole.\n"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactPersonalDetailsResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Replace a contact's personal details",
        "description": "Replaces the contact's name and date of birth, and returns them as stored.\n\nThe write is TOTAL over its three fields, and every one must be stated: sending `dateOfBirth` as\nnull clears the stored date, and omitting it is refused with `400` rather than read as either a\nclear or a request to leave the date alone.  Every other field of the contact - including ones\nthis API does not yet expose - is preserved.\n\n`If-Match` is optional here, unlike on the contact's collections.  Send the validator from a read\nwhen you are changing a value you read; omit it when you are asserting absolute values from your\nown system of record, and accept that your three fields are then last-write-wins.  A stale\nvalidator is refused with `412` carrying the current one.\n\nAn immediate read may briefly return the OLD values even after a `200`: reads are served from a\nreplica and the write reaches it a moment later.  This response already carries every field such\na read would return, so use it rather than re-reading.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/trash": {
      "put": {
        "operationId": "trashContact",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Trash a contact",
        "description": "Moves a contact to the trash.  Recoverable, and hidden from every read on\nthis API while it is there.  Every other write to it is refused with `409`\nand the code `resource_conflict` while it is there, and `detail` names the\ntrash as the cause.\n\nTrashing a contact voids its unpaid, sent and scheduled invoices, cancels\ntheir payment intents and reminders, and removes its tasks and notes;\nrestoring the contact in Carepatron restores tasks and notes but not the\nvoided invoices.\n\nRepeating the request on a contact that is already trashed succeeds, so a\nretry after a timeout is safe and cannot be mistaken for a wrong\nidentifier.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/archive": {
      "put": {
        "operationId": "archiveContact",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Archive a contact",
        "description": "Hides a contact from the active working set without removing it.\n\nArchived and trashed are independent states, not two points on one scale:\na contact can be in both at once.  Archiving one that is already archived\nsucceeds and changes nothing.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/unarchive": {
      "put": {
        "operationId": "unarchiveContact",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Unarchive a contact",
        "description": "Returns an archived contact to the active working set.  Unarchiving one\nthat is not archived succeeds and changes nothing.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/convert-to-client": {
      "put": {
        "operationId": "convertContactToClient",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Convert a contact to a client",
        "description": "Makes a contact one of the workspace's clients.  A client with no status\ntakes the workspace's default active status.\n\nConverting a contact that is already a client succeeds and changes\nnothing.  This API has no reverse operation.\n\nReads are served from a replica, so an immediate read may briefly still\nserve `isClient: false`.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/emails": {
      "get": {
        "operationId": "getContactEmails",
        "tags": [
          "Email addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactEmailsResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "List a contact's email addresses",
        "description": "Returns every stored email address for the contact, each with its identifier, in identifier\norder.  The set serves no validator: it is written only through its members, each at its own URL\nwith a validator of its own.\n\nAn address stored before this API's format bar existed is returned as stored, and may not satisfy\n`EmailAddress`.  It does not stop a write to any other address, and replacing it at its own URL is\nhow it is corrected.\n"
      },
      "post": {
        "operationId": "createContactEmail",
        "tags": [
          "Email addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactEmailInput"
              }
            }
          },
          "description": "The email address to add to the contact.\n"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/ContactEmailCreated"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Add an email address to a contact",
        "description": "Adds one address without stating anything about the ones already stored, and answers `201` with\nthe new address's own address in `Location`.  The resource is readable there immediately.\n\nThe identifier is minted by this API, so `Location` is final.  There is no `If-Match` - a create\nasserts nothing about the set it joins - and the required `Idempotency-Key` is what makes a retry safe.\n\nEntries you never touched are relayed as they were stored, so a stored address this contract\ncould not describe does not refuse your write unless the system of record will not store it again.\nThat write is refused with `409`, and `detail` names the URL of each relayed address this contract\ncould not describe, that one among them, where each can be replaced or removed.\n\nThe body says nothing about which address is primary.  An address added to an empty set becomes\nthe designated one.\n\nRefused with `409` when the set already holds the maximum number of entries.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/phone-numbers": {
      "get": {
        "operationId": "getContactPhoneNumbers",
        "tags": [
          "Phone numbers"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactPhoneNumbersResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "List a contact's phone numbers",
        "description": "Returns every stored phone number for the contact, each with its identifier, in identifier order.\nThe set serves no validator: it is written only through its members, each at its own URL with a\nvalidator of its own.\n\nStored numbers are returned as stored.  Many predate this API's E.164 bar and will not satisfy\n`PhoneNumber`; such an entry does not stop a write to any other number, and replacing it at its own\nURL is how it is corrected.\n"
      },
      "post": {
        "operationId": "createContactPhoneNumber",
        "tags": [
          "Phone numbers"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactPhoneInput"
              }
            }
          },
          "description": "The phone number to add to the contact.\n"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/ContactPhoneCreated"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Add a phone number to a contact",
        "description": "Adds one number without stating anything about the ones already stored, and answers `201` with\nthe new number's address in `Location`.  The resource is readable there immediately.\n\nThe identifier is minted by this API, so `Location` is final rather than provisional.  There is no\n`If-Match`: a create asserts nothing about the set it joins, and the required `Idempotency-Key` is what\nmakes a retry safe.\n\nEntries you never touched are relayed as they were stored, so a stored number that predates this\nAPI's E.164 bar cannot refuse your write.  One the system of record will not store again does: the\nwrite is refused with `409`, and `detail` names the URL of each relayed number outside that bar, that\none among them, where each can be replaced or removed.\n\nThe body says nothing about which number is primary.  A number added to an empty set becomes\nthe designated one; added to a set that already has entries, it joins as an ordinary member.\n\nRefused with `409` when the set already holds the maximum number of entries.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/phone-numbers/{contactPhoneId}": {
      "get": {
        "operationId": "getContactPhoneNumber",
        "tags": [
          "Phone numbers"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactPhoneId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactPhoneResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Get one of a contact's phone numbers",
        "description": "Returns the one number the path names, with a validator of its own.\n\n`404` if the contact holds no entry with that identifier - a contact that does not exist and a\ncontact that does not hold this entry are the same answer.\n\n`isPrimary` is served here because it describes what is stored, and it is not part of what the\nvalidator covers: moving the designation does not stale a validator you hold for this number.\n"
      },
      "put": {
        "operationId": "updateContactPhoneNumber",
        "tags": [
          "Phone numbers"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactPhoneId"
          },
          {
            "$ref": "#/components/parameters/IfMatchRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactPhoneInput"
              }
            }
          },
          "description": "The phone number this entry holds from now on, stated whole.\n"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactPhoneResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Replace one of a contact's phone numbers",
        "description": "Replaces the one number the path names, leaving every other entry of the set exactly as it was\nstored.  `If-Match` is required, and the validator must be one this resource issued.\n\nThe body is total over this member and carries no designation.  Replacing a number does not move\nwhich entry is primary, and replacing the designated entry leaves it designated.\n\nEntries you did not send are relayed rather than re-validated, so a stored number this contract\ncould not describe does not refuse this write - unless the system of record will not store it\nagain, which is refused with `409` as the create is.\n"
      },
      "delete": {
        "operationId": "deleteContactPhoneNumber",
        "tags": [
          "Phone numbers"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactPhoneId"
          },
          {
            "$ref": "#/components/parameters/IfMatchRequired"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Remove one of a contact's phone numbers",
        "description": "Removes the one number the path names.  `If-Match` is required.\n\nRefused with `409` while the number is the designated primary and the contact holds another, because\na removal does not move the designation by implication.  `detail` names this number's designation\nroute: designate another number at its own, then retry.  The contact's only number can be removed,\nwhich leaves the set empty.  Refused with `409` over another stored number the system of record will\nnot store again, as the create is.\n\nThe maximum set length is not consulted, so a contact already over it can still be brought back\nunder.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/phone-numbers/{contactPhoneId}/primary": {
      "put": {
        "operationId": "updateContactPhoneNumberPrimary",
        "tags": [
          "Phone numbers"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactPhoneId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Make a phone number the contact's primary one",
        "description": "Makes the number the path names the contact's primary number, clears the designation from every\nother, and answers `204`.  There is no body and no `If-Match`: the request names the state it wants\nrather than one computed from a read.  An identifier the contact does not hold answers `404`.\n\nNo number's validator moves, because which entry is primary is not part of what it covers.  The\ndesignation relays every stored number, so it is refused with `409` over one the system of record\nwill not store again, as the create is.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/emails/{contactEmailId}": {
      "get": {
        "operationId": "getContactEmail",
        "tags": [
          "Email addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactEmailId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactEmailResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Get one of a contact's email addresses",
        "description": "Returns the one address the path names, with a validator of its own.  `404` if the contact\nholds no entry with that identifier.\n\nThe address is served as stored, which is trimmed and lower-cased: the system of record\nnormalises before storing, and this resource hashes the normalised form - so re-sending the\nsame address in a different case converges rather than rotating the validator.\n"
      },
      "put": {
        "operationId": "updateContactEmail",
        "tags": [
          "Email addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactEmailId"
          },
          {
            "$ref": "#/components/parameters/IfMatchRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactEmailInput"
              }
            }
          },
          "description": "The email address this entry holds from now on, stated whole.\n"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactEmailResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Replace one of a contact's email addresses",
        "description": "Replaces the one address the path names, leaving every other entry of the set as it was stored.\n`If-Match` is required.\n\nThe body is total over this member and carries no designation.  Replacing the designated address\nleaves it designated; it does not change which entry is primary.\n\nReplacing the address of the designated entry moves the contact's primary address with it.  On a\ncontact with portal access that is refused with `409` `primary_email_not_writable_here`: the\naddress its portal login uses is changed in Carepatron, not through the API.  Refused with `409`\n`unsupported_resource_state` over another stored\naddress the system of record will not store again, as the create is.\n"
      },
      "delete": {
        "operationId": "deleteContactEmail",
        "tags": [
          "Email addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactEmailId"
          },
          {
            "$ref": "#/components/parameters/IfMatchRequired"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Remove one of a contact's email addresses",
        "description": "Removes the one address the path names.  `If-Match` is required.\n\nRefused with `409` while the address is the contact's primary one and the contact holds another,\nbecause a removal does not move the designation by implication.  `detail` names this address's\ndesignation route: designate another address at its own, then retry.  On a contact with portal\naccess that designation is refused too, because the primary address is its login, which is changed\nin Carepatron.  The contact's only address can\nbe removed, which leaves the set empty - except on a contact with portal access, whose login it is:\nthat removal is refused with `409` `primary_email_not_writable_here`.  Refused with `409`\n`unsupported_resource_state` over another stored address the system of record will not store again,\nas the create is.\n\nThe maximum set length is not consulted, so a contact already over it can still be brought back\nunder.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/emails/{contactEmailId}/primary": {
      "put": {
        "operationId": "updateContactEmailPrimary",
        "tags": [
          "Email addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactEmailId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Make an email address the contact's primary one",
        "description": "Makes the address the path names the contact's primary address and answers `204`.  There is no body\nand no `If-Match`: the request names the state it wants rather than one computed from a read.  An\nidentifier the contact does not hold answers `404`.\n\nA contact with client portal access logs in with its primary email, so this operation answers `409`\nwith `portal_linked_primary_email` rather than move the login; change that contact's primary email in\nCarepatron, which also sends the contact a portal invitation.  A contact with no portal access is never\nmailed.\n\nThe stored address is what is sent, so one the system of record will not take is refused with `409`\nand a `detail` naming this address's URL, where it can be replaced first.  Because the route takes the\naddress rather than the entry, an address another of the contact's entries also holds is refused with\n`409` before anything is sent, and `detail` names that entry's URL: replace or remove it, then retry.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/addresses": {
      "get": {
        "operationId": "getContactAddresses",
        "tags": [
          "Addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactAddressesResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "List a contact's postal addresses",
        "description": "Returns every stored postal address for the contact, each with its identifier, in identifier order.\nThe set serves no validator: it is written only through its members, each at its own URL with a\nvalidator of its own.\n\nEvery part is present on every address, and null where the address has none.  An address stored\nbefore its parts were recorded separately is returned with its whole text as `streetAddress`.\n\nStored addresses are returned as stored and may not satisfy `PostalAddress` - a street line\nspanning two lines, a country spelled out.  Such an entry does not stop a write to any other\naddress, and replacing it at its own URL is how it is corrected.\n"
      },
      "post": {
        "operationId": "createContactAddress",
        "tags": [
          "Addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactAddressInput"
              }
            }
          },
          "description": "The postal address to add to the contact.\n"
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/ContactAddressCreated"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Add a postal address to a contact",
        "description": "Adds one address without stating anything about the ones already stored, and answers `201` with the\nnew address's URL in `Location`.  The resource is readable there immediately.\n\nThe identifier is minted by this API.  There is no `If-Match`: a create asserts nothing about the set\nit joins, and the required `Idempotency-Key` is what makes a retry safe.\n\nEntries you never touched are relayed as they were stored, so a stored address that does not satisfy\n`PostalAddress` cannot refuse your write.\n\nAn address added to an empty set becomes the designated one.  Refused with `409` when the set\nalready holds the maximum number of entries, and over a stored address the system of record will not\nstore again, which every write relaying it would carry back; `detail` names the URL of each relayed\naddress that does not satisfy `PostalAddress`, that one among them.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/addresses/{contactAddressId}": {
      "get": {
        "operationId": "getContactAddress",
        "tags": [
          "Addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactAddressId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactAddressResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Get one of a contact's postal addresses",
        "description": "Returns the one address the path names, with a validator of its own.  `404` if the contact holds no\nentry with that identifier.\n\n`isPrimary` is served here because it describes what is stored, and it is not part of what the\nvalidator covers: moving the designation does not stale a validator you hold for this address.\n"
      },
      "put": {
        "operationId": "updateContactAddress",
        "tags": [
          "Addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactAddressId"
          },
          {
            "$ref": "#/components/parameters/IfMatchRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactAddressInput"
              }
            }
          },
          "description": "The postal address this entry holds from now on, stated whole.\n"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactAddressResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Replace one of a contact's postal addresses",
        "description": "Replaces the one address the path names, leaving every other entry of the set as it was stored.\n`If-Match` is required.\n\nThe body is total over this address: a part you leave out is cleared, not kept.  It carries no\ndesignation.\n\nRefused with `409` over another stored address the system of record will not store again, as the\ncreate is; replacing that address itself is how it is repaired.\n"
      },
      "delete": {
        "operationId": "deleteContactAddress",
        "tags": [
          "Addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactAddressId"
          },
          {
            "$ref": "#/components/parameters/IfMatchRequired"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Remove one of a contact's postal addresses",
        "description": "Removes the one address the path names.  `If-Match` is required.\n\nRefused with `409` while the address is the designated primary and the contact holds another, because\na removal does not move the designation by implication.  `detail` names this address's designation\nroute: designate another address at its own, then retry.  The contact's only address can be removed,\nwhich leaves the set empty.  Refused with `409` over another stored address the system of record will\nnot store again, as the create is.\n\nThe maximum set length is not consulted.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/addresses/{contactAddressId}/primary": {
      "put": {
        "operationId": "updateContactAddressPrimary",
        "tags": [
          "Addresses"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/ContactAddressId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Make a postal address the contact's primary one",
        "description": "Makes the address the path names the contact's primary address, clears the designation from every\nother, and answers `204`.  There is no body and no `If-Match`: the request names the state it wants\nrather than one computed from a read.  An identifier the contact does not hold answers `404`.\n\nNo address's validator moves, because which entry is primary is not part of what it covers.  The\ndesignation relays every stored address, so it is refused with `409` over one the system of record\nwill not store again, as the create is.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/custom-fields": {
      "get": {
        "operationId": "getContactCustomFields",
        "tags": [
          "Custom fields"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactCustomFieldsResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Get a contact's custom field values",
        "description": "Returns one entry per field the workspace has defined and this version serves, whether the\ncontact has filled it in or not, plus the validator for the set.  A field the contact has no\nvalue for carries a null.\n\nEach entry carries the field's `type` as well as its value, because an option id and a line of\ntext are both strings on the wire.  The type belongs to the definition rather than to this\ncontact, so it is not part of the validator: a field retyped in the app does not refuse a write\nto every contact holding it.\n\nAn entry whose stored value cannot be read under its definition is left out rather than served\nas a null, which would say the field is empty when it is not.  The set cannot be written while\nit holds one: a body that leaves the field out is refused with `custom_field_missing`, like any\nother, and a body that states it with `409` rather than applied over a value the read never\nshowed you.  Set or clear that field through its own member first.\n"
      },
      "put": {
        "operationId": "updateContactCustomFields",
        "tags": [
          "Custom fields"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/IfMatchRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactCustomFieldsInput"
              }
            }
          },
          "description": "Every custom field the workspace defines, each with the value it holds from now on.\n"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactCustomFieldsResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Replace a contact's custom field values",
        "description": "REPLACES the whole set, so state every field the definitions list: its value, or `null` to\nclear it.  A field left out is refused with `custom_field_missing` rather than read as a clear.\nRead the set, edit what you read, and send it back whole.\n\n`If-Match` is required: it forces the read that shows you the values the write replaces.\n\nThe write is total over what this API serves and over nothing else: a field of a type this\nversion cannot represent is carried through exactly as stored.  A value left behind by a\ndefinition the workspace has since deleted is not - it is already absent from every read of the\ncontact, including the application's own, and a write does not restore it.\n\nEach value must be one its own definition can hold, and every violation is reported at once: a\nfield left out at `/customFields`, its `detail` naming the key, and a value by position -\n`/customFields/0/value`.  A key the workspace has not defined is refused with\n`custom_field_unknown`, and one it has defined with a type this version cannot represent with\n`custom_field_type_unsupported`; neither is silently skipped.\n\nIf the contact holds a value this API cannot read under its definition, the write is refused\nwith `409` rather than applied over it - the read never showed you that entry.  Set or clear it\nthrough its own member first.\n"
      }
    },
    "/providers/{providerId}/contacts/{contactId}/custom-fields/{key}": {
      "get": {
        "operationId": "getContactCustomField",
        "tags": [
          "Custom fields"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/CustomFieldKey"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactCustomFieldResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/ContactCustomFieldConflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Get one of a contact's custom field values",
        "description": "Returns one value with its own validator.  A member exists for every field the workspace has\ndefined, whether the contact has filled it in or not, so a first write can take a validator from\nhere without reading the whole set.\n\nA `200` carrying a null means the field is defined and empty.  A key the workspace has not\ndefined is `404`, and a key whose type this version cannot represent is `409`.\n\nA stored value that cannot be read under its definition is `409` rather than a null: the set\nleaves such an entry out, and a member has nothing to leave out.\n\nThe validator is this field's own.  It moves when and only when this field's value moves, so an\nedit to another of the contact's custom fields does not refuse a write here.\n"
      },
      "put": {
        "operationId": "updateContactCustomField",
        "tags": [
          "Custom fields"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/ContactId"
          },
          {
            "$ref": "#/components/parameters/CustomFieldKey"
          },
          {
            "$ref": "#/components/parameters/IfMatchRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactCustomFieldValueInput"
              }
            }
          },
          "description": "The value this custom field holds from now on, or null to clear it.\n"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactCustomFieldResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Set one of a contact's custom field values",
        "description": "Writes one value, stated absolutely.  Every other field on the contact is carried through as\nstored.\n\n`If-Match` is required, and the validator is this member's own rather than the set's.\n\nThe value must be one the definition can hold:\n\n- text within the definition's own length bounds, with no control character other than tab,\n  carriage return and line feed, no markup - a `\u003c` directly followed by a letter, `/`, `!` or `?` -\n  and not a date and time on its own, such as `2024-07-09T10:30:00Z`, which the field would store\n  as a date;\n- a date as an ISO 8601 instant that names its offset, kept to the second, so a fraction is\n  dropped;\n- a single choice as one of the definition's option ids;\n- multiple choice as a list of them, with at least one entry and no repeats.\n\nA field whose definition reports `allowsUnlistedValues` still takes only the ids that definition\nlists.\n\n`null` clears the field, and omitting `value` is refused with `400` rather than read as a clear.\nThe definition is never removed - a field belongs to the workspace and is managed in the\nCarepatron app - so a later read of a cleared member answers `200` with a null.\n"
      }
    },
    "/providers/{providerId}/contacts/export": {
      "post": {
        "operationId": "startContactsExport",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:export"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "$ref": "#/components/responses/ContactsExportJobAccepted"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Start contacts export",
        "description": "Initiates a contacts export job and returns 202 Accepted with the\nLocation header pointing at the status endpoint.  Poll until status is\nsucceeded or failed; on success the response body includes the result\nURL.\n"
      }
    },
    "/providers/{providerId}/contacts/export/jobs/{jobId}": {
      "get": {
        "operationId": "getContactsExportJob",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:export"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ContactsExportJobStatusResponse"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Get export job status",
        "description": "Polls for export job status.  Always returns 200 with a status monitor; while pending or processing, the Retry-After header suggests a polling interval.  When succeeded, the body includes the result URL; when failed, the body includes an error."
      },
      "delete": {
        "operationId": "deleteContactsExportJob",
        "tags": [
          "Contacts"
        ],
        "security": [
          {
            "apiKeyAuth": [
              "contacts:export"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          },
          {
            "$ref": "#/components/parameters/ProviderId"
          },
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          },
          "default": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "summary": "Delete export job",
        "description": "Cancels a non-terminal export job idempotently."
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "cp-api-key",
        "description": "API key authentication.\n\nPass your API key as a header.\nKeys use the format: cp_{mode}_{context}_{randomString}.\n\nExample: cp-api-key: cp_live_prv_4eC39HqLyjWDarjtT1zdp7dc\n\nScopes used by this API:\n- `contacts:export`\n- `contacts:read`\n- `contacts:write`\n"
      }
    },
    "schemas": {
      "ContactCustomFieldsInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "customFields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactCustomFieldInput"
            },
            "maxItems": 50,
            "example": [
              {
                "key": "PreferredPharmacy",
                "value": "harbour"
              }
            ]
          }
        },
        "required": [
          "customFields"
        ],
        "description": "The custom field values to store, whole: one entry for every field the definitions list.  A field\nleft out is refused rather than cleared; clear one by stating it with a null value.\n",
        "example": {
          "customFields": [
            {
              "key": "PreferredPharmacy",
              "value": "harbour"
            },
            {
              "key": "ReferralNotes",
              "value": "Referred for physiotherapy after a knee reconstruction."
            }
          ]
        }
      },
      "ContactCustomFieldValueInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "value": {
            "$ref": "#/components/schemas/CustomFieldValue"
          }
        },
        "required": [
          "value"
        ],
        "description": "The value this field should hold.  `null` clears the field, and omitting `value` is refused with\n`400` rather than read as a clear.\n",
        "example": {
          "value": "harbour"
        }
      },
      "ContactInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "personalDetails": {
            "$ref": "#/components/schemas/ContactPersonalDetailsInput"
          },
          "isClient": {
            "type": "boolean"
          },
          "emails": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactEmailEntryInput"
            },
            "maxItems": 10
          },
          "phoneNumbers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactPhoneEntryInput"
            },
            "maxItems": 10
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactAddressEntryInput"
            },
            "maxItems": 10
          },
          "customFields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactCustomFieldInput"
            },
            "maxItems": 50
          }
        },
        "required": [
          "personalDetails",
          "isClient",
          "emails",
          "phoneNumbers",
          "addresses",
          "customFields"
        ],
        "description": "A contact as it is created: its personal details, whether it is a client, its email addresses, phone\nnumbers and addresses, and the custom field values it starts with, grouped as the contact serves\nthem.  Every member is required, and a collection holding nothing is `[]`.  Deliberately closed:\nsending a property not listed here is rejected rather than ignored, so a field that does not exist\nyet cannot be silently dropped.\n",
        "example": {
          "personalDetails": {
            "firstName": "Ada",
            "lastName": "Lovelace",
            "dateOfBirth": "1815-12-10"
          },
          "isClient": true,
          "emails": [
            {
              "email": "ada@example.com",
              "type": "personal",
              "isPrimary": true
            }
          ],
          "phoneNumbers": [
            {
              "phoneNumber": "+64211234567",
              "type": "mobile",
              "isPrimary": true
            }
          ],
          "addresses": [],
          "customFields": []
        }
      },
      "ContactPersonalDetailsInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "firstName": {
            "$ref": "#/components/schemas/PersonName"
          },
          "lastName": {
            "$ref": "#/components/schemas/PersonName"
          },
          "dateOfBirth": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOfBirth"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "firstName",
          "lastName",
          "dateOfBirth"
        ],
        "description": "A contact's name and date of birth as a write states them, on a create and on the personal-details\nwrite alike.  All three are required: send `dateOfBirth` as null for a contact without one, which\non the personal-details write clears the stored date.\n",
        "example": {
          "firstName": "Ada",
          "lastName": "Lovelace",
          "dateOfBirth": null
        }
      },
      "ContactEmailInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "email": {
            "$ref": "#/components/schemas/EmailAddress"
          },
          "type": {
            "$ref": "#/components/schemas/ContactEmailType"
          }
        },
        "required": [
          "email",
          "type"
        ],
        "description": "One email address as you write it: the body of a create or a replace at the address's own URL.\n"
      },
      "ContactPhoneInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "phoneNumber": {
            "$ref": "#/components/schemas/PhoneNumber"
          },
          "type": {
            "$ref": "#/components/schemas/ContactPhoneType"
          }
        },
        "required": [
          "phoneNumber",
          "type"
        ],
        "description": "One phone number as you write it: the body of a create or a replace at the number's own URL.\n"
      },
      "ContactAddressInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "address": {
            "$ref": "#/components/schemas/PostalAddress"
          },
          "type": {
            "$ref": "#/components/schemas/ContactAddressType"
          }
        },
        "required": [
          "address",
          "type"
        ],
        "description": "One postal address as you write it: the body of a create or a replace at the address's own URL.\n\nAn empty `city`, `district` or `state` is stored as absent; an empty `postalCode` or `country` is\nrefused, so send `null` or leave it out.  A `country` the standard does not assign - a reserved code\nsuch as `UK`, or `ZZ` - is refused with the code `country_unknown`.\n",
        "example": {
          "address": {
            "streetAddress": "12 Ash Street",
            "city": "Wellington",
            "postalCode": "6011",
            "country": "NZ"
          },
          "type": "residential"
        }
      },
      "ContactCustomFieldInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "key": {
            "$ref": "#/components/schemas/CustomFieldKey"
          },
          "value": {
            "$ref": "#/components/schemas/CustomFieldValue"
          }
        },
        "required": [
          "key",
          "value"
        ],
        "description": "One field's value as you send it.  No `type` - it belongs to the definition, and accepting one\nhere would let a caller assert a field is something it is not.  `null` clears the field, and\nomitting `value` is refused with `400` rather than read as a clear.\n",
        "example": {
          "key": "PreferredPharmacy",
          "value": "harbour"
        }
      },
      "CustomFieldValue": {
        "type": [
          "string",
          "boolean",
          "array",
          "null"
        ],
        "maxLength": 20000,
        "items": {
          "type": "string",
          "maxLength": 256
        },
        "maxItems": 5000,
        "description": "One custom field's value, in the same shape wherever it appears.  Which shape applies is decided by\nthe field's own `type`, which this schema cannot see: text and long text are a string, boolean a\nboolean, date an ISO 8601 instant naming its offset, single choice one of the field's option ids,\nand multiple choice a list of them, at most 5,000 long; a longer list is refused whole with\n`max_items`.  A write refuses text that is a date and time on its own, such\nas `2024-07-09T10:30:00Z`, with `custom_field_value_invalid`, because the field would store it as a\ndate.  A null means the field is empty.  A date is kept to the\nsecond, so a fraction written with one is dropped.  A date stored as text in any form but an ISO 8601\ndate and time is served as that text, because the order its day and month were written in is not\nstored with it.  Length bounds count UTF-16 code units, so a character outside the Basic\nMultilingual Plane counts as two.\n\nA value is data that integrations and people using the Carepatron app both write, so encode it for\nwhatever you output it into.  A write through this API refuses text holding markup - a `\u003c` directly\nfollowed by a letter, `/`, `!` or `?` - with `custom_field_value_invalid`, but a read neither strips\nnor encodes what is stored, whoever wrote it.\n"
      },
      "EntityTag": {
        "type": "string",
        "pattern": "^\"[0-9a-fA-F]{64}\"$",
        "maxLength": 66,
        "description": "A strong entity tag: a hex SHA-256 over the resource's writable content, in double quotes.\nCompare it byte for byte and pass it back unchanged - both hex cases are accepted, so do not\nnormalise it.\n",
        "example": "\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\""
      },
      "ProviderId": {
        "type": "string",
        "pattern": "^prv-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
        "maxLength": 53,
        "description": "Prefixed identifier of the provider (practice) that owns the resource.  Always the value of\nthe `providerId` path segment, and always scoped to the API key making the request - a key\ncannot address another provider's data.\n",
        "example": "prv-9f6c1a2b-3d4e-4567-89ab-cdef01234567"
      },
      "ContactId": {
        "type": "string",
        "pattern": "^cont-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
        "maxLength": 53,
        "description": "Unique contact identifier.",
        "example": "cont-3f2504e0-4f89-11d3-9a0c-0305e82c3301"
      },
      "ContactEmailId": {
        "type": "string",
        "pattern": "^cemail-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
        "maxLength": 53,
        "description": "Unique identifier of one of a contact's email addresses.",
        "example": "cemail-3f2504e0-4f89-11d3-9a0c-0305e82c3301"
      },
      "ContactPhoneId": {
        "type": "string",
        "pattern": "^cphone-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
        "maxLength": 53,
        "description": "Unique identifier of one of a contact's phone numbers.",
        "example": "cphone-3f2504e0-4f89-11d3-9a0c-0305e82c3301"
      },
      "ContactAddressId": {
        "type": "string",
        "pattern": "^caddr-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
        "maxLength": 53,
        "description": "Unique identifier of one of a contact's postal addresses.",
        "example": "caddr-3f2504e0-4f89-11d3-9a0c-0305e82c3301"
      },
      "CustomFieldKey": {
        "type": "string",
        "minLength": 1,
        "maxLength": 256,
        "description": "The key a workspace gave one of its own fields.  Chosen in the Carepatron app rather than minted\nhere, so it carries no prefix and no fixed shape; treat it as opaque.\n",
        "example": "PreferredPharmacy"
      },
      "JobId": {
        "type": "string",
        "pattern": "^job-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
        "maxLength": 53,
        "description": "Prefixed identifier of an asynchronous job.  Returned in the `202 Accepted` body when the job\nis started, and used to poll the job-status endpoint until it reaches a terminal state.\n",
        "example": "job-9f6c1a2b-3d4e-4567-89ab-cdef01234567"
      },
      "PaginationLimit": {
        "type": "integer",
        "format": "int32",
        "minimum": 1,
        "maximum": 100,
        "default": 20,
        "description": "Maximum items per page, between 1 and 100 inclusive.  20 when not specified.\n",
        "example": 20
      },
      "PaginationCursorParameter": {
        "type": "string",
        "maxLength": 500,
        "description": "An opaque position token as SENT by a caller.  Pass back the previous response's `nextCursor`\nunchanged.\n",
        "example": "eyJDIjoiMjAyNC0wMS0wMVQxMDowMDowMC4wMDAwMDAwWiIsIkkiOiJjb250LTNmMjUwNGUwLTRmODktNDFkMy05YTBjLTAzMDVlODJjMzMwMSJ9"
      },
      "ProblemDetails": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "example": "https://developer.carepatron.com/docs/errors#internal"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "example": "Internal server error"
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "minimum": 100,
            "maximum": 599,
            "example": 500
          },
          "detail": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1024,
            "example": "Retry with backoff.  If the error persists, contact support with the instance value from the response."
          },
          "instance": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "example": "urn:trace:0af7651916cd43dd8448eb211c80319c"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "example": "2026-01-15T10:30:00.0000000Z"
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "minItems": 1,
            "maxItems": 100,
            "description": "Every constraint the request body broke, one item per violation, present only on\n`request_validation_failed`.  A caller fixes the whole body from one response.\n",
            "example": [
              {
                "pointer": "/firstName",
                "code": "pattern",
                "detail": "Must match the pattern the schema declares."
              },
              {
                "pointer": "/lastName",
                "code": "max_length",
                "detail": "Character count must be at most 100."
              }
            ]
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "instance",
          "timestamp"
        ],
        "description": "Error response following RFC 9457 (Problem Details for HTTP APIs), served as\n`application/problem+json`.  Returned as the body of every 4xx/5xx response.  Includes\nextension members (`timestamp`, and `errors` on `request_validation_failed`) alongside the\nstandard RFC 9457 members.\n",
        "example": {
          "type": "https://developer.carepatron.com/docs/errors#internal",
          "title": "Internal server error",
          "status": 500,
          "detail": "Retry with backoff.  If the error persists, contact support with the instance value from the response.",
          "instance": "urn:trace:0af7651916cd43dd8448eb211c80319c",
          "timestamp": "2026-01-15T10:30:00.0000000Z"
        }
      },
      "ContactsListResponse": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contact"
            },
            "maxItems": 100
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        },
        "required": [
          "data",
          "pagination"
        ],
        "description": "One page of contacts, with the state needed to ask for the next.",
        "example": {
          "data": [
            {
              "contactId": "cont-3f2504e0-4f89-11d3-9a0c-0305e82c3301",
              "personalDetails": {
                "firstName": "Ada",
                "lastName": "Lovelace",
                "dateOfBirth": "1815-12-10"
              },
              "createdAtUtc": "2024-01-01T10:00:00Z",
              "archived": false,
              "isClient": true
            }
          ],
          "pagination": {
            "limit": 20,
            "nextCursor": "eyJDIjoiMjAyNC0wMS0wMVQxMDowMDowMC4wMDAwMDAwWiIsIkkiOiJjb250LTNmMjUwNGUwLTRmODktNDFkMy05YTBjLTAzMDVlODJjMzMwMSJ9"
          }
        }
      },
      "Contact": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "contactId": {
            "$ref": "#/components/schemas/ContactId"
          },
          "personalDetails": {
            "$ref": "#/components/schemas/ContactPersonalDetails"
          },
          "createdAtUtc": {
            "$ref": "#/components/schemas/DateTime"
          },
          "archived": {
            "type": "boolean",
            "description": "Whether the contact is archived.  An archived contact is still read by id, and is listed only with\n`includeArchived=true`.\n"
          },
          "isClient": {
            "type": "boolean",
            "description": "Whether the contact is one of the workspace's clients rather than another of its contacts.\n"
          },
          "customFields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactCustomField"
            },
            "maxItems": 50,
            "description": "The contact's custom field values, when the response carries them.  A read carries them only when it\nasked for them with `include=customFields`, one entry for each field the workspace defines and this\nversion serves.  A value that cannot be read under its field's type is left out.\n"
          },
          "emails": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactEmail"
            },
            "maxItems": 10,
            "description": "The contact's email addresses in identifier order, when the response carries them.  A read carries\nthem only when it asked for them with `include`.  An address stored before this API's format bar\nexisted is returned as stored, and may not satisfy `EmailAddress`.  Reads are served from a replica,\nso a read soon after the addresses change may briefly serve them as they were.\n"
          },
          "phoneNumbers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactPhone"
            },
            "maxItems": 10,
            "description": "The contact's phone numbers in identifier order, when the response carries them.  A read carries\nthem only when it asked for them with `include`.  Stored numbers are returned as stored, and many\npredate this API's E.164 bar and will not satisfy `PhoneNumber`.  Reads are served from a replica, so\na read soon after the numbers change may briefly serve them as they were.\n"
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactAddress"
            },
            "maxItems": 10,
            "description": "The contact's postal addresses in identifier order, when the response carries them.  A read carries\nthem only when it asked for them with `include`.  Stored addresses are returned as stored, and may not\nsatisfy `PostalAddress`.  Reads are served from a replica, so a read soon after the addresses change\nmay briefly serve them as they were.\n"
          }
        },
        "required": [
          "contactId",
          "personalDetails",
          "createdAtUtc",
          "archived",
          "isClient"
        ],
        "description": "A contact as this API serves it.\n",
        "example": {
          "contactId": "cont-3f2504e0-4f89-11d3-9a0c-0305e82c3301",
          "personalDetails": {
            "firstName": "Ada",
            "lastName": "Lovelace",
            "dateOfBirth": "1815-12-10"
          },
          "createdAtUtc": "2024-01-01T10:00:00Z",
          "archived": false,
          "isClient": true
        }
      },
      "ContactPersonalDetails": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "firstName": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PersonName"
              },
              {
                "type": "null"
              }
            ]
          },
          "lastName": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PersonName"
              },
              {
                "type": "null"
              }
            ]
          },
          "dateOfBirth": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateOfBirth"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "firstName",
          "lastName",
          "dateOfBirth"
        ],
        "description": "A contact's name and date of birth as stored.  All three are always present, and each is\nnullable because a contact can be stored without it.\n",
        "example": {
          "firstName": "Ada",
          "lastName": "Lovelace",
          "dateOfBirth": "1815-12-10"
        }
      },
      "ContactEmails": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "emails": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactEmail"
            },
            "maxItems": 10
          }
        },
        "required": [
          "emails"
        ],
        "description": "A contact's email addresses, whole, in identifier order.",
        "example": {
          "emails": [
            {
              "contactEmailId": "cemail-3f2504e0-4f89-11d3-9a0c-0305e82c3301",
              "email": "ada@example.com",
              "type": "personal",
              "isPrimary": true
            },
            {
              "contactEmailId": "cemail-9b2e7c1a-5d3f-4e8b-a1c2-6f0d4e8b9a7c",
              "email": "ada@work.example.com",
              "type": "work",
              "isPrimary": false
            }
          ]
        }
      },
      "ContactPhoneNumbers": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "phoneNumbers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactPhone"
            },
            "maxItems": 10
          }
        },
        "required": [
          "phoneNumbers"
        ],
        "description": "A contact's phone numbers, whole, in identifier order.",
        "example": {
          "phoneNumbers": [
            {
              "contactPhoneId": "cphone-3f2504e0-4f89-11d3-9a0c-0305e82c3301",
              "phoneNumber": "+64211234567",
              "type": "mobile",
              "isPrimary": true
            }
          ]
        }
      },
      "ContactEmail": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "contactEmailId": {
            "$ref": "#/components/schemas/ContactEmailId"
          },
          "email": {
            "$ref": "#/components/schemas/EmailAddress"
          },
          "type": {
            "$ref": "#/components/schemas/ContactEmailType"
          },
          "isPrimary": {
            "type": "boolean"
          }
        },
        "required": [
          "contactEmailId",
          "email",
          "type",
          "isPrimary"
        ],
        "description": "One stored email address.  `contactEmailId` is always present on a read, and identifies the entry.\n",
        "example": {
          "contactEmailId": "cemail-3f2504e0-4f89-11d3-9a0c-0305e82c3301",
          "email": "ada@example.com",
          "type": "personal",
          "isPrimary": true
        }
      },
      "ContactPhone": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "contactPhoneId": {
            "$ref": "#/components/schemas/ContactPhoneId"
          },
          "phoneNumber": {
            "$ref": "#/components/schemas/PhoneNumber"
          },
          "type": {
            "$ref": "#/components/schemas/ContactPhoneType"
          },
          "isPrimary": {
            "type": "boolean"
          }
        },
        "required": [
          "contactPhoneId",
          "phoneNumber",
          "type",
          "isPrimary"
        ],
        "description": "One stored phone number.  `contactPhoneId` is always present on a read, and identifies the entry.\n",
        "example": {
          "contactPhoneId": "cphone-3f2504e0-4f89-11d3-9a0c-0305e82c3301",
          "phoneNumber": "+64211234567",
          "type": "mobile",
          "isPrimary": true
        }
      },
      "ContactAddresses": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactAddress"
            },
            "maxItems": 10
          }
        },
        "required": [
          "addresses"
        ],
        "description": "A contact's postal addresses, whole, in identifier order.",
        "example": {
          "addresses": [
            {
              "contactAddressId": "caddr-3f2504e0-4f89-11d3-9a0c-0305e82c3301",
              "address": {
                "streetAddress": "12 Ash Street",
                "city": "Wellington",
                "district": null,
                "state": null,
                "postalCode": "6011",
                "country": "NZ"
              },
              "type": "residential",
              "isPrimary": true
            }
          ]
        }
      },
      "ContactAddress": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "contactAddressId": {
            "$ref": "#/components/schemas/ContactAddressId"
          },
          "address": {
            "$ref": "#/components/schemas/PostalAddress"
          },
          "type": {
            "$ref": "#/components/schemas/ContactAddressType"
          },
          "isPrimary": {
            "type": "boolean"
          }
        },
        "required": [
          "contactAddressId",
          "address",
          "type",
          "isPrimary"
        ],
        "description": "One stored postal address.  `contactAddressId` is always present on a read, and identifies the entry.\n",
        "example": {
          "contactAddressId": "caddr-3f2504e0-4f89-11d3-9a0c-0305e82c3301",
          "address": {
            "streetAddress": "12 Ash Street",
            "city": "Wellington",
            "district": null,
            "state": null,
            "postalCode": "6011",
            "country": "NZ"
          },
          "type": "residential",
          "isPrimary": true
        }
      },
      "ContactCustomFieldDefinitions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "customFields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactCustomFieldDefinition"
            },
            "maxItems": 50
          }
        },
        "required": [
          "customFields"
        ],
        "description": "A workspace's contact custom field definitions, whole, in key order.",
        "example": {
          "customFields": [
            {
              "key": "PreferredPharmacy",
              "displayName": "Preferred pharmacy",
              "type": "single-choice",
              "options": [
                {
                  "id": "central",
                  "label": "Central Pharmacy"
                },
                {
                  "id": "harbour",
                  "label": "Harbour Pharmacy"
                }
              ]
            },
            {
              "key": "ReferralNotes",
              "displayName": "Referral notes",
              "type": "long-text",
              "minLength": 1,
              "maxLength": 20000
            }
          ]
        }
      },
      "ContactCustomFieldDefinition": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "key": {
            "$ref": "#/components/schemas/CustomFieldKey"
          },
          "displayName": {
            "type": "string",
            "maxLength": 1000
          },
          "type": {
            "$ref": "#/components/schemas/CustomFieldType"
          },
          "minLength": {
            "type": "integer",
            "format": "int32",
            "minimum": 1,
            "maximum": 20000
          },
          "maxLength": {
            "type": "integer",
            "format": "int32",
            "minimum": 1,
            "maximum": 20000
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomFieldOption"
            },
            "maxItems": 5000
          },
          "allowsUnlistedValues": {
            "type": "boolean",
            "description": "Whether the Carepatron app lets this field hold a choice that is not one of its listed\n`options`.  Present only when true.\n\nThis version of the API accepts listed option ids only, so a value like that can be read back\nfrom a contact but not written to one - which is the reason the member is published at all.  A\nwrite naming an unlisted value is refused with `400` and the code `custom_field_option_unknown`,\nwhether or not the field allows them.\n"
          }
        },
        "required": [
          "key",
          "displayName",
          "type"
        ],
        "description": "One custom field a workspace has defined on its contacts.  Four members are conditional:\n`minLength` and `maxLength` appear only for a field that holds text, `options` only for a choice\nfield, and `allowsUnlistedValues` only where it is true.\n",
        "example": {
          "key": "PreferredPharmacy",
          "displayName": "Preferred pharmacy",
          "type": "single-choice",
          "options": [
            {
              "id": "central",
              "label": "Central Pharmacy"
            },
            {
              "id": "harbour",
              "label": "Harbour Pharmacy"
            }
          ]
        }
      },
      "ContactCustomFields": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "customFields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactCustomField"
            },
            "maxItems": 50
          }
        },
        "required": [
          "customFields"
        ],
        "description": "A contact's custom field values, whole, one entry per field the workspace has defined and this\nversion serves.\n",
        "example": {
          "customFields": [
            {
              "key": "PreferredPharmacy",
              "type": "single-choice",
              "value": "central"
            },
            {
              "key": "ReferralNotes",
              "type": "long-text",
              "value": null
            }
          ]
        }
      },
      "ContactCustomField": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "key": {
            "$ref": "#/components/schemas/CustomFieldKey"
          },
          "type": {
            "$ref": "#/components/schemas/CustomFieldType"
          },
          "value": {
            "$ref": "#/components/schemas/CustomFieldValue"
          }
        },
        "required": [
          "key",
          "type",
          "value"
        ],
        "description": "One field's value on one contact, with the type the field's definition gives it.\n",
        "example": {
          "key": "PreferredPharmacy",
          "type": "single-choice",
          "value": "central"
        }
      },
      "ExportJob": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "jobId": {
            "$ref": "#/components/schemas/JobId"
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus"
          },
          "createdAtUtc": {
            "$ref": "#/components/schemas/DateTime"
          },
          "completedAtUtc": {
            "$ref": "#/components/schemas/DateTime"
          },
          "error": {
            "$ref": "#/components/schemas/Error"
          },
          "result": {
            "$ref": "#/components/schemas/ExportResult"
          }
        },
        "required": [
          "jobId",
          "status",
          "createdAtUtc"
        ],
        "description": "Export job status monitor.\nAlways returned with 200 from GET job. When status is succeeded, result\ncontains resultUrl; GET that URL to fetch the export.\n",
        "example": {
          "jobId": "job-9f6c1a2b-3d4e-4567-89ab-cdef01234567",
          "status": "succeeded",
          "createdAtUtc": "2026-01-15T10:30:00Z",
          "completedAtUtc": "2026-01-15T10:31:00Z",
          "result": {
            "resultUrl": "https://api.carepatron.com/019df4bf-3beb-74de-be05-f92eb9f89ca1"
          }
        }
      },
      "RateLimit": {
        "type": "integer",
        "format": "int32",
        "minimum": 0,
        "maximum": 2147483647,
        "description": "Rate limit value",
        "example": 1000
      },
      "ExportedFirstName": {
        "type": "string",
        "minLength": 0,
        "maxLength": 100,
        "description": "A contact's first name as stored.",
        "example": "John"
      },
      "ExportedLastName": {
        "type": "string",
        "minLength": 0,
        "maxLength": 100,
        "description": "A contact's last name as stored.",
        "example": "Doe"
      },
      "ExportedEmailAddress": {
        "type": "string",
        "minLength": 0,
        "maxLength": 320,
        "description": "An email address as stored.",
        "example": "ada@example.com"
      },
      "ExportedPhoneNumber": {
        "type": "string",
        "minLength": 0,
        "maxLength": 50,
        "description": "A phone number as stored.",
        "example": "+64211234567"
      },
      "DateTime": {
        "type": "string",
        "format": "date-time",
        "maxLength": 33,
        "description": "ISO 8601 date-time timestamp",
        "example": "2024-01-01T10:00:00Z"
      },
      "PersonName": {
        "type": "string",
        "minLength": 1,
        "maxLength": 100,
        "pattern": "^[\\p{L}\\p{M}\\p{N}\\s'’.\\-\u0026,]+$",
        "description": "One of a contact's names.  Letters, marks and digits from any script, spaces, apostrophes, periods,\nhyphens, ampersands and commas.  A letter outside the Basic Multilingual Plane is refused, and the\nlength bounds count UTF-16 code units.\n",
        "example": "Ada"
      },
      "DateOfBirth": {
        "type": "string",
        "format": "date",
        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
        "maxLength": 10,
        "description": "A date of birth as a calendar date, carrying no time and no time zone.",
        "example": "1815-12-10"
      },
      "ContactEmailEntryInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "email": {
            "$ref": "#/components/schemas/EmailAddress"
          },
          "type": {
            "$ref": "#/components/schemas/ContactEmailType"
          },
          "isPrimary": {
            "type": "boolean"
          }
        },
        "required": [
          "email",
          "type",
          "isPrimary"
        ],
        "description": "One email address as a contact create states it: the address's write body, and whether it is the\ncontact's primary address.  Exactly one entry of a non-empty `emails` carries `isPrimary: true`.\n"
      },
      "ContactPhoneEntryInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "phoneNumber": {
            "$ref": "#/components/schemas/PhoneNumber"
          },
          "type": {
            "$ref": "#/components/schemas/ContactPhoneType"
          },
          "isPrimary": {
            "type": "boolean"
          }
        },
        "required": [
          "phoneNumber",
          "type",
          "isPrimary"
        ],
        "description": "One phone number as a contact create states it: the number's write body, and whether it is the\ncontact's primary number.  Exactly one entry of a non-empty `phoneNumbers` carries `isPrimary: true`.\n"
      },
      "ContactAddressEntryInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "address": {
            "$ref": "#/components/schemas/PostalAddress"
          },
          "type": {
            "$ref": "#/components/schemas/ContactAddressType"
          },
          "isPrimary": {
            "type": "boolean"
          }
        },
        "required": [
          "address",
          "type",
          "isPrimary"
        ],
        "description": "One postal address as a contact create states it: the address's write body, and whether it is the\ncontact's primary address.  Exactly one entry of a non-empty `addresses` carries `isPrimary: true`.\n"
      },
      "EmailAddress": {
        "type": "string",
        "pattern": "^[A-Za-z0-9!#$%\u0026'*+/=?^_`{|}~-]+(?:\\.[A-Za-z0-9!#$%\u0026'*+/=?^_`{|}~-]+)*@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$",
        "maxLength": 128,
        "description": "An email address.  ASCII only, at a dotted domain, at most 128 characters.\n",
        "example": "ada@example.com"
      },
      "ContactEmailType": {
        "type": "string",
        "enum": [
          "unknown",
          "personal",
          "work",
          "school",
          "others"
        ],
        "description": "How a contact classifies one of its email addresses.",
        "example": "personal"
      },
      "PhoneNumber": {
        "type": "string",
        "pattern": "^\\+[1-9]\\d{1,14}(?:;ext=\\d{1,20})?$",
        "maxLength": 41,
        "description": "A phone number.  E.164, optionally with an RFC 3966 extension.\n",
        "example": "+6495551234;ext=12"
      },
      "ContactPhoneType": {
        "type": "string",
        "enum": [
          "unknown",
          "mobile",
          "home",
          "work",
          "school",
          "fax"
        ],
        "description": "How a contact classifies one of its phone numbers.",
        "example": "mobile"
      },
      "PostalAddress": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "streetAddress": {
            "type": "string",
            "maxLength": 255,
            "pattern": "^(?:[^\\S\\u0000-\\u001F\\u007F]*(?:[^\\s\u003c\\u0000-\\u001F\\u007F]|\u003c+[^\u003cA-Za-z/!?\\u0000-\\u001F\\u007F])(?:[^\u003c\\u0000-\\u001F\\u007F]|\u003c+[^\u003cA-Za-z/!?\\u0000-\\u001F\\u007F])*\u003c*|[^\\S\\u0000-\\u001F\\u007F]*\u003c+)$",
            "description": "The street line: one line, holding more than whitespace.  A second line of an address belongs in\nthe same string.\n"
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "pattern": "^(?:[^\u003c\\u0000-\\u001F\\u007F]|\u003c+[^\u003cA-Za-z/!?\\u0000-\\u001F\\u007F])*\u003c*$"
          },
          "district": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "pattern": "^(?:[^\u003c\\u0000-\\u001F\\u007F]|\u003c+[^\u003cA-Za-z/!?\\u0000-\\u001F\\u007F])*\u003c*$",
            "description": "A county or equivalent subdivision - FHIR's `district`."
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "pattern": "^(?:[^\u003c\\u0000-\\u001F\\u007F]|\u003c+[^\u003cA-Za-z/!?\\u0000-\\u001F\\u007F])*\u003c*$"
          },
          "postalCode": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 32,
            "pattern": "^[A-Za-z0-9](?:[A-Za-z0-9 -]*[A-Za-z0-9])?$",
            "description": "Letters and digits, with spaces and hyphens only between them.  Checked for shape, never against the\ncountry.\n"
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2,
            "pattern": "^[A-Z]{2}$",
            "description": "An ISO 3166-1 alpha-2 code, in upper case.\n",
            "example": "NZ"
          }
        },
        "required": [
          "streetAddress"
        ],
        "description": "A postal address.  `streetAddress` is required and every other part is optional; a read returns\nevery part, null where the address has none.  The street line, city, district and state each hold\nno C0 control character, so a tab or line feed is refused though a Unicode line or paragraph\nseparator is not, and no markup: no `\u003c` directly followed by a letter, `/`, `!` or `?`.  Length\nbounds count UTF-16 code units, so a character outside the Basic Multilingual Plane counts as two.\n\nAn address is data that integrations and people using the Carepatron app both write, and a read\nreturns it as stored, so encode every part for whatever you output it into.\n"
      },
      "ContactAddressType": {
        "type": "string",
        "enum": [
          "unknown",
          "residential",
          "billing",
          "work",
          "others"
        ],
        "description": "How a contact classifies one of its postal addresses.",
        "example": "residential"
      },
      "CustomFieldType": {
        "type": "string",
        "enum": [
          "text",
          "long-text",
          "boolean",
          "date",
          "single-choice",
          "multiple-choice"
        ],
        "description": "What a field holds, which decides the shape its value takes.  Only the types this version serves\nare listed, and a field of any other type is never served.\n",
        "example": "single-choice"
      },
      "CustomFieldOption": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 256
          },
          "label": {
            "type": "string",
            "maxLength": 1000
          }
        },
        "required": [
          "id",
          "label"
        ],
        "description": "One choice a choice field offers.  `id` is what a value carries; `label` is what the app shows\nand is presentation only.\n"
      },
      "Pagination": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "limit": {
            "$ref": "#/components/schemas/PaginationLimit"
          },
          "nextCursor": {
            "$ref": "#/components/schemas/PaginationCursorToken"
          }
        },
        "required": [
          "limit",
          "nextCursor"
        ],
        "description": "The pagination state of the page just returned.",
        "example": {
          "limit": 20,
          "nextCursor": "eyJDIjoiMjAyNC0wMS0wMVQxMDowMDowMC4wMDAwMDAwWiIsIkkiOiJjb250LTNmMjUwNGUwLTRmODktNDFkMy05YTBjLTAzMDVlODJjMzMwMSJ9"
        }
      },
      "PaginationCursorToken": {
        "type": [
          "string",
          "null"
        ],
        "pattern": "^[A-Za-z0-9+/=_-]+$",
        "maxLength": 500,
        "description": "An opaque position token as ISSUED by this API, or null on the last page.\n",
        "example": "eyJDIjoiMjAyNC0wMS0wMVQxMDowMDowMC4wMDAwMDAwWiIsIkkiOiJjb250LTNmMjUwNGUwLTRmODktNDFkMy05YTBjLTAzMDVlODJjMzMwMSJ9"
      },
      "JobStatus": {
        "type": "string",
        "enum": [
          "pending",
          "processing",
          "succeeded",
          "failed",
          "canceled"
        ],
        "description": "Current status of the job",
        "example": "pending"
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "code": {
            "$ref": "#/components/schemas/ErrorCode"
          },
          "message": {
            "$ref": "#/components/schemas/ErrorMessage"
          },
          "details": {
            "$ref": "#/components/schemas/ErrorDetails"
          }
        },
        "required": [
          "code",
          "message"
        ],
        "description": "Structured error object embedded in a resource body (for example the `error` on a\nfailed export job).  Distinct from `ProblemDetails`, which is the body of a 4xx/5xx\nHTTP response.\n",
        "example": {
          "code": "EXPORT_FAILED",
          "message": "The export could not be completed."
        }
      },
      "ExportResult": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "resultUrl": {
            "type": "string",
            "format": "uri-reference",
            "maxLength": 2048,
            "x-content-schema": "#/components/schemas/ContactsExportResultBody"
          }
        },
        "required": [
          "resultUrl"
        ],
        "description": "Result when export job has succeeded.\nGET resultUrl to fetch the export.\n",
        "example": {
          "resultUrl": "https://api.carepatron.com/019df4bf-3beb-74de-be05-f92eb9f89ca1"
        }
      },
      "ContactExportRecord": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "contactId": {
            "$ref": "#/components/schemas/ContactId"
          },
          "firstName": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ExportedFirstName"
              },
              {
                "type": "null"
              }
            ]
          },
          "lastName": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ExportedLastName"
              },
              {
                "type": "null"
              }
            ]
          },
          "email": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ExportedEmailAddress"
              },
              {
                "type": "null"
              }
            ]
          },
          "phone": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ExportedPhoneNumber"
              },
              {
                "type": "null"
              }
            ],
            "description": "The contact's primary phone number as stored, or null when it has none."
          },
          "archived": {
            "type": "boolean",
            "description": "Whether the contact is archived.\n"
          },
          "isClient": {
            "type": "boolean",
            "description": "Whether the contact is one of the workspace's clients rather than another of its contacts."
          },
          "customFields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactCustomField"
            },
            "maxItems": 50,
            "description": "The contact's custom fields that hold a value, each with its key, the type its definition gives it\nand its value, and `[]` when none do.  A date stored as text in any form but an ISO 8601 date and\ntime is served as that text, and any other value that cannot be read under its field's type is left\nout.\nThe result of a workspace whose custom fields exceed a bound this API publishes - more than 50 served fields, a key over 256 characters, a display name or option label over 1,000 characters, or a choice field with more than 5,000 options - is refused\nwith `409` and the code `unsupported_resource_state` rather than served without them.\n"
          },
          "createdAtUtc": {
            "$ref": "#/components/schemas/DateTime"
          }
        },
        "required": [
          "contactId",
          "firstName",
          "lastName",
          "email",
          "phone",
          "archived",
          "isClient",
          "customFields",
          "createdAtUtc"
        ],
        "description": "A contact record in an export payload."
      },
      "ExportMetadata": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "provenance": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "exportedAtUtc": {
                "$ref": "#/components/schemas/DateTime"
              },
              "jobId": {
                "$ref": "#/components/schemas/JobId"
              },
              "actor": {
                "$ref": "#/components/schemas/Actor"
              }
            },
            "required": [
              "exportedAtUtc",
              "jobId",
              "actor"
            ]
          },
          "recordCount": {
            "type": "integer",
            "format": "int32",
            "minimum": 0,
            "maximum": 10000
          }
        },
        "required": [
          "provenance",
          "recordCount"
        ],
        "description": "Metadata for export responses."
      },
      "ContactsExportResultBody": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactExportRecord"
            },
            "maxItems": 10000,
            "description": "The workspace's active and archived contacts, each flagged by its record's `archived`.  Trashed\ncontacts are never exported.\n"
          },
          "metadata": {
            "$ref": "#/components/schemas/ExportMetadata"
          }
        },
        "required": [
          "data",
          "metadata"
        ],
        "description": "Body returned when a contact GETs the opaque `resultUrl` from a succeeded contacts\nexport job.\n\nThis schema is documented for client convenience but the URL itself is intentionally\nabsent from the path list - clients must follow the URL the server hands back rather\nthan constructing it.  See `ExportJob.result.resultUrl`.\n"
      },
      "Actor": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "app": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "appId": {
                "type": "string",
                "pattern": "^app-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
                "maxLength": 53
              }
            },
            "required": [
              "appId"
            ]
          }
        },
        "required": [
          "app"
        ],
        "description": "The actor who performed the request."
      },
      "ErrorCode": {
        "type": "string",
        "pattern": "^[A-Z][A-Z0-9_]*$",
        "maxLength": 50,
        "description": "Machine-readable error code",
        "example": "INVALID_INPUT"
      },
      "ErrorMessage": {
        "type": "string",
        "minLength": 1,
        "maxLength": 500,
        "description": "Human-readable error message",
        "example": "The request contains invalid data"
      },
      "ErrorDetails": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/ErrorDetail"
        },
        "maxItems": 100,
        "description": "Array of field-specific error details"
      },
      "ValidationError": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "pointer": {
            "type": "string",
            "pattern": "^(?:/(?:[^/~]|~0|~1)*)*$",
            "maxLength": 512
          },
          "code": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_]*$",
            "maxLength": 64
          },
          "detail": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "required": [
          "pointer",
          "code",
          "detail"
        ],
        "description": "One violated constraint, carried by the `errors` member of a `request_validation_failed`\nresponse.  `pointer` locates the offending value in the request body as sent, `code` names the\nconstraint it broke, and `detail` states what the constraint requires.  No member of an item\ncontains the rejected value or anything derived from it.\n",
        "example": {
          "pointer": "/emails/0/email",
          "code": "format",
          "detail": "Must be an email address."
        }
      },
      "ErrorDetail": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "field": {
            "$ref": "#/components/schemas/ErrorFieldName"
          },
          "message": {
            "$ref": "#/components/schemas/ErrorMessage"
          }
        },
        "required": [
          "field",
          "message"
        ],
        "description": "Field-specific error detail"
      },
      "ErrorFieldName": {
        "type": "string",
        "minLength": 1,
        "maxLength": 100,
        "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
        "description": "Name of the field that caused the error",
        "example": "email"
      }
    },
    "responses": {
      "TooManyRequests": {
        "description": "Too many requests - a rate limit or budget is exceeded; the problem type says which",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfterRequired"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal server error",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "BadGateway": {
        "description": "Bad gateway - the gateway could not reach the upstream service.  Retry with backoff.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfterRequired"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "GatewayTimeout": {
        "description": "Gateway timeout - the request exceeded the gateway's wall-clock limit.  Retry with backoff.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfterRequired"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "InvalidRequest": {
        "description": "Bad request - invalid input",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Unauthorized - authentication required",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Forbidden - insufficient permissions",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Service unavailable - the request could not be completed.  Retry with backoff.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfterRequired"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "NoContent": {
        "description": "No content - no response body",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Payload too large - the request body exceeds 1 MiB, the most any request may carry, or 64 KiB, the most a POST may carry.  Retry with a smaller body.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "ContactsListResponse": {
        "description": "Successful response containing a paginated list of contacts",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactsListResponse"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflict - the request cannot be applied to the resource in its current state, or the Idempotency-Key was reused for a request with a different payload or while the first request is still in progress.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "ContactAccepted": {
        "description": "Contact accepted - created with the identifier in Location",
        "headers": {
          "Location": {
            "$ref": "#/components/headers/Location"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Idempotent-Replayed": {
            "$ref": "#/components/headers/IdempotentReplayed"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Contact"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Unprocessable entity - the request was well-formed but was refused by the system of record",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "ContactCustomFieldDefinitionsResponse": {
        "description": "Successful response containing a provider's contact custom field definitions",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactCustomFieldDefinitions"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "ContactCustomFieldDefinitionResponse": {
        "description": "Successful response containing one contact custom field definition",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactCustomFieldDefinition"
            }
          }
        }
      },
      "ContactResponse": {
        "description": "Successful response containing a single contact",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Contact"
            }
          }
        }
      },
      "ContactPersonalDetailsResponse": {
        "description": "Successful response containing a contact's personal details",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactPersonalDetails"
            }
          }
        }
      },
      "PreconditionFailed": {
        "description": "Precondition failed - the If-Match validator does not match the current state of the resource, or the precondition could not be honoured as supplied",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "ContactEmailsResponse": {
        "description": "Successful response containing a contact's email addresses",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactEmails"
            }
          }
        }
      },
      "ContactEmailCreated": {
        "description": "Email address created - addressable at the identifier in Location",
        "headers": {
          "Location": {
            "$ref": "#/components/headers/Location"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Idempotent-Replayed": {
            "$ref": "#/components/headers/IdempotentReplayed"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactEmail"
            }
          }
        }
      },
      "ContactPhoneNumbersResponse": {
        "description": "Successful response containing a contact's phone numbers",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactPhoneNumbers"
            }
          }
        }
      },
      "ContactPhoneCreated": {
        "description": "Phone number created - addressable at the identifier in Location",
        "headers": {
          "Location": {
            "$ref": "#/components/headers/Location"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Idempotent-Replayed": {
            "$ref": "#/components/headers/IdempotentReplayed"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactPhone"
            }
          }
        }
      },
      "ContactPhoneResponse": {
        "description": "Successful response containing one of a contact's phone numbers",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactPhone"
            }
          }
        }
      },
      "ContactEmailResponse": {
        "description": "Successful response containing one of a contact's email addresses",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactEmail"
            }
          }
        }
      },
      "ContactAddressesResponse": {
        "description": "Successful response containing a contact's postal addresses",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactAddresses"
            }
          }
        }
      },
      "ContactAddressCreated": {
        "description": "Postal address created - addressable at the identifier in Location",
        "headers": {
          "Location": {
            "$ref": "#/components/headers/Location"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Idempotent-Replayed": {
            "$ref": "#/components/headers/IdempotentReplayed"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactAddress"
            }
          }
        }
      },
      "ContactAddressResponse": {
        "description": "Successful response containing one of a contact's postal addresses",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactAddress"
            }
          }
        }
      },
      "ContactCustomFieldsResponse": {
        "description": "Successful response containing a contact's custom field values",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactCustomFields"
            }
          }
        }
      },
      "ContactCustomFieldResponse": {
        "description": "Successful response containing one of a contact's custom field values",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETag"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ContactCustomField"
            }
          }
        }
      },
      "ContactCustomFieldConflict": {
        "description": "Conflict - the custom field cannot be served: this version does not serve its type, it exceeds a bound this API publishes, or the contact holds a value its definition cannot read.  For the last, the response carries the field's ETag, so the write that replaces the value needs no read first.",
        "headers": {
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "ETag": {
            "$ref": "#/components/headers/ETagOptional"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "ContactsExportJobAccepted": {
        "description": "Export job accepted.\nPoll the job URL (Location) until status is succeeded or failed.  The\ninitial job status is returned in the response.\n",
        "headers": {
          "Location": {
            "$ref": "#/components/headers/Location"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Idempotent-Replayed": {
            "$ref": "#/components/headers/IdempotentReplayed"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ExportJob"
            }
          }
        }
      },
      "ContactsExportJobStatusResponse": {
        "description": "Export job status.\nWhen status is succeeded, body includes result.resultUrl; GET\nthat URL to fetch the export.  When pending or processing, Retry-After\nsuggests polling interval.  When failed, body includes error.\n",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/CacheControl"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ExportJob"
            }
          }
        }
      }
    },
    "parameters": {
      "ApiVersion": {
        "name": "cp-api-version",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "maxLength": 10
        },
        "description": "Carepatron API version for this request",
        "example": "2026-10-02"
      },
      "ProviderId": {
        "name": "providerId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ProviderId"
        },
        "description": "Unique provider identifier"
      },
      "Include": {
        "name": "include",
        "in": "query",
        "required": false,
        "explode": false,
        "schema": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "customFields",
              "emails",
              "phoneNumbers",
              "addresses"
            ]
          },
          "maxItems": 10
        },
        "description": "Read-only projections to add to the response, comma-separated, each value naming the member it adds.\nOmit it for the default response.  A value this operation does not declare is refused with `400`\nrather than ignored, so a misspelling cannot read as an empty projection.  Naming the same projection\nmore than once selects it once.\n"
      },
      "IncludeArchived": {
        "name": "includeArchived",
        "in": "query",
        "required": false,
        "schema": {
          "type": "boolean"
        },
        "description": "`true` lists archived contacts alongside active ones, each flagged by `archived`; `false`, or leaving\nthe parameter out, lists active contacts only.  Any other value, an empty one included, is refused\nwith `400` rather than read as either.\n"
      },
      "PaginationLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "$ref": "#/components/schemas/PaginationLimit"
        },
        "description": "Maximum number of items to return in one page.  Defaults to 20 when omitted."
      },
      "PaginationCursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "schema": {
          "$ref": "#/components/schemas/PaginationCursorParameter"
        },
        "description": "Opaque position token taken from the previous response's `nextCursor`.  Omit it to start at the\nfirst page.\n"
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]+$",
          "maxLength": 255
        },
        "description": "Idempotency key for this request"
      },
      "CustomFieldKey": {
        "name": "key",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/CustomFieldKey"
        },
        "description": "Which custom field the operation addresses, by the key the workspace gave it.  Matched exactly,\ncase included.  A key holding a character the path cannot carry - the app permits spaces and\nslashes - is percent-encoded.\n"
      },
      "ContactId": {
        "name": "contactId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ContactId"
        },
        "description": "Identifier of the contact to address, as returned by the list and create operations."
      },
      "IfMatch": {
        "name": "If-Match",
        "in": "header",
        "required": false,
        "schema": {
          "$ref": "#/components/schemas/EntityTag"
        },
        "description": "The validator the write is conditional on, as returned by a read of this resource.  Optional on\nthis operation: send it to have the write refused if the resource has changed since you read it,\nor omit it to assert absolute values.\n"
      },
      "ContactPhoneId": {
        "name": "contactPhoneId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ContactPhoneId"
        },
        "description": "Which of the contact's phone numbers the operation addresses."
      },
      "IfMatchRequired": {
        "name": "If-Match",
        "in": "header",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/EntityTag"
        },
        "description": "The validator the write is conditional on, as returned by a read of this resource.  Required: the\nwrite is refused with `412` if it is stale, and with `400` if it is absent.  A validator this API\ncannot evaluate - a list, `*`, a weak tag - is refused rather than ignored.\n"
      },
      "ContactEmailId": {
        "name": "contactEmailId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ContactEmailId"
        },
        "description": "Which of the contact's email addresses the operation addresses."
      },
      "ContactAddressId": {
        "name": "contactAddressId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ContactAddressId"
        },
        "description": "Which of the contact's postal addresses the operation addresses."
      },
      "JobId": {
        "name": "jobId",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/JobId"
        },
        "description": "Unique async job identifier in path parameter"
      }
    },
    "headers": {
      "CacheControl": {
        "required": true,
        "schema": {
          "type": "string",
          "const": "no-store"
        },
        "description": "Always `no-store`, on every response including errors.  Responses carry health information and are\nserved fresh on every request, so do not retain any representation this API returns in a cache,\nshared or private, and do not attempt cache revalidation against it.\n"
      },
      "RateLimitLimit": {
        "schema": {
          "$ref": "#/components/schemas/RateLimit"
        },
        "description": "Requests permitted in the current rate-limit window.  Sent on responses to authenticated,\nrate-limited requests - including error responses - so a client can pace itself without waiting for\na `429`.  Not sent when the request failed authentication, and may be absent if rate limiting is\ntemporarily unavailable, so treat it as advisory rather than guaranteed.\n"
      },
      "RateLimitRemaining": {
        "schema": {
          "$ref": "#/components/schemas/RateLimit"
        },
        "description": "Requests still available in the current window.  Reaching zero means the next request will be\nrejected with `429 Too Many Requests` until the window resets.\n"
      },
      "RateLimitReset": {
        "schema": {
          "type": "integer",
          "format": "int64",
          "minimum": 0,
          "maximum": 9223372036854776000
        },
        "description": "Unix time in seconds (UTC) at which the current rate-limit window resets and `RateLimit-Remaining`\nreturns to `RateLimit-Limit`.  An absolute timestamp, not a delta: subtract your own clock to get a\nwait, or on a `429` prefer `Retry-After`, which is already a delta and needs no clock\nsynchronisation.\n"
      },
      "RetryAfter": {
        "schema": {
          "type": "integer",
          "format": "int32",
          "minimum": 1,
          "maximum": 86400
        },
        "description": "Seconds to wait before retrying, per RFC 9110.  Sent when the service can suggest an interval: on a\n`409` for an idempotent request still in flight, on a `500` whose cause is transient, and on a\njob-status `200` while the job is still pending or processing.  Not every response at these statuses\ncarries it, so fall back to your own backoff when it is absent.\n"
      },
      "ETagOptional": {
        "schema": {
          "$ref": "#/components/schemas/EntityTag"
        },
        "description": "The resource's current validator, as `ETag` serves it, on the responses whose description says when\nthey carry one.  Send it back on `If-Match` with the write that follows.\n"
      },
      "ETag": {
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/EntityTag"
        },
        "description": "The current validator for this resource - an opaque, strong entity tag over the resource's\n**writable** content.  Send it back on `If-Match` when you write, and the write applies only if\nnothing has changed the resource since you read it; a stale value is refused with `412`, and that\nresponse carries the current validator so you can re-read, re-apply and resend without guessing.\n\nIt is a **write precondition only**, and is not valid for `If-None-Match`.  It covers the writable\ncontent rather than the whole representation, so a read-only field can change without moving it -\nwhich makes it deliberately under-sensitive as a cache validator.  Conditional `GET` is not\nhonoured on any resource of this API: a matching `If-None-Match` is answered with `200` and the\nfull body.\n"
      },
      "RetryAfterRequired": {
        "required": true,
        "schema": {
          "type": "integer",
          "format": "int32",
          "minimum": 1,
          "maximum": 86400
        },
        "description": "Seconds to wait before retrying, per RFC 9110.  Always sent on `429`, `502`, `503` and `504` - every\nresponse at those statuses carries it, so honour it rather than applying your own backoff.  On a\n`429` it is the time remaining in your current rate-limit window, so waiting exactly this long is\nthe earliest a retry can succeed; on `502`, `503` and `504` it is a suggested interval, since the\nservice cannot know when the failing dependency will recover.\n"
      },
      "Location": {
        "required": true,
        "schema": {
          "type": "string",
          "format": "uri-reference",
          "maxLength": 2048
        },
        "description": "URL of what the request created or started: the new resource, or the asynchronous job accepted.\nFollow it rather than constructing the path yourself.  An export's `202` names the job to poll for\nits status, and a contact create's `202` names the contact.\n"
      },
      "IdempotentReplayed": {
        "schema": {
          "type": "boolean"
        },
        "description": "Present and `true` when the response was replayed from a previously completed request carrying\nthe same `Idempotency-Key`, rather than produced by executing the request again.  A safe retry\nthat reaches an already-completed operation is a replay, not a duplicate.\n"
      }
    }
  }
}