{
  "schemaVersion": "1.0",
  "title": "Twenty record create, update, and batch upsert",
  "description": "Use these four mutations to create or update standard and custom Twenty records. The connector binds each request to a fixed workspace, requires approval and idempotency, and adds deterministic create IDs when needed.",
  "canonical": "https://getaip.org/docs/connectors/twenty/capabilities/record-create-update-and-batch-upsert",
  "route": "/docs/connectors/twenty/capabilities/record-create-update-and-batch-upsert",
  "source": "docs/connectors/twenty/capabilities/record-create-update-and-batch-upsert.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/twenty/capabilities/record-create-update-and-batch-upsert.md",
    "txt": "/docs/download/connectors/twenty/capabilities/record-create-update-and-batch-upsert.txt",
    "json": "/docs/download/connectors/twenty/capabilities/record-create-update-and-batch-upsert.json",
    "pdf": "/docs/download/connectors/twenty/capabilities/record-create-update-and-batch-upsert.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Twenty record create, update, and batch upsert\ndescription: >-\n  Reference deterministic create, batch create, single update, and filtered\n  bulk update operations\nkind: capability-reference\naudience: application-developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: twenty\ncapabilityIds:\n  - cap:twenty:record.create\n  - cap:twenty:record.create_many\n  - cap:twenty:record.update\n  - cap:twenty:record.update_many\n---\n\n# Twenty record create, update, and batch upsert\n\nUse these four mutations to create or update standard and custom Twenty\nrecords. The connector binds each request to a fixed workspace, requires\napproval and idempotency, and adds deterministic create IDs when needed.\n\nThe connector validates the outer request and safety guards. Twenty owns the\ncurrent object schema, writable fields, relation semantics, automation, and\nthe provider-shaped response.\n\n## Compare the operations\n\n| Operation | Provider request | Required input | Retry contract |\n|---|---|---|---|\n| `record.create` | `POST /rest/{object}?upsert=true` | `object`, object `body` | Safe with original key |\n| `record.create_many` | `POST /rest/batch/{object}?upsert=true` | `object`, array `body` | Safe with original key |\n| `record.update` | `PATCH /rest/{object}/{id}` | `object`, `id`, object `body` | Safe with original key |\n| `record.update_many` | `PATCH /rest/{object}?filter=...` | `object`, object `body`, non-empty `filter` | Unsafe |\n\nAll four are high-risk writes. They require tenant-policy approval and an\nidempotency key and support only synchronous, non-streaming execution.\n\nNo operation exposes cancellation, transaction planning, reconciliation,\ncompensation, or provider rollback.\n\n## Apply the shared input bounds\n\nEvery operation requires `object` matching:\n\n```text\n^[A-Za-z][A-Za-z0-9_]{0,127}$\n```\n\nAn object body may contain at most 2,048 top-level properties. Serialized\nprovider request JSON cannot exceed 16 MiB.\n\nEvery supplied record `id` must be a canonical RFC 4122 UUID with version 1\nthrough 5 and an RFC variant nibble. A valid identifier still does not prove\nthat a record belongs to the configured workspace.\n\n## Create one deterministic upsert\n\n`cap:twenty:record.create` accepts an object body and optional `depth` query:\n\n```json\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"name\": \"AIP Example Company\"\n  },\n  \"query\": {\n    \"depth\": 0\n  }\n}\n```\n\nThe connector always adds provider `upsert=true`. If `body.id` is absent, it\nderives this UUID-v5 input string:\n\n```text\naip:twenty:<workspace_uuid>:<idempotency_key>:0\n```\n\nThe UUID namespace is `Uuid::NAMESPACE_URL`. The configured workspace and the\noriginal key therefore target the same create identity across equivalent\nattempts.\n\nIf the caller supplies `body.id`, the connector validates and preserves it.\nIt does not compare an explicit ID with the deterministic ID.\n\nForced upsert and deterministic targeting reduce duplicate-create risk. They\ndo not grant authorization, validate the provider body, or establish global\nexactly-once behavior.\n\n## Create a deterministic batch\n\n`cap:twenty:record.create_many` accepts one through 200 object bodies:\n\n```json\n{\n  \"object\": \"companies\",\n  \"body\": [\n    {\n      \"name\": \"AIP Example Company One\"\n    },\n    {\n      \"id\": \"0190c42f-2d5a-7000-8000-000000000020\",\n      \"name\": \"AIP Example Company Two\"\n    }\n  ],\n  \"query\": {\n    \"depth\": 0\n  }\n}\n```\n\nThe connector preserves each valid explicit ID. For a body without `id`, it\nderives UUID v5 from workspace, key, and the zero-based array index.\n\n```text\naip:twenty:<workspace_uuid>:<idempotency_key>:<index>\n```\n\nArray order is part of deterministic identity. Reordering, inserting, or\nremoving members changes index ownership and requires a new governed intent and\nkey.\n\nThe connector forces provider `upsert=true` for the complete batch. It does\nnot promise that the provider commits all records atomically.\n\n## Update one record\n\n`cap:twenty:record.update` requires the target ID separately from the body:\n\n```json\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000020\",\n  \"body\": {\n    \"name\": \"AIP Example Company Renamed\"\n  },\n  \"query\": {\n    \"depth\": 1\n  }\n}\n```\n\n`depth` is the only optional query key. The connector does not reject an empty\nbody at its outer layer; deployment policy should require a meaningful change\nbefore approval.\n\nSingle update advertises retry safety only with the original idempotency key\nand unchanged canonical input. A new key represents a different AIP intent.\n\n## Update records selected by a filter\n\n`cap:twenty:record.update_many` requires a non-empty provider filter:\n\n```json\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"accountOwnerId\": \"0190c42f-2d5a-7000-8000-000000000030\"\n  },\n  \"query\": {\n    \"filter\": \"id[in]:[0190c42f-2d5a-7000-8000-000000000020]\",\n    \"depth\": 0\n  }\n}\n```\n\nThe connector rejects a missing filter, whitespace-only filter, literal `{}`,\nor literal `null`. It does not evaluate the provider filter or calculate the\nmatching record count.\n\nBulk update advertises unsafe retry even though a key is required. Preserve the\noriginal Action and inspect provider state after any ambiguous result.\n\nUse a preflight read to bound the target set. Approval should retain the exact\nfilter, expected count, selected fields, and current workspace schema evidence.\n\n## Apply approval\n\nEvery operation requires a tenant-policy decision with:\n\n- a reason;\n- an input snapshot;\n- a policy decision;\n- a policy hash matching the frozen request;\n- an authenticated approver accepted by the deployment.\n\nThe approval window is 900,000 ms. Approval resumes the same queued Action and\ndoes not authorize changed input or a replacement Action.\n\nThe policy reason states that Twenty mutations can change CRM records, schema,\nlayouts, or webhook delivery policy. These four operations change CRM records,\nbut provider automation can cause wider consequences.\n\n## Apply idempotency\n\nMutation keys must contain 1 through 1,024 ASCII graphic bytes. The contract\nscope is the external account, collision behavior revalidates the input hash,\nand the published TTL is 604,800,000 ms.\n\nThe connector forwards the key as `Idempotency-Key`. It also uses the key for\nmissing create IDs, but not for explicit update target selection.\n\nKeep these values together:\n\n- Action ID;\n- capability ID;\n- canonical input and hash;\n- configured external account and workspace;\n- idempotency key;\n- approval ID and policy hash;\n- provider request ID and terminal result.\n\nNever reuse a key for another object, body, array order, filter, target, or\nworkspace intent.\n\n## Interpret successful results\n\nA completed result has this connector-owned wrapper:\n\n```json\n{\n  \"http_status\": 200,\n  \"provider_request_id\": \"provider-request-id-or-null\",\n  \"body\": {}\n}\n```\n\nThe body is decoded provider JSON without record-shape normalization. The\nconnector records its provider-effect-committed checkpoint after a successful\nresponse and before constructing the completed result.\n\nA successful provider HTTP response establishes the returned provider outcome.\nIt does not prove that every automation, notification, webhook, or integration\ntriggered by the record change completed.\n\n## Handle failure and uncertainty\n\n| Condition | Retryable | Uncertain outcome |\n|---|---:|---:|\n| Invalid input, key, object, body, UUID, or filter | No | No |\n| Provider `401` or `403` | No | No |\n| Other provider `4xx` | No | No |\n| Provider `429` or `5xx`, create/create-many/update | Yes | Yes |\n| Provider `429` or `5xx`, update-many | No | Yes |\n| Connect failure before provider connection | Create/create-many/update only | No |\n| Other transport failure | Create/create-many/update only when source classifies it safe | Yes |\n| Oversized or invalid provider response | No | Yes |\n| Runtime cancellation branch | No | Yes |\n\n`retryable` and `uncertain_outcome` answer different questions. A retry-safe\nmutation still requires the original identity and key. An unsafe uncertain\nmutation requires reconciliation before another write.\n\nThe connector exposes no reconciliation operation. Read the original Action,\nretain provider request identity, and inspect the actual provider record or\nbounded target set through an authorized read.\n\n## Security and data boundaries\n\nAll four contracts classify input and output as confidential, possibly\ncontaining personal information, with redaction required. Arbitrary writable\nrecord bodies can include tenant-defined sensitive fields.\n\nThe connector fixes origin, workspace, token, and route templates. It does not\napply field-level authorization, provider schema migrations, data residency,\nor retention policy.\n\nApply least-privilege provider scopes, object and field allowlists, target-count\nlimits, approval separation, evidence redaction, and post-write verification at\nthe deployment and application layers.\n\n## Related documentation\n\n- [Twenty capability index](README.md)\n- [Record query and aggregation](record-query-and-aggregation.md)\n- [Twenty connector quickstart](../getting-started/quickstart.md)\n- [Approvals and policy](../../../concepts/approvals-and-policy.md)\n- [Errors and retry decisions](../../../reference/errors.md)\n",
    "text": "Twenty record create, update, and batch upsert\n\nUse these four mutations to create or update standard and custom Twenty\nrecords. The connector binds each request to a fixed workspace, requires\napproval and idempotency, and adds deterministic create IDs when needed.\n\nThe connector validates the outer request and safety guards. Twenty owns the\ncurrent object schema, writable fields, relation semantics, automation, and\nthe provider-shaped response.\n\nCompare the operations\n\n| Operation | Provider request | Required input | Retry contract |\n\n| record.create | POST /rest/{object}?upsert=true | object, object body | Safe with original key |\n| record.createmany | POST /rest/batch/{object}?upsert=true | object, array body | Safe with original key |\n| record.update | PATCH /rest/{object}/{id} | object, id, object body | Safe with original key |\n| record.updatemany | PATCH /rest/{object}?filter=... | object, object body, non-empty filter | Unsafe |\n\nAll four are high-risk writes. They require tenant-policy approval and an\nidempotency key and support only synchronous, non-streaming execution.\n\nNo operation exposes cancellation, transaction planning, reconciliation,\ncompensation, or provider rollback.\n\nApply the shared input bounds\n\nEvery operation requires object matching:\n\n^[A-Za-z][A-Za-z0-9]{0,127}$\n\nAn object body may contain at most 2,048 top-level properties. Serialized\nprovider request JSON cannot exceed 16 MiB.\n\nEvery supplied record id must be a canonical RFC 4122 UUID with version 1\nthrough 5 and an RFC variant nibble. A valid identifier still does not prove\nthat a record belongs to the configured workspace.\n\nCreate one deterministic upsert\n\ncap:twenty:record.create accepts an object body and optional depth query:\n\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"name\": \"AIP Example Company\"\n  },\n  \"query\": {\n    \"depth\": 0\n  }\n}\n\nThe connector always adds provider upsert=true. If body.id is absent, it\nderives this UUID-v5 input string:\n\naip:twenty:::0\n\nThe UUID namespace is Uuid::NAMESPACEURL. The configured workspace and the\noriginal key therefore target the same create identity across equivalent\nattempts.\n\nIf the caller supplies body.id, the connector validates and preserves it.\nIt does not compare an explicit ID with the deterministic ID.\n\nForced upsert and deterministic targeting reduce duplicate-create risk. They\ndo not grant authorization, validate the provider body, or establish global\nexactly-once behavior.\n\nCreate a deterministic batch\n\ncap:twenty:record.createmany accepts one through 200 object bodies:\n\n{\n  \"object\": \"companies\",\n  \"body\": [\n    {\n      \"name\": \"AIP Example Company One\"\n    },\n    {\n      \"id\": \"0190c42f-2d5a-7000-8000-000000000020\",\n      \"name\": \"AIP Example Company Two\"\n    }\n  ],\n  \"query\": {\n    \"depth\": 0\n  }\n}\n\nThe connector preserves each valid explicit ID. For a body without id, it\nderives UUID v5 from workspace, key, and the zero-based array index.\n\naip:twenty:::\n\nArray order is part of deterministic identity. Reordering, inserting, or\nremoving members changes index ownership and requires a new governed intent and\nkey.\n\nThe connector forces provider upsert=true for the complete batch. It does\nnot promise that the provider commits all records atomically.\n\nUpdate one record\n\ncap:twenty:record.update requires the target ID separately from the body:\n\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000020\",\n  \"body\": {\n    \"name\": \"AIP Example Company Renamed\"\n  },\n  \"query\": {\n    \"depth\": 1\n  }\n}\n\ndepth is the only optional query key. The connector does not reject an empty\nbody at its outer layer; deployment policy should require a meaningful change\nbefore approval.\n\nSingle update advertises retry safety only with the original idempotency key\nand unchanged canonical input. A new key represents a different AIP intent.\n\nUpdate records selected by a filter\n\ncap:twenty:record.updatemany requires a non-empty provider filter:\n\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"accountOwnerId\": \"0190c42f-2d5a-7000-8000-000000000030\"\n  },\n  \"query\": {\n    \"filter\": \"id[in]:[0190c42f-2d5a-7000-8000-000000000020]\",\n    \"depth\": 0\n  }\n}\n\nThe connector rejects a missing filter, whitespace-only filter, literal {},\nor literal null. It does not evaluate the provider filter or calculate the\nmatching record count.\n\nBulk update advertises unsafe retry even though a key is required. Preserve the\noriginal Action and inspect provider state after any ambiguous result.\n\nUse a preflight read to bound the target set. Approval should retain the exact\nfilter, expected count, selected fields, and current workspace schema evidence.\n\nApply approval\n\nEvery operation requires a tenant-policy decision with:\n• a reason;\n• an input snapshot;\n• a policy decision;\n• a policy hash matching the frozen request;\n• an authenticated approver accepted by the deployment.\n\nThe approval window is 900,000 ms. Approval resumes the same queued Action and\ndoes not authorize changed input or a replacement Action.\n\nThe policy reason states that Twenty mutations can change CRM records, schema,\nlayouts, or webhook delivery policy. These four operations change CRM records,\nbut provider automation can cause wider consequences.\n\nApply idempotency\n\nMutation keys must contain 1 through 1,024 ASCII graphic bytes. The contract\nscope is the external account, collision behavior revalidates the input hash,\nand the published TTL is 604,800,000 ms.\n\nThe connector forwards the key as Idempotency-Key. It also uses the key for\nmissing create IDs, but not for explicit update target selection.\n\nKeep these values together:\n• Action ID;\n• capability ID;\n• canonical input and hash;\n• configured external account and workspace;\n• idempotency key;\n• approval ID and policy hash;\n• provider request ID and terminal result.\n\nNever reuse a key for another object, body, array order, filter, target, or\nworkspace intent.\n\nInterpret successful results\n\nA completed result has this connector-owned wrapper:\n\n{\n  \"httpstatus\": 200,\n  \"providerrequestid\": \"provider-request-id-or-null\",\n  \"body\": {}\n}\n\nThe body is decoded provider JSON without record-shape normalization. The\nconnector records its provider-effect-committed checkpoint after a successful\nresponse and before constructing the completed result.\n\nA successful provider HTTP response establishes the returned provider outcome.\nIt does not prove that every automation, notification, webhook, or integration\ntriggered by the record change completed.\n\nHandle failure and uncertainty\n\n| Condition | Retryable | Uncertain outcome |\n\n| Invalid input, key, object, body, UUID, or filter | No | No |\n| Provider 401 or 403 | No | No |\n| Other provider 4xx | No | No |\n| Provider 429 or 5xx, create/create-many/update | Yes | Yes |\n| Provider 429 or 5xx, update-many | No | Yes |\n| Connect failure before provider connection | Create/create-many/update only | No |\n| Other transport failure | Create/create-many/update only when source classifies it safe | Yes |\n| Oversized or invalid provider response | No | Yes |\n| Runtime cancellation branch | No | Yes |\n\nretryable and uncertainoutcome answer different questions. A retry-safe\nmutation still requires the original identity and key. An unsafe uncertain\nmutation requires reconciliation before another write.\n\nThe connector exposes no reconciliation operation. Read the original Action,\nretain provider request identity, and inspect the actual provider record or\nbounded target set through an authorized read.\n\nSecurity and data boundaries\n\nAll four contracts classify input and output as confidential, possibly\ncontaining personal information, with redaction required. Arbitrary writable\nrecord bodies can include tenant-defined sensitive fields.\n\nThe connector fixes origin, workspace, token, and route templates. It does not\napply field-level authorization, provider schema migrations, data residency,\nor retention policy.\n\nApply least-privilege provider scopes, object and field allowlists, target-count\nlimits, approval separation, evidence redaction, and post-write verification at\nthe deployment and application layers.\n\nRelated documentation\n• Twenty capability index (README.md)\n• Record query and aggregation (record-query-and-aggregation.md)\n• Twenty connector quickstart (../getting-started/quickstart.md)\n• Approvals and policy (../../../concepts/approvals-and-policy.md)\n• Errors and retry decisions (../../../reference/errors.md)\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "bba789540c7d6fad192827ee9887f5ed6a2b167b709bbf2a211ad2b637642fb8"
  }
}
