{
  "schemaVersion": "1.0",
  "title": "Twenty record delete, restore, and merge",
  "description": "Use these seven mutations to change record lifecycle or consolidate duplicate records. Choose the narrowest operation, freeze its exact target, and preserve the original Action and idempotency identity through verification.",
  "canonical": "https://getaip.org/docs/connectors/twenty/capabilities/record-delete-restore-and-merge",
  "route": "/docs/connectors/twenty/capabilities/record-delete-restore-and-merge",
  "source": "docs/connectors/twenty/capabilities/record-delete-restore-and-merge.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/twenty/capabilities/record-delete-restore-and-merge.md",
    "txt": "/docs/download/connectors/twenty/capabilities/record-delete-restore-and-merge.txt",
    "json": "/docs/download/connectors/twenty/capabilities/record-delete-restore-and-merge.json",
    "pdf": "/docs/download/connectors/twenty/capabilities/record-delete-restore-and-merge.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Twenty record delete, restore, and merge\ndescription: >-\n  Reference soft deletion, permanent destruction, restoration, and duplicate\n  record merge operations\nkind: capability-reference\naudience: application-developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: twenty\ncapabilityIds:\n  - cap:twenty:record.soft_delete\n  - cap:twenty:record.soft_delete_many\n  - cap:twenty:record.destroy\n  - cap:twenty:record.destroy_many\n  - cap:twenty:record.restore\n  - cap:twenty:record.restore_many\n  - cap:twenty:record.merge\n---\n\n# Twenty record delete, restore, and merge\n\nUse these seven mutations to change record lifecycle or consolidate duplicate\nrecords. Choose the narrowest operation, freeze its exact target, and preserve\nthe original Action and idempotency identity through verification.\n\nSoft delete, permanent destroy, restore, and merge have different provider\nconsequences. The connector does not expose transactions, compensation,\nrollback, reconciliation, cancellation, or safe retry for any of them.\n\n## Compare the operations\n\n| Operation | Provider request | Risk | Target |\n|---|---|---|---|\n| `record.soft_delete` | `DELETE /rest/{object}/{id}?soft_delete=true` | High | One ID |\n| `record.soft_delete_many` | `DELETE /rest/{object}?filter=...&soft_delete=true` | High | Explicit filter |\n| `record.destroy` | `DELETE /rest/{object}/{id}?soft_delete=false` | Critical | One ID |\n| `record.destroy_many` | `DELETE /rest/{object}?filter=...&soft_delete=false` | Critical | Explicit filter |\n| `record.restore` | `PATCH /rest/restore/{object}?filter=id[eq]:{id}` | High | One ID through filter |\n| `record.restore_many` | `PATCH /rest/restore/{object}?filter=...` | High | Explicit filter |\n| `record.merge` | `PATCH /rest/{object}/merge` | High | Two through nine IDs |\n\nEvery operation requires tenant-policy approval and an external-account-scoped\nidempotency key. Every operation advertises unsafe retry.\n\n## Apply shared target rules\n\nEvery operation requires `object` matching:\n\n```text\n^[A-Za-z][A-Za-z0-9_]{0,127}$\n```\n\nSingle-record operations require a canonical RFC 4122 UUID with version 1\nthrough 5 and an RFC variant nibble. Merge applies the same check to each\nmember.\n\nThe connector fixes the provider origin, workspace, token, and path template.\nObject and filter values cannot select a different provider host.\n\n## Soft-delete one record\n\n`cap:twenty:record.soft_delete` accepts only object and ID:\n\n```json\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000040\"\n}\n```\n\nThe connector adds `soft_delete=true`. It accepts no query object for this\nsingle-target operation.\n\nSoft deletion represents a reversible provider lifecycle state only when the\nrecord still exists and the provider permits restore. It is not an AIP undo\ntoken or retention guarantee.\n\n## Soft-delete a filtered set\n\n`cap:twenty:record.soft_delete_many` requires a non-empty provider filter:\n\n```json\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"filter\": \"id[in]:[0190c42f-2d5a-7000-8000-000000000040]\"\n  }\n}\n```\n\nThe connector rejects a missing filter, whitespace-only filter, literal `{}`,\nor literal `null`. It does not interpret the provider filter or count matches.\n\nPreflight the same filter with an authorized read and retain the expected IDs\nand count in approval evidence. Do not widen the filter after approval.\n\n## Permanently destroy one record\n\n`cap:twenty:record.destroy` uses the same outer input as single soft delete:\n\n```json\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000040\"\n}\n```\n\nThe connector sends `soft_delete=false` and classifies the operation as\ncritical. No connector capability restores the permanently destroyed record.\n\nApproval evidence should identify data-retention authority, dependencies,\nbackups, legal holds, and a separately tested recovery plan when one exists.\nAn idempotency key is not a backup.\n\n## Permanently destroy a filtered set\n\n`cap:twenty:record.destroy_many` requires an explicit non-empty filter:\n\n```json\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"filter\": \"id[in]:[0190c42f-2d5a-7000-8000-000000000040]\"\n  }\n}\n```\n\nThe connector adds `soft_delete=false`. The filter guard prevents an empty or\nobvious whole-collection request, but it does not prove that a syntactically\nnon-empty provider filter is narrow.\n\nTreat filter semantics, selected IDs, count, workspace schema, and permanent\nconsequences as approval inputs. Re-read the exact target set immediately\nbefore authorization when deployment policy requires it.\n\n## Restore one soft-deleted record\n\n`cap:twenty:record.restore` accepts an object, ID, and optional depth:\n\n```json\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000040\",\n  \"query\": {\n    \"depth\": 0\n  }\n}\n```\n\nThe pinned provider source declares a single-record restore route, but its\ncore-path parser rejects that three-segment form. The connector therefore uses\nthe filtered collection restore route with `filter=id[eq]:<id>`.\n\nThis compatibility mapping has the intended single-record target. It remains a\nprovider collection request and should be requalified when the pinned provider\nrevision changes.\n\nRestore cannot recover a permanent destroy. It also does not prove that every\nrelationship, automation, or downstream index returns to its prior state.\n\n## Restore a filtered set\n\n`cap:twenty:record.restore_many` accepts a non-empty filter and optional depth:\n\n```json\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"filter\": \"id[in]:[0190c42f-2d5a-7000-8000-000000000040]\",\n    \"depth\": 0\n  }\n}\n```\n\nThe connector applies the same non-empty filter guard used by other bulk\nmutations. It does not verify that each selected record is soft-deleted or that\nthe restored set matches an earlier delete Action.\n\nUse a separate Action and key for restore. Do not reuse the soft-delete key to\nrepresent the inverse intent.\n\n## Merge duplicate records\n\n`cap:twenty:record.merge` accepts only `ids`, `conflictPriorityIndex`, and\noptional `dryRun` inside `body`:\n\n```json\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"ids\": [\n      \"0190c42f-2d5a-7000-8000-000000000040\",\n      \"0190c42f-2d5a-7000-8000-000000000041\"\n    ],\n    \"conflictPriorityIndex\": 0,\n    \"dryRun\": true\n  },\n  \"query\": {\n    \"depth\": 1\n  }\n}\n```\n\nThe ID array must contain two through nine unique UUIDs. The priority index\nmust be non-negative and smaller than the actual array length. `dryRun`, when\npresent, must be Boolean.\n\n`depth` is the only accepted query key. Additional body keys fail before\nprovider dispatch.\n\nThe connector classifies merge as a mutation even when `dryRun` is true. It\nstill requires approval and an idempotency key and still advertises unsafe\nretry. The provider owns dry-run response semantics.\n\n## Apply approval and idempotency\n\nEvery operation requires a tenant-policy decision with a reason, input\nsnapshot, and policy decision. The approval validity window is 900,000 ms.\n\nMutation keys must contain 1 through 1,024 ASCII graphic bytes. The contract\nscope is the external account, collision behavior revalidates input hash, and\nthe published TTL is 604,800,000 ms.\n\nBind approval and key to the exact:\n\n- capability and object;\n- ID, ordered merge IDs, or encoded filter;\n- merge priority and dry-run value;\n- configured external account and workspace;\n- expected target count and provider schema evidence;\n- Action input hash and policy hash.\n\nApproval does not make destruction reversible. Idempotency does not turn an\nunsafe operation into an automatically retryable one.\n\n## Interpret results\n\nA completed result contains:\n\n```json\n{\n  \"http_status\": 200,\n  \"provider_request_id\": \"provider-request-id-or-null\",\n  \"body\": {}\n}\n```\n\nThe connector passes through decoded provider JSON. It records the\nprovider-effect-committed checkpoint after a successful response.\n\nVerify actual record state with an authorized read. For bulk operations,\ncompare the observed IDs and count with the approved target set. For merge,\nverify the surviving record and intended field priority.\n\n## Handle failures and unknown outcomes\n\nAll seven operations advertise unsafe retry. Provider `429` or `5xx`, an\nambiguous transport failure, an invalid or oversized response after dispatch,\nor runtime cancellation can carry `uncertain_outcome: true`.\n\n| Observation | Response |\n|---|---|\n| Invalid object, ID, filter, merge body, key, or query | Correct input under a new governed intent |\n| Provider `401` or `403` | Repair fixed credential or workspace binding; do not replay |\n| Definitive provider `4xx` | Correct the rejected intent after checking current state |\n| Temporary or transport failure | Preserve the original Action and key; do not retry automatically |\n| Invalid provider response | Inspect current provider state before any new mutation |\n| Pending or incomplete AIP Action | Read the same Action and receipts |\n\nThe connector exposes no automatic reconciliation. Use bounded record reads\nand retained provider request identity to establish the current state before a\nseparate approved recovery action.\n\n## Security and data boundaries\n\nAll seven contracts classify input and output as confidential, possibly\ncontaining personal information, with redaction required. Delete and merge\nevidence can itself reveal sensitive record identity and retention decisions.\n\nApply least-privilege provider credentials, field and object policy, target\ncount limits, separation of duties, evidence redaction, legal-retention checks,\nand post-operation verification outside the connector.\n\n## Related documentation\n\n- [Twenty capability index](README.md)\n- [Record create, update, and batch upsert](record-create-update-and-batch-upsert.md)\n- [Record query and aggregation](record-query-and-aggregation.md)\n- [Approvals and policy](../../../concepts/approvals-and-policy.md)\n- [Errors and retry decisions](../../../reference/errors.md)\n",
    "text": "Twenty record delete, restore, and merge\n\nUse these seven mutations to change record lifecycle or consolidate duplicate\nrecords. Choose the narrowest operation, freeze its exact target, and preserve\nthe original Action and idempotency identity through verification.\n\nSoft delete, permanent destroy, restore, and merge have different provider\nconsequences. The connector does not expose transactions, compensation,\nrollback, reconciliation, cancellation, or safe retry for any of them.\n\nCompare the operations\n\n| Operation | Provider request | Risk | Target |\n\n| record.softdelete | DELETE /rest/{object}/{id}?softdelete=true | High | One ID |\n| record.softdeletemany | DELETE /rest/{object}?filter=...&softdelete=true | High | Explicit filter |\n| record.destroy | DELETE /rest/{object}/{id}?softdelete=false | Critical | One ID |\n| record.destroymany | DELETE /rest/{object}?filter=...&softdelete=false | Critical | Explicit filter |\n| record.restore | PATCH /rest/restore/{object}?filter=id[eq]:{id} | High | One ID through filter |\n| record.restoremany | PATCH /rest/restore/{object}?filter=... | High | Explicit filter |\n| record.merge | PATCH /rest/{object}/merge | High | Two through nine IDs |\n\nEvery operation requires tenant-policy approval and an external-account-scoped\nidempotency key. Every operation advertises unsafe retry.\n\nApply shared target rules\n\nEvery operation requires object matching:\n\n^[A-Za-z][A-Za-z0-9]{0,127}$\n\nSingle-record operations require a canonical RFC 4122 UUID with version 1\nthrough 5 and an RFC variant nibble. Merge applies the same check to each\nmember.\n\nThe connector fixes the provider origin, workspace, token, and path template.\nObject and filter values cannot select a different provider host.\n\nSoft-delete one record\n\ncap:twenty:record.softdelete accepts only object and ID:\n\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000040\"\n}\n\nThe connector adds softdelete=true. It accepts no query object for this\nsingle-target operation.\n\nSoft deletion represents a reversible provider lifecycle state only when the\nrecord still exists and the provider permits restore. It is not an AIP undo\ntoken or retention guarantee.\n\nSoft-delete a filtered set\n\ncap:twenty:record.softdeletemany requires a non-empty provider filter:\n\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"filter\": \"id[in]:[0190c42f-2d5a-7000-8000-000000000040]\"\n  }\n}\n\nThe connector rejects a missing filter, whitespace-only filter, literal {},\nor literal null. It does not interpret the provider filter or count matches.\n\nPreflight the same filter with an authorized read and retain the expected IDs\nand count in approval evidence. Do not widen the filter after approval.\n\nPermanently destroy one record\n\ncap:twenty:record.destroy uses the same outer input as single soft delete:\n\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000040\"\n}\n\nThe connector sends softdelete=false and classifies the operation as\ncritical. No connector capability restores the permanently destroyed record.\n\nApproval evidence should identify data-retention authority, dependencies,\nbackups, legal holds, and a separately tested recovery plan when one exists.\nAn idempotency key is not a backup.\n\nPermanently destroy a filtered set\n\ncap:twenty:record.destroymany requires an explicit non-empty filter:\n\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"filter\": \"id[in]:[0190c42f-2d5a-7000-8000-000000000040]\"\n  }\n}\n\nThe connector adds softdelete=false. The filter guard prevents an empty or\nobvious whole-collection request, but it does not prove that a syntactically\nnon-empty provider filter is narrow.\n\nTreat filter semantics, selected IDs, count, workspace schema, and permanent\nconsequences as approval inputs. Re-read the exact target set immediately\nbefore authorization when deployment policy requires it.\n\nRestore one soft-deleted record\n\ncap:twenty:record.restore accepts an object, ID, and optional depth:\n\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000040\",\n  \"query\": {\n    \"depth\": 0\n  }\n}\n\nThe pinned provider source declares a single-record restore route, but its\ncore-path parser rejects that three-segment form. The connector therefore uses\nthe filtered collection restore route with filter=id[eq]:.\n\nThis compatibility mapping has the intended single-record target. It remains a\nprovider collection request and should be requalified when the pinned provider\nrevision changes.\n\nRestore cannot recover a permanent destroy. It also does not prove that every\nrelationship, automation, or downstream index returns to its prior state.\n\nRestore a filtered set\n\ncap:twenty:record.restoremany accepts a non-empty filter and optional depth:\n\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"filter\": \"id[in]:[0190c42f-2d5a-7000-8000-000000000040]\",\n    \"depth\": 0\n  }\n}\n\nThe connector applies the same non-empty filter guard used by other bulk\nmutations. It does not verify that each selected record is soft-deleted or that\nthe restored set matches an earlier delete Action.\n\nUse a separate Action and key for restore. Do not reuse the soft-delete key to\nrepresent the inverse intent.\n\nMerge duplicate records\n\ncap:twenty:record.merge accepts only ids, conflictPriorityIndex, and\noptional dryRun inside body:\n\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"ids\": [\n      \"0190c42f-2d5a-7000-8000-000000000040\",\n      \"0190c42f-2d5a-7000-8000-000000000041\"\n    ],\n    \"conflictPriorityIndex\": 0,\n    \"dryRun\": true\n  },\n  \"query\": {\n    \"depth\": 1\n  }\n}\n\nThe ID array must contain two through nine unique UUIDs. The priority index\nmust be non-negative and smaller than the actual array length. dryRun, when\npresent, must be Boolean.\n\ndepth is the only accepted query key. Additional body keys fail before\nprovider dispatch.\n\nThe connector classifies merge as a mutation even when dryRun is true. It\nstill requires approval and an idempotency key and still advertises unsafe\nretry. The provider owns dry-run response semantics.\n\nApply approval and idempotency\n\nEvery operation requires a tenant-policy decision with a reason, input\nsnapshot, and policy decision. The approval validity window is 900,000 ms.\n\nMutation keys must contain 1 through 1,024 ASCII graphic bytes. The contract\nscope is the external account, collision behavior revalidates input hash, and\nthe published TTL is 604,800,000 ms.\n\nBind approval and key to the exact:\n• capability and object;\n• ID, ordered merge IDs, or encoded filter;\n• merge priority and dry-run value;\n• configured external account and workspace;\n• expected target count and provider schema evidence;\n• Action input hash and policy hash.\n\nApproval does not make destruction reversible. Idempotency does not turn an\nunsafe operation into an automatically retryable one.\n\nInterpret results\n\nA completed result contains:\n\n{\n  \"httpstatus\": 200,\n  \"providerrequestid\": \"provider-request-id-or-null\",\n  \"body\": {}\n}\n\nThe connector passes through decoded provider JSON. It records the\nprovider-effect-committed checkpoint after a successful response.\n\nVerify actual record state with an authorized read. For bulk operations,\ncompare the observed IDs and count with the approved target set. For merge,\nverify the surviving record and intended field priority.\n\nHandle failures and unknown outcomes\n\nAll seven operations advertise unsafe retry. Provider 429 or 5xx, an\nambiguous transport failure, an invalid or oversized response after dispatch,\nor runtime cancellation can carry uncertainoutcome: true.\n\n| Observation | Response |\n\n| Invalid object, ID, filter, merge body, key, or query | Correct input under a new governed intent |\n| Provider 401 or 403 | Repair fixed credential or workspace binding; do not replay |\n| Definitive provider 4xx | Correct the rejected intent after checking current state |\n| Temporary or transport failure | Preserve the original Action and key; do not retry automatically |\n| Invalid provider response | Inspect current provider state before any new mutation |\n| Pending or incomplete AIP Action | Read the same Action and receipts |\n\nThe connector exposes no automatic reconciliation. Use bounded record reads\nand retained provider request identity to establish the current state before a\nseparate approved recovery action.\n\nSecurity and data boundaries\n\nAll seven contracts classify input and output as confidential, possibly\ncontaining personal information, with redaction required. Delete and merge\nevidence can itself reveal sensitive record identity and retention decisions.\n\nApply least-privilege provider credentials, field and object policy, target\ncount limits, separation of duties, evidence redaction, legal-retention checks,\nand post-operation verification outside the connector.\n\nRelated documentation\n• Twenty capability index (README.md)\n• Record create, update, and batch upsert (record-create-update-and-batch-upsert.md)\n• Record query and aggregation (record-query-and-aggregation.md)\n• Approvals and policy (../../../concepts/approvals-and-policy.md)\n• Errors and retry decisions (../../../reference/errors.md)\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "7d0774d20b290a4d1557e6ce5a3a2b70c75f28e4b4e11f5166e169010af33775"
  }
}
