*API REST › Referencia de endpoints › CRM › Contactos*

# Actualizar un contacto

> Actualiza parcialmente un contacto (envía solo los campos que cambian).

Las listas de emails, teléfonos y direcciones sustituyen por completo la lista existente. Devuelve el contacto actualizado en detalle.

`PATCH /api/v1/contacts/{contactId}` — permiso `contacts:write`

Permiso: `contacts:write` · 120/min · Ámbito heredado · Idempotency-Key · If-Match · MCP: `filmo_contacts_update`

## Autorizaciones

Clave de API como Bearer, con el permiso de abajo. `contacts:write`

## Cabeceras

| Nombre | Tipo | Obligatorio | Descripción |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | No | Identificador único de la petición, elegido por el cliente (sirve un UUID). Entre 1 y 255 caracteres: una cabecera vacía se rechaza con 400 `validation_error`, porque el cliente se creería protegido. Repetir la misma clave con el MISMO cuerpo en 24 h devuelve la respuesta guardada sin volver a ejecutar, con la cabecera `Idempotent-Replay: true`. Con un cuerpo DISTINTO devuelve 422. Es la forma segura de reintentar tras un tiempo de espera de red. |
| `If-Match` | string | No | Bloqueo optimista: el `ETag` que leíste del recurso (o `*`). Si el recurso ha cambiado desde entonces, la escritura se rechaza con `412 precondition_failed` antes de hacer nada; léelo de nuevo y reintenta. Sin la cabecera, la escritura siempre se realiza. |

## Parámetros de ruta

| Nombre | Tipo | Obligatorio | Descripción |
| --- | --- | --- | --- |
| `contactId` | string | Sí | Identificador del contacto. |

## Cuerpo

Esquema del cuerpo: `ContactUpdateBody`

| Nombre | Tipo | Obligatorio | Descripción |
| --- | --- | --- | --- |
| `name` | string | No | Nombre del contacto (persona o empresa). |
| `contactType` | string | No | Tipo de contacto: person (particular) o company (negocio). `person`, `company` |
| `role` | string | No | Rol del contacto: client, provider, lead, debtor, creditor, student, teacher o unspecified. `client`, `provider`, `lead`, `debtor`, `creditor`, `student`, `teacher`, `unspecified` |
| `status` | string | No | Estado del contacto: active (operativo), inactive (desactivado) o archived (cerrado, conservado como histórico). `active`, `inactive`, `archived` |
| `nif` | string | No | NIF/CIF o número de identificación fiscal; null lo borra ("" también se normaliza a null). |
| `commercialName` | string | No | Nombre comercial; null lo borra. |
| `position` | string | No | Cargo o puesto; null lo borra. |
| `emails` | object[] | No | Lista de direcciones de correo electrónico que REEMPLAZA por completo la lista existente (máx. 20). Los ids internos los genera el servidor. |
| `emails.email` | string<email> | Sí | Dirección de email; se valida el formato. |
| `emails.label` | string | No | Etiqueta descriptiva del email (p. ej. "Personal", "Facturación"). |
| `emails.isPrimary` | boolean | No | Marca este correo electrónico como el principal del contacto. |
| `phoneNumbers` | object[] | No | Lista de teléfonos que REEMPLAZA por completo la lista existente (máx. 20). Los ids internos los genera el servidor. |
| `phoneNumbers.number` | string | Sí | Número de teléfono. |
| `phoneNumbers.label` | string | No | Etiqueta descriptiva del teléfono (p. ej. "Móvil", "Oficina"). |
| `phoneNumbers.countryCode` | string | No | Prefijo telefónico internacional del país (p. ej. "+34"). |
| `phoneNumbers.isPrimary` | boolean | No | Marca este teléfono como el principal del contacto. |
| `websites` | object[] | No | Lista de sitios web que REEMPLAZA por completo la lista existente (máx. 20). Los ids internos los genera el servidor. |
| `websites.url` | string<uri> | Sí | URL del sitio web; se valida el formato. |
| `websites.label` | string | No | Etiqueta descriptiva del sitio web (p. ej. "Web corporativa", "LinkedIn"). |
| `addresses` | object[] | No | Lista de direcciones que REEMPLAZA por completo la lista existente (máx. 20). Envía el id de cada dirección existente que quieras conservar; las direcciones sin id se crean con uno nuevo. Un id que no es una dirección de este contacto responde 400. Omitir una dirección que un pedido usa como dirección de facturación o de envío responde 409 `address_in_use` y no se cambia nada. |
| `addresses.id` | string | No | Identificador de una dirección existente para conservarla; omítelo para añadir una nueva. |
| `addresses.label` | string | No | Etiqueta descriptiva de la dirección (p. ej. "Facturación", "Envío"). |
| `addresses.addressLine1` | string | Sí | Primera línea de la dirección (calle y número). Obligatorio. |
| `addresses.addressLine2` | string | No | Segunda línea de la dirección (piso, puerta, etc.). |
| `addresses.city` | string | No | Ciudad de la dirección. |
| `addresses.province` | string | No | Provincia o región de la dirección. |
| `addresses.postcode` | string | No | Código postal de la dirección. |
| `addresses.country` | string | No | País de la dirección (código o nombre corto). |
| `addresses.isMain` | boolean | No | Marca esta dirección como la principal del contacto. |
| `folder` | string | No | Carpeta o agrupación organizativa; null la borra. |
| `contactNumber` | string | No | Número de contacto (referencia interna); null lo borra. |
| `discountPercentage` | number | No | Porcentaje de descuento general del contacto (0-100); null lo borra. |
| `subscribedToMarketing` | boolean | No | Si el contacto está suscrito a comunicaciones de marketing. |

## Ejemplos de petición

*Actualizar un contacto*

**cURL**

```bash
curl https://app.filmo.io/api/v1/contacts/cont_8f2a1c \
  --request PATCH \
  --header "Authorization: Bearer $FILMO_API_KEY" \
  --header "Idempotency-Key: $(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)" \
  --header 'Content-Type: application/json' \
  --data '{
  "role": "client",
  "status": "active",
  "emails": [
    {
      "email": "facturacion@example.com",
      "label": "Facturación"
    },
    {
      "email": "produccion@example.com",
      "label": "Producción"
    }
  ],
  "discountPercentage": 10
}'
```

**Node.js**

```javascript
fetch('https://app.filmo.io/api/v1/contacts/cont_8f2a1c', {
  method: 'PATCH',
  headers: {
    Authorization: 'Bearer YOUR_FILMO_API_KEY',
    'Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    role: 'client',
    status: 'active',
    emails: [
      {
        email: 'facturacion@example.com',
        label: 'Facturación'
      },
      {
        email: 'produccion@example.com',
        label: 'Producción'
      }
    ],
    discountPercentage: 10
  })
})
```

**Python**

```python
import uuid
import requests

requests.patch(
    "https://app.filmo.io/api/v1/contacts/cont_8f2a1c",
    headers={
      "Authorization": "Bearer YOUR_FILMO_API_KEY",
      "Idempotency-Key": str(uuid.uuid4()),
      "Content-Type": "application/json"
    },
    json={
      "role": "client",
      "status": "active",
      "emails": [
        {
          "email": "facturacion@example.com",
          "label": "Facturación"
        },
        {
          "email": "produccion@example.com",
          "label": "Producción"
        }
      ],
      "discountPercentage": 10
    }
)
```

## Respuesta

Esquema de la respuesta: `ContactDetailDto`

