Factuarea APIDevelopers
Contract

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

OperationRouteEffect
ArchivePOST /v1/contacts/{contact}/archiveThe contact stays readable with is_archived: true, keeps every document and comes back with PUT /v1/contacts/{contact}/restore. Idempotent.
Archive in bulkPOST /v1/contacts/bulk/archiveSame effect, up to 500 ids, partial success.
DeleteDELETE /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 bulkPOST /v1/contacts/bulk-deleteSame 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_id are released, so the same identity can be registered again as a different contact.
  • Deleting an unknown, foreign or already deleted id returns 404 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

ToolChange
delete_contactDeletes (soft delete). It used to archive. Returns { id, object: "contact", deleted: true }.
bulk_delete_contactsNew. Deletes up to 500 contacts with partial success.
archive_contactNew. Archives a single contact and returns it.
bulk_archive_contactsUnchanged.
restore_contactUnchanged: 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

  1. Every call whose intent was "park this contact" moves from DELETE /v1/contacts/{contact} to POST /v1/contacts/{contact}/archive, and from POST /v1/contacts/bulk-delete to POST /v1/contacts/bulk/archive.
  2. Any retry or error handling branching on subcode: contact_has_commercial_references can be removed: archiving no longer fails for that reason.
  3. If you relied on PUT /v1/contacts/{contact}/restore to undo a DELETE, archive instead. Restoring a deleted contact is a support operation on the database, not an API call.
  4. Reading a contact you deleted returns 404, so treat that code as "gone", not as a transient failure.

Read Contacts.

New endpoints1

EndpointDescription
POST/v1/contacts/{contact}/archiveArchive a contact

Updated endpoints3

EndpointDescription
DEL/v1/contacts/{contact}Delete a contact
POST/v1/contacts/bulk-deleteDelete contacts in bulk
POST/v1/contacts/bulk/archiveArchive contacts in bulk

On this page

Need a hand?Contact support