{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/conduyt-com/json-schema/conduyt-com-contact-update-schema.json",
  "title": "ContactUpdate",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/conduyt-com-openapi.yaml#/components/schemas/ContactUpdate",
  "type": "object",
  "properties": {
    "keepPreviousOwnerAsCollaborator": {
      "description": "With assignedTo set to another user: keep the previous owner as a collaborator of this contact (read-only access to the lead and its cards) instead of releasing them. Default false.",
      "type": "boolean"
    },
    "expectedRevision": {
      "type": "integer",
      "minimum": 0,
      "maximum": 2147483647,
      "description": "Optional optimistic-concurrency pin: the contact's `revision` as the caller last read it (returned by GET, list and PATCH). A database trigger bumps the revision whenever a column a person could be looking at changes, whichever writer ran it (so, unlike `updatedAt`, raw-SQL writers move it), and leaves it alone for background-maintained stamps (message clocks, Last Action, lead score, pool and assignment stamps), so an arriving text does not make a save conflict. When it no longer matches, the request is refused with 409 `code: \"contact_changed\"` and nothing is written. Omit it for last-write-wins per field."
    },
    "customFieldsPutIfEmpty": {
      "maxItems": 20,
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 64
      }
    },
    "firstName": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 100
        },
        {
          "type": "null"
        }
      ]
    },
    "lastName": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 100
        },
        {
          "type": "null"
        }
      ]
    },
    "email": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 254,
          "format": "email",
          "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
          "description": "The pattern above is the server's rule (Zod's email check). It accepts a domain label that ends in a hyphen (a@b-.co), which an RFC `format: email` validator such as ajv-formats refuses."
        },
        {
          "type": "null"
        }
      ]
    },
    "phone": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 30
        },
        {
          "type": "null"
        }
      ]
    },
    "company": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ]
    },
    "companyId": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 100
        },
        {
          "type": "null"
        }
      ]
    },
    "jobTitle": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 150
        },
        {
          "type": "null"
        }
      ]
    },
    "source": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 100
        },
        {
          "type": "null"
        }
      ]
    },
    "addressLine1": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ]
    },
    "addressLine2": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ]
    },
    "city": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 100
        },
        {
          "type": "null"
        }
      ]
    },
    "state": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 100
        },
        {
          "type": "null"
        }
      ]
    },
    "zip": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 20
        },
        {
          "type": "null"
        }
      ]
    },
    "country": {
      "type": "string",
      "minLength": 1,
      "pattern": "\\S",
      "description": "A non-empty country. It cannot be cleared (null or blank is refused with `country_invalid`). A country name is converted to its ISO code: the value is trimmed, and a country name or code the server recognises (any case) becomes its ISO 3166-1 alpha-2 code before validation; anything else must be the 2-letter code itself; an empty string is read as null."
    },
    "timezone": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 64
        },
        {
          "type": "null"
        }
      ],
      "description": "A valid IANA time zone identifier (e.g. America/New_York); any other value is refused."
    },
    "legacyStatus": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ]
    },
    "masterStatus": {
      "type": "string",
      "minLength": 1,
      "maxLength": 40,
      "description": "Leading and trailing whitespace is trimmed before the length check, so a blank value is refused."
    },
    "externalBackend": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ]
    },
    "language": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 16
        },
        {
          "type": "null"
        }
      ]
    },
    "doNotContact": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "type": "null"
        }
      ]
    },
    "doNotContactReason": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 500
        },
        {
          "type": "null"
        }
      ]
    },
    "assignedTo": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 100
        },
        {
          "type": "null"
        }
      ]
    },
    "avatarUrl": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 2048,
          "format": "uri",
          "description": "The server parses the value with the WHATWG URL parser (Zod's .url()), not RFC 3986: it accepts a space (percent-encoded), a non-ASCII host, a second '#', an invalid percent escape and a bare scheme such as `x:`, which a strict `format: uri` validator refuses, and it refuses an empty host such as `http://` and a port above 65535, which that validator accepts."
        },
        {
          "type": "null"
        }
      ]
    },
    "leadScore": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100
    },
    "optedOutSms": {
      "type": "boolean"
    },
    "optedOutEmail": {
      "type": "boolean"
    },
    "metadata": {
      "description": "A JSON object, null or absent; any other kind is refused. At most 100 keys, nested at most 6 levels and at most 32768 bytes once sanitised (strings are trimmed and cut to 10000 characters, arrays to 100 items); a larger or deeper object is refused. Reserved keys are stripped, not stored."
    },
    "customFields": {
      "type": "object",
      "additionalProperties": {},
      "description": "Key-value pairs for custom fields. At most 65536 bytes (64 KiB) as UTF-8 JSON (JSON.stringify of the object); a larger object is refused (413)."
    }
  }
}