| Estado | Descripción |
| --- | --- |
| 200 | Respuesta correcta. |
| 400 | Petición no válida (validation_error): consulta o cuerpo mal formados. `errors[]` enumera cada campo erróneo con su código estable. |
| 401 | Fallo de autenticación (unauthorized, api_key_invalid, api_key_revoked, api_key_expired, api_key_owner_inactive, api_key_owner_not_admin). |
| 403 | No permitido (insufficient_scope: la clave de API no tiene el scope requerido, indicado en `requiredScope`; forbidden: el propietario de la clave carece del permiso que exige la operación). `insufficient_scope` se responde antes de que la petición cuente contra el límite de peticiones, así que no lleva cabeceras `RateLimit-*`; `forbidden` sí. |
| 404 | Recurso no encontrado (not_found): no existe o está fuera del alcance de la clave de API. |
| 409 | Conflicto (conflict): el estado actual del recurso impide la operación, ya hay en curso una petición con esa Idempotency-Key, o otra escritura condicional (`If-Match`) retuvo el recurso durante más de cinco segundos. `errors[0].code` indica cuál: `in_progress` y `lock_timeout` se resuelven reintentando; `invalid_state` no, porque el estado del recurso prohíbe la operación. |
| 412 | Precondición fallida (precondition_failed): la etiqueta de entidad `If-Match` no coincide con la representación actual. Lee el recurso de nuevo y reintenta. |
| 422 | Idempotency-Key reutilizada (idempotency_key_reused): la misma clave se usó antes con un cuerpo distinto. Cambia la clave. |
| 429 | Límite de peticiones superado (rate_limited): reintenta tras `Retry-After` segundos; `RateLimit-*` indica qué cuota se ha agotado. |
| 500 | Error interno (internal_error). |

**200**

