Delete and archive contacts
Breaking change: DELETE /v1/contacts/{id} and POST /v1/contacts/bulk-delete now delete instead of archiving, and the deletion cannot be undone through the API. Archiving moves to POST /v1/contacts/{id}/archive, is always allowed and stays reversible. A contact.deleted webhook event is published and the contact_has_commercial_references subcode disappears.
Removing a contact used to mean one thing: DELETE /v1/contacts/{contact}
archived it, and archiving was rejected with 422 whenever any of its roles had
invoices, contracts or supplier offers behind it. There was no way to make a
contact disappear, and no way to park a contact that had ever been used.
Both operations now exist, both are always allowed, and neither destroys fiscal history.
Two removals, two routes
| Operation | Route | Effect |
|---|---|---|
| Archive | POST /v1/contacts/{contact}/archive | The contact stays readable with is_archived: true, keeps every document and comes back with PUT /v1/contacts/{contact}/restore. Idempotent. |
| Archive in bulk | POST /v1/contacts/bulk/archive | Same effect, up to 500 ids, partial success. |
| Delete | DELETE /v1/contacts/{contact} | Soft delete: the contact disappears from every read surface and its detail answers 404 contact_not_found. Documents are preserved. Irreversible through the API. |
| Delete in bulk | POST /v1/contacts/bulk-delete | Same effect, up to 500 ids, partial success. |
All four require contacts:delete. PUT /v1/contacts/{contact}/restore
unarchives only; it does not bring back a deleted contact.
Breaking: DELETE and bulk-delete now delete
If your integration called DELETE /v1/contacts/{contact} or
POST /v1/contacts/bulk-delete to archive a contact, it now deletes it, and
you cannot undo that through the API. Move those calls to
POST /v1/contacts/{contact}/archive and POST /v1/contacts/bulk/archive.
DELETE /v1/contacts/{contact} answers 200 with the deletion acknowledgement
instead of the contact:
{
"data": {
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
"object": "contact",
"deleted": true
}
}What a deletion does, and does not do:
- The contact is gone from list, search, options and selectors, statistics, export, activities, import deduplication and legacy alias resolution, and can no longer be used in new documents.
- Its row, roles, directional profiles, bank accounts, legacy aliases and every invoice, quote, delivery note or purchase invoice that references it are kept untouched, and those documents keep showing their customer or supplier data.
- Its tax ID and its
external_idare released, so the same identity can be registered again as a different contact. - Deleting an unknown, foreign or already deleted
idreturns404 contact_not_found.
Archiving is never blocked again
422 business_rule_violation with
subcode: contact_has_commercial_references is retired: no operation emits it
any more. A contact with purchase invoices, contracts or supplier offers can be
archived, and archiving an already archived contact returns 200 and keeps the
first archived_at.
New event: contact.deleted
Subscribe to contact.deleted in POST /v1/webhook-endpoints to learn that a
contact left the application. Unlike contact.archived and contact.restored,
it carries no snapshot — there is nothing left to read:
{
"id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
"object": "contact",
"deleted": true
}contact.archived and contact.restored are unchanged.
MCP
| Tool | Change |
|---|---|
delete_contact | Deletes (soft delete). It used to archive. Returns { id, object: "contact", deleted: true }. |
bulk_delete_contacts | New. Deletes up to 500 contacts with partial success. |
archive_contact | New. Archives a single contact and returns it. |
bulk_archive_contacts | Unchanged. |
restore_contact | Unchanged: unarchives only. |
All five require contacts:delete and are destructive; delete_contact and
bulk_delete_contacts are also flagged irreversible, so an assistant confirms
them before running. See the MCP tool catalog.
Migration
- Every call whose intent was "park this contact" moves from
DELETE /v1/contacts/{contact}toPOST /v1/contacts/{contact}/archive, and fromPOST /v1/contacts/bulk-deletetoPOST /v1/contacts/bulk/archive. - Any retry or error handling branching on
subcode: contact_has_commercial_referencescan be removed: archiving no longer fails for that reason. - If you relied on
PUT /v1/contacts/{contact}/restoreto undo aDELETE, archive instead. Restoring a deleted contact is a support operation on the database, not an API call. - Reading a contact you deleted returns
404, so treat that code as "gone", not as a transient failure.
Read Contacts.
New endpoints1
| Endpoint | Description |
|---|---|
POST/v1/contacts/{contact}/archive | Archive a contact |
Updated endpoints3
| Endpoint | Description |
|---|---|
DEL/v1/contacts/{contact} | Delete a contact |
POST/v1/contacts/bulk-delete | Delete contacts in bulk |
POST/v1/contacts/bulk/archive | Archive contacts in bulk |