# Update a Contact

PATCH https://api.smobi.com/v1/contacts/{identifier}

Authentication: x-api-key header (brand-scoped API key).

Partial update. Scalars: send a value to set it, null to clear it, omit to leave untouched. tags replaces the whole list; customFields merges per key (null deletes a key). phoneNumber is immutable. Set active=true to restore an archived contact.

## Operation

```json
{
  "description": "Partial update. Scalars: send a value to set it, null to clear it, omit to leave untouched. tags replaces the whole list; customFields merges per key (null deletes a key). phoneNumber is immutable. Set active=true to restore an archived contact.",
  "operationId": "updateContact",
  "parameters": [
    {
      "name": "identifier",
      "required": true,
      "in": "path",
      "description": "Contact id (uuid) or phone number (digits-only recommended in URLs; E.164 requires encoding + as %2B)",
      "schema": {
        "example": "14155552222",
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/UpdateContactDto"
        },
        "examples": {
          "update-fields": {
            "summary": "Update attributes",
            "description": "tags replaces the whole list; customFields merges per key.",
            "value": {
              "tags": [
                "plan-enterprise"
              ],
              "customFields": {
                "seats": 25
              }
            }
          },
          "clear-data": {
            "summary": "Clear personal data",
            "description": "null clears a scalar field or deletes a customFields key — use before archiving to remove personal data.",
            "value": {
              "firstName": null,
              "lastName": null,
              "email": null,
              "customFields": {
                "externalId": null
              }
            }
          },
          "opt-out": {
            "summary": "Suppress marketing",
            "value": {
              "marketingConsent": "OPTED_OUT"
            }
          },
          "restore": {
            "summary": "Restore an archived contact",
            "value": {
              "active": true
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "The updated contact",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/ContactDto"
          }
        }
      }
    },
    "400": {
      "description": "Validation error, empty body, or phoneNumber in body"
    },
    "401": {
      "description": "Missing or invalid API key",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "statusCode": {
                "type": "number",
                "example": 401
              },
              "message": {
                "type": "string",
                "example": "Invalid API key"
              },
              "error": {
                "type": "string",
                "example": "Unauthorized"
              }
            }
          }
        }
      }
    },
    "403": {
      "description": "The API key is not scoped to a brand",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "statusCode": {
                "type": "number",
                "example": 403
              },
              "message": {
                "type": "string",
                "example": "This endpoint requires a brand-scoped API key"
              },
              "error": {
                "type": "string",
                "example": "Forbidden"
              }
            }
          }
        }
      }
    },
    "404": {
      "description": "No contact with this id or phone number exists for the brand",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "statusCode": {
                "type": "number",
                "example": 404
              },
              "message": {
                "type": "string",
                "example": "Contact not found"
              },
              "error": {
                "type": "string",
                "example": "Not Found"
              }
            }
          }
        }
      }
    },
    "422": {
      "description": "Consent transition rejected: a contact that opted out (e.g. texted STOP) cannot be opted back in via the API",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "statusCode": {
                "type": "number",
                "example": 422
              },
              "message": {
                "type": "string",
                "example": "Cannot opt in a contact that has opted out; the contact must opt back in themselves (e.g. by texting START)"
              },
              "error": {
                "type": "string",
                "example": "Unprocessable Entity"
              }
            }
          }
        }
      }
    }
  },
  "summary": "Update a Contact",
  "tags": [
    "Contacts"
  ],
  "security": [
    {
      "x-api-key": []
    }
  ]
}
```

## Referenced Schemas