| Nombre | Tipo | Descripción |
| --- | --- | --- |
| `data` | object | El contacto completo, tal como lo devuelve la API. |
| `data.id` | string | Identificador único del contacto. |
| `data.name` | string | Nombre del contacto (persona o empresa). |
| `data.contactType` | string | Tipo de contacto: person (particular) o company (negocio). `person`, `company` |
| `data.role` | string | Rol del contacto: client, provider, lead, debtor, creditor, student, teacher o unspecified. `client`, `provider`, `lead`, `debtor`, `creditor`, `student`, `teacher`, `unspecified` |
| `data.status` | string | Estado del contacto: active (operativo), inactive (desactivado) o archived (cerrado, conservado como histórico). `active`, `inactive`, `archived` |
| `data.nif` | string | NIF/CIF o número de identificación fiscal; null si no se ha registrado. |
| `data.commercialName` | string | Nombre comercial; null si coincide con el nombre o no consta. |
| `data.position` | string | Cargo o puesto de la persona de contacto; null si no está registrado. |
| `data.contactNumber` | string | Número de contacto asignado por la cuenta (referencia interna); null si no consta. |
| `data.emails` | object[] | Lista de direcciones de correo electrónico del contacto. |
| `data.emails.id` | string | Identificador interno del correo electrónico dentro del contacto. |
| `data.emails.label` | string | Etiqueta descriptiva del email; null si no tiene. |
| `data.emails.email` | string | Dirección de email. |
| `data.emails.isPrimary` | boolean | Si este es el email principal del contacto. |
| `data.billingEmail` | string | Email de facturación dedicado; null significa que se usa el email principal. |
| `data.phoneNumbers` | object[] | Lista de teléfonos del contacto. |
| `data.phoneNumbers.id` | string | Identificador interno del teléfono dentro del contacto. |
| `data.phoneNumbers.label` | string | Etiqueta descriptiva del teléfono; null si no tiene. |
| `data.phoneNumbers.number` | string | Número de teléfono. |
| `data.phoneNumbers.countryCode` | string | Prefijo telefónico internacional del país (p. ej. "+34"); null si no consta. |
| `data.phoneNumbers.isPrimary` | boolean | Si este es el teléfono principal del contacto. |
| `data.websites` | object[] | Lista de sitios web del contacto. |
| `data.websites.id` | string | Identificador interno del sitio web dentro del contacto. |
| `data.websites.label` | string | Etiqueta descriptiva del sitio web; null si no tiene. |
| `data.websites.url` | string | URL del sitio web. |
| `data.addresses` | object[] | Lista de direcciones del contacto. |
| `data.addresses.id` | string | Identificador interno de la dirección dentro del contacto. |
| `data.addresses.label` | string | Etiqueta descriptiva de la dirección; null si no tiene. |
| `data.addresses.addressLine1` | string | Primera línea de la dirección; null si no consta. |
| `data.addresses.addressLine2` | string | Segunda línea de la dirección; null si no consta. |
| `data.addresses.city` | string | Ciudad; null si no consta. |
| `data.addresses.province` | string | Provincia o región; null si no se ha registrado. |
| `data.addresses.postcode` | string | Código postal; null si no se ha registrado. |
| `data.addresses.country` | string | País; null si no consta. |
| `data.addresses.isMain` | boolean | Si esta es la dirección principal del contacto. |
| `data.folder` | string | Carpeta o agrupación organizativa; null si el contacto no está clasificado. |
| `data.discountPercentage` | number | Porcentaje de descuento general del contacto (0-100); null si no hay descuento configurado. |
| `data.preferredLanguage` | string | Idioma preferido del contacto; null si no se ha registrado. |
| `data.currency` | string | Moneda por defecto del contacto como código ISO 4217; null si no consta. |
| `data.paymentMethod` | object | Método de pago preferido del contacto; null si no está configurado. |
| `data.paymentMethod.type` | string | Tipo de método de pago: bank_transfer, card, cash, sepa_direct_debit u other. `bank_transfer`, `card`, `cash`, `other`, `sepa_direct_debit` |
| `data.paymentMethod.customValue` | string | Detalle en texto libre cuando el tipo es "other" (por ejemplo, un método registrado antes de configurarse); null en caso contrario. |
| `data.paymentDays` | integer[] | Días del mes en que el contacto hace pagos; null si no está configurado. |
| `data.expirationDays` | integer | Días de vencimiento por defecto de las facturas del contacto; null si no está configurado. |
| `data.subscribedToMarketing` | boolean | Si el contacto está suscrito a comunicaciones de marketing. |
| `data.isAppUser` | boolean | Si el contacto tiene acceso a la app. |
| `data.customFields` | object<string, any> | Valores de los campos personalizados por identificador de campo personalizado; las definiciones están en `/api/v1/custom-fields`. |
| `data.createdAt` | string<date-time> | Fecha de creación en ISO 8601 con desplazamiento. |
| `data.updatedAt` | string<date-time> | Fecha de la última actualización en ISO 8601 con desplazamiento. |
| `data.relatedContacts` | object[] | Contactos relacionados con este contacto (p. ej. las personas de una empresa). |
| `data.relatedContacts.id` | string | Identificador único del contacto relacionado. |
| `data.relatedContacts.name` | string | Nombre del contacto relacionado. |
| `data.relatedContacts.commercialName` | string | Nombre comercial del contacto relacionado; null si no consta. |
| `data.relatedContacts.contactType` | string | Tipo del contacto relacionado: person o company. `person`, `company` |
| `data.relatedContacts.role` | string | Rol del contacto relacionado. `client`, `provider`, `lead`, `debtor`, `creditor`, `student`, `teacher`, `unspecified` |
| `data.relatedContacts.status` | string | Estado del contacto relacionado: active, inactive o archived. `active`, `inactive`, `archived` |
| `data.relatedContacts.relationRole` | string | Rol de la relación entre los dos contactos (p. ej. "empleado de"); null si no consta. |
| `meta` | object | Información adicional sobre la respuesta. |
| `meta.warnings` | object[] | Avisos del ciclo de vida; solo aparecen cuando la operación está obsoleta. |
| `meta.warnings.code` | string | Código de aviso estable. `deprecated` |
| `meta.warnings.message` | string | Para personas: qué está obsoleto, hasta cuándo y qué usar. |
| `meta.warnings.sunset` | string | Día (YYYY-MM-DD) en que la operación deja de estar disponible. |
| `meta.warnings.replacement` | string | `operationId` de la operación que hay que usar en su lugar, o null si no hay ninguna. |
| `meta.warnings.link` | string | Dónde está documentada la deprecación (el changelog). |

*Respuesta de ejemplo · 200*

```json
{
  "data": {
    "id": "contact_9f3a2b",
    "name": "Example contact",
    "contactType": "person",
    "role": "client",
    "status": "active",
    "nif": "B00000000",
    "commercialName": "Example commercial name",
    "position": "Example position",
    "contactNumber": "2026-0001",
    "emails": [
      {
        "id": "email_9f3a2b",
        "label": "Main",
        "email": "ana.garcia@example.com",
        "isPrimary": true
      }
    ],
    "billingEmail": "ana.garcia@example.com",
    "phoneNumbers": [
      {
        "id": "phone_number_9f3a2b",
        "label": "Main",
        "number": "+34 600 000 000",
        "countryCode": "+34",
        "isPrimary": true
      }
    ],
    "websites": [
      {
        "id": "website_9f3a2b",
        "label": "Main",
        "url": "https://example.com/resource"
      }
    ],
    "addresses": [
      {
        "id": "address_9f3a2b",
        "label": "Main",
        "addressLine1": "Calle de Ejemplo 12",
        "addressLine2": "Calle de Ejemplo 12",
        "city": "Madrid",
        "province": "Madrid",
        "postcode": "28001",
        "country": "ES",
        "isMain": true
      }
    ],
    "folder": "Example folder",
    "discountPercentage": 1,
    "preferredLanguage": "es-ES",
    "currency": "EUR",
    "paymentMethod": {
      "type": "bank_transfer",
      "customValue": "Example custom value"
    },
    "paymentDays": [
      1
    ],
    "expirationDays": 1,
    "subscribedToMarketing": true,
    "isAppUser": true,
    "customFields": {
      "example": "example"
    },
    "createdAt": "2026-09-01T08:00:00.000Z",
    "updatedAt": "2026-09-01T08:00:00.000Z",
    "relatedContacts": [
      {
        "id": "related_contact_9f3a2b",
        "name": "Example related contact",
        "commercialName": "Example commercial name",
        "contactType": "person",
        "role": "client",
        "status": "active",
        "relationRole": "Example relation role"
      }
    ]
  }
}
```

## Errores

| Estado | Códigos | Significado |
| --- | --- | --- |
| 400 | `validation_error` | Consulta o cuerpo mal formados. `errors[]` enumera cada campo erróneo con su código estable. |
| 404 | `not_found` | No existe o queda fuera del alcance de la clave de API. |
| 409 | `conflict` | El estado actual del recurso impide la operación, ya hay en curso una petición con esa Idempotency-Key, u otra escritura condicional (`If-Match`) retuvo el recurso durante más de cinco segundos. `errors[0].code` indica cuál: `in_progress` y `lock_timeout` se resuelven reintentando; `invalid_state` no, porque el estado del recurso prohíbe la operación. |
| 412 | `precondition_failed` | La etiqueta de entidad `If-Match` no coincide con la representación actual. Vuelve a leer el recurso y reintenta. |
| 422 | `idempotency_key_reused` | La misma clave se usó antes con un cuerpo distinto. Cambia la clave. |

## Obsolescencia

Esta operación no está obsoleta. Las obsolescencias se anuncian con la cabecera Deprecation y una fecha de retirada.

---

URL canónica: https://docs.filmo.io/es/rest-api/endpoint-reference/contacts/update-a-contact  
Última actualización: 2026-10-25