```json
{
  "UpdateContactDto": {
    "type": "object",
    "properties": {
      "firstName": {
        "type": "string",
        "example": "Jane",
        "nullable": true,
        "minLength": 1,
        "maxLength": 255
      },
      "lastName": {
        "type": "string",
        "example": "Doe",
        "nullable": true,
        "minLength": 1,
        "maxLength": 255
      },
      "email": {
        "type": "string",
        "example": "jane@example.com",
        "nullable": true,
        "format": "email",
        "maxLength": 255
      },
      "state": {
        "type": "string",
        "example": "CA",
        "nullable": true,
        "minLength": 1,
        "maxLength": 255
      },
      "country": {
        "type": "string",
        "example": "US",
        "nullable": true,
        "minLength": 1,
        "maxLength": 255
      },
      "dob": {
        "type": "string",
        "example": "1990-04-12",
        "description": "Date of birth in YYYY-MM-DD format. Must be a valid date in the past; null clears the value.",
        "nullable": true,
        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
      },
      "gender": {
        "type": "string",
        "nullable": true,
        "minLength": 1,
        "maxLength": 255
      },
      "preferredLanguage": {
        "type": "string",
        "example": "en",
        "nullable": true,
        "minLength": 1,
        "maxLength": 255
      },
      "tags": {
        "example": [
          "vip"
        ],
        "description": "Full replace on updates; usable in segment rules",
        "type": "array",
        "items": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "maxItems": 100
      },
      "customFields": {
        "type": "object",
        "additionalProperties": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 1000,
              "nullable": true
            },
            {
              "type": "number"
            },
            {
              "type": "boolean"
            },
            {
              "type": "array",
              "maxItems": 100,
              "items": {
                "type": "string",
                "maxLength": 1000
              }
            }
          ]
        },
        "example": {
          "loyaltyTier": "gold"
        },
        "description": "Map of up to 50 fields. Values may be strings (up to 1,000 characters), numbers, booleans, arrays of up to 100 strings, or null. Nested objects are not accepted. Keys must match ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$; `__proto__`, `constructor`, and `prototype` are reserved. A null value deletes that key on update.",
        "maxProperties": 50
      },
      "marketingConsent": {
        "type": "string",
        "enum": [
          "OPTED_IN",
          "OPTED_OUT"
        ],
        "description": "Setting OPTED_IN attests that you collected the contact's marketing consent. OPTED_IN is rejected (422) when the contact previously opted out (e.g. texted STOP); OPTED_OUT is always accepted. Contacts default to UNKNOWN, and campaigns only send to OPTED_IN contacts."
      },
      "active": {
        "type": "boolean",
        "example": true,
        "description": "Set true to restore an archived contact"
      }
    },
    "additionalProperties": false,
    "minProperties": 1
  },
  "ContactDto": {
    "type": "object",
    "properties": {
      "id": {
        "type": "string",
        "example": "9b2f1c3e-8a4d-4f6b-9c2e-1d3f5a7b9c0e"
      },
      "phoneNumber": {
        "type": "string",
        "example": "+14155552222",
        "description": "Always returned in E.164 format"
      },
      "active": {
        "type": "boolean",
        "example": true,
        "description": "false when the contact has been archived (DELETE)"
      },
      "firstName": {
        "type": "string",
        "nullable": true,
        "example": "Jane"
      },
      "lastName": {
        "type": "string",
        "nullable": true,
        "example": "Doe"
      },
      "email": {
        "type": "string",
        "nullable": true,
        "example": "jane@example.com"
      },
      "state": {
        "type": "string",
        "nullable": true,
        "example": "CA"
      },
      "country": {
        "type": "string",
        "nullable": true,
        "example": "US"
      },
      "dob": {
        "type": "string",
        "nullable": true,
        "example": "1990-04-12",
        "description": "Date of birth (YYYY-MM-DD)"
      },
      "gender": {
        "type": "string",
        "nullable": true,
        "example": null
      },
      "preferredLanguage": {
        "type": "string",
        "nullable": true,
        "example": "en"
      },
      "tags": {
        "example": [
          "vip",
          "spring-sale"
        ],
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "customFields": {
        "type": "object",
        "additionalProperties": true,
        "example": {
          "loyaltyTier": "gold",
          "storeIds": [
            "s1",
            "s2"
          ]
        },
        "description": "Stored custom-field values keyed by field name. See create/update request schemas for accepted write types and limits."
      },
      "marketingConsent": {
        "$ref": "#/components/schemas/ContactMarketingConsentDto"
      },
      "createdAt": {
        "type": "string",
        "example": "2026-07-07T12:00:00.000Z"
      },
      "updatedAt": {
        "type": "string",
        "example": "2026-07-07T12:00:00.000Z"
      }
    },
    "required": [
      "id",
      "phoneNumber",
      "active",
      "firstName",
      "lastName",
      "email",
      "state",
      "country",
      "dob",
      "gender",
      "preferredLanguage",
      "tags",
      "customFields",
      "marketingConsent",
      "createdAt",
      "updatedAt"
    ]
  },
  "ContactMarketingConsentDto": {
    "type": "object",
    "properties": {
      "channel": {
        "type": "string",
        "enum": [
          "MOBILE"
        ],
        "example": "MOBILE"
      },
      "status": {
        "type": "string",
        "enum": [
          "UNKNOWN",
          "SENT",
          "OPTED_IN",
          "OPTED_OUT"
        ],
        "example": "OPTED_IN",
        "description": "Campaigns are only sent to OPTED_IN contacts. Contacts created via the API start as UNKNOWN until consent is set."
      },
      "updatedAt": {
        "type": "string",
        "nullable": true,
        "example": "2026-07-07T12:00:00.000Z"
      }
    },
    "required": [
      "channel",
      "status",
      "updatedAt"
    ]
  }
}
```
