{
  "schemaVersion": "1.0",
  "title": "Twenty record query and aggregation",
  "description": "Use these four read operations to inspect records, fetch one stable ID, detect duplicate candidates, or request provider grouping. They accept standard and custom object names through fixed Twenty REST routes.",
  "canonical": "https://getaip.org/docs/connectors/twenty/capabilities/record-query-and-aggregation",
  "route": "/docs/connectors/twenty/capabilities/record-query-and-aggregation",
  "source": "docs/connectors/twenty/capabilities/record-query-and-aggregation.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/twenty/capabilities/record-query-and-aggregation.md",
    "txt": "/docs/download/connectors/twenty/capabilities/record-query-and-aggregation.txt",
    "json": "/docs/download/connectors/twenty/capabilities/record-query-and-aggregation.json",
    "pdf": "/docs/download/connectors/twenty/capabilities/record-query-and-aggregation.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Twenty record query and aggregation\ndescription: >-\n  Reference bounded list, get, duplicate-detection, and group-by operations for\n  standard and custom Twenty records\nkind: capability-reference\naudience: application-developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: twenty\ncapabilityIds:\n  - cap:twenty:record.list\n  - cap:twenty:record.get\n  - cap:twenty:record.find_duplicates\n  - cap:twenty:record.group_by\n---\n\n# Twenty record query and aggregation\n\nUse these four read operations to inspect records, fetch one stable ID, detect\nduplicate candidates, or request provider grouping. They accept standard and\ncustom object names through fixed Twenty REST routes.\n\nThe connector validates the outer input and query allowlist. Twenty owns field\nnames, filter grammar, ordering syntax, aggregation semantics, and the returned\nrecord shape for the selected workspace revision.\n\n## Compare the operations\n\n| Operation | Provider request | Required input | Primary result |\n|---|---|---|---|\n| `record.list` | `GET /rest/{object}` | `object` | Provider collection and pagination data |\n| `record.get` | `GET /rest/{object}/{id}` | `object`, `id` | One provider record |\n| `record.find_duplicates` | `POST /rest/{object}/duplicates` | `object`, `body` | Provider duplicate candidates |\n| `record.group_by` | `GET /rest/{object}/groupBy` | `object` | Provider groups and aggregates |\n\nAll four are low-risk reads. They require no approval and accept an optional\nidempotency key. They advertise synchronous execution and safe connector retry,\nwith no asynchronous, streaming, or cancellation support.\n\nThe POST method used by duplicate detection does not make it a mutation in the\nreviewed AIP contract.\n\n## Use a bounded object name\n\nEvery input requires `object` matching:\n\n```text\n^[A-Za-z][A-Za-z0-9_]{0,127}$\n```\n\nThis admits standard and custom identifiers without allowing slashes, dots,\nhyphens, percent escapes, or caller-selected paths. It does not prove that the\nobject exists in the configured workspace.\n\nSingle-record IDs and duplicate-ID candidates must be canonical RFC 4122 UUIDs\nwith version 1 through 5 and an RFC variant nibble.\n\n## List records\n\n`cap:twenty:record.list` accepts this input shape:\n\n```json\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"limit\": 25,\n    \"order_by\": \"createdAt[DESC]\",\n    \"depth\": 0,\n    \"filter\": \"name[like]:AIP%\"\n  }\n}\n```\n\nAllowed query keys are:\n\n| Key | Connector schema |\n|---|---|\n| `limit` | Integer from `0` through `200` |\n| `order_by` | String, array, or object |\n| `depth` | Integer `0` or `1` |\n| `filter` | String, array, or object |\n| `starting_after` | RFC 4122 UUID |\n| `ending_before` | RFC 4122 UUID |\n\nThe connector does not require a limit or cursor. Applications should choose a\nbounded limit and retain provider pagination state rather than assuming one\nresponse is complete.\n\n## Get one record\n\n`cap:twenty:record.get` requires an object and ID:\n\n```json\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000010\",\n  \"query\": {\n    \"depth\": 1\n  }\n}\n```\n\n`depth` is the only accepted query key. Omit it when nested relationships are\nnot needed. The connector sends the configured workspace credential and does\nnot add a workspace selector to the Action.\n\n## Find duplicate candidates\n\n`cap:twenty:record.find_duplicates` accepts exactly one of two body forms.\n\nCompare one through 200 candidate objects:\n\n```json\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"data\": [\n      {\n        \"name\": \"AIP Quickstart Company\"\n      }\n    ]\n  },\n  \"query\": {\n    \"depth\": 0\n  }\n}\n```\n\nOr compare one through 200 existing IDs:\n\n```json\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"ids\": [\n      \"0190c42f-2d5a-7000-8000-000000000010\"\n    ]\n  }\n}\n```\n\nThe body cannot contain both alternatives, neither alternative, or additional\ntop-level fields. Each `data` member must be an object, and each `ids` member\nmust pass the connector's UUID validation.\n\nThe connector treats this provider POST as read-only. Provider behavior still\nbelongs to the pinned mapping and selected workspace deployment.\n\n## Group records\n\n`cap:twenty:record.group_by` accepts a bounded provider grouping request:\n\n```json\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"limit\": 20,\n    \"filter\": \"employees[gte]:10\",\n    \"group_by\": \"address.city\",\n    \"aggregate\": \"count\",\n    \"include_records_sample\": false\n  }\n}\n```\n\nAllowed query keys are:\n\n| Key | Connector schema |\n|---|---|\n| `limit` | Integer from `0` through `200` |\n| `order_by` | String, array, or object |\n| `filter` | String, array, or object |\n| `group_by` | String, array, or object |\n| `include_records_sample` | Boolean |\n| `order_by_for_records` | String, array, or object |\n| `aggregate` | String, array, or object |\n| `viewId` | RFC 4122 UUID |\n\nThe connector forwards allowed values; it does not interpret provider field\npaths or prove that a named view belongs to the selected object.\n\n## Understand query encoding and bounds\n\nThe connector appends every allowed query member as one URL query pair.\n\n- strings are forwarded as strings;\n- booleans and numbers use their JSON scalar text;\n- arrays and objects use compact JSON text;\n- `null` is rejected;\n- one encoded value cannot exceed 32 KiB or contain NUL;\n- the complete encoded URL cannot exceed 128 KiB;\n- no query object can exceed the operation's fixed property set.\n\nThe common append layer has a 64-entry ceiling, while these four schemas admit\nat most eight named keys. Unknown keys fail before provider dispatch.\n\nStructured query values preserve their JSON shape as encoded text. The\nconnector does not transform them into provider filter or order expressions.\n\n## Interpret 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\n`body` is the decoded provider JSON without record-shape normalization. An\nempty response becomes `null`. A non-empty response that is invalid JSON or\nexceeds the configured response bound fails as an invalid provider response.\n\n`provider_request_id` comes from `x-request-id`, then `traceparent`, when the\nheader is valid, contains no control character, and is at most 512 bytes.\n\n## Handle failures and retry\n\n| Condition | Connector code | Retry decision |\n|---|---|---|\n| Invalid object, UUID, body, query key, value, or bound | `connector.twenty.invalid_request` | Correct input |\n| Provider `401` or `403` | `connector.twenty.authentication` | Repair fixed host credential or workspace binding |\n| Provider `429` or `5xx` | `connector.twenty.remote_temporary` | Retry the same read according to policy |\n| Other provider non-success | `connector.twenty.remote_rejected` | Correct the provider-facing request or deployment |\n| Read transport failure | `connector.twenty.transport` | Retryable for these operations |\n| Oversized or invalid JSON response | `connector.twenty.invalid_response` | Not automatically retryable |\n\nProvider error projection retains only bounded `statusCode`, `error`,\n`message`, `messages`, and `code` fields. Do not expect the complete provider\nerror body in AIP evidence.\n\nRetry the same read intent with a stable Action identity. A retryable flag does\nnot prove that provider state, schema, or pagination remained unchanged between\nattempts.\n\n## Security and data boundaries\n\nAll four contracts classify data as confidential, possibly containing personal\ninformation, with redaction required. Inputs and outputs can expose customer,\nrelationship, view, or custom-field data even though the operations are reads.\n\nThe connector fixes origin, workspace, token, and route templates. Query values\ncannot select another host, add a path segment, or replace the provider\ncredential.\n\nRetain only the fields needed for the application task. Apply tenant access,\nlog redaction, pagination bounds, and evidence retention controls outside the\nconnector.\n\n## Related documentation\n\n- [Twenty capability index](README.md)\n- [Twenty connector quickstart](../getting-started/quickstart.md)\n- [Twenty connector configuration](../reference/configuration.md)\n- [Errors and retry decisions](../../../reference/errors.md)\n- [Capabilities and contracts](../../../concepts/capabilities.md)\n",
    "text": "Twenty record query and aggregation\n\nUse these four read operations to inspect records, fetch one stable ID, detect\nduplicate candidates, or request provider grouping. They accept standard and\ncustom object names through fixed Twenty REST routes.\n\nThe connector validates the outer input and query allowlist. Twenty owns field\nnames, filter grammar, ordering syntax, aggregation semantics, and the returned\nrecord shape for the selected workspace revision.\n\nCompare the operations\n\n| Operation | Provider request | Required input | Primary result |\n\n| record.list | GET /rest/{object} | object | Provider collection and pagination data |\n| record.get | GET /rest/{object}/{id} | object, id | One provider record |\n| record.findduplicates | POST /rest/{object}/duplicates | object, body | Provider duplicate candidates |\n| record.groupby | GET /rest/{object}/groupBy | object | Provider groups and aggregates |\n\nAll four are low-risk reads. They require no approval and accept an optional\nidempotency key. They advertise synchronous execution and safe connector retry,\nwith no asynchronous, streaming, or cancellation support.\n\nThe POST method used by duplicate detection does not make it a mutation in the\nreviewed AIP contract.\n\nUse a bounded object name\n\nEvery input requires object matching:\n\n^[A-Za-z][A-Za-z0-9]{0,127}$\n\nThis admits standard and custom identifiers without allowing slashes, dots,\nhyphens, percent escapes, or caller-selected paths. It does not prove that the\nobject exists in the configured workspace.\n\nSingle-record IDs and duplicate-ID candidates must be canonical RFC 4122 UUIDs\nwith version 1 through 5 and an RFC variant nibble.\n\nList records\n\ncap:twenty:record.list accepts this input shape:\n\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"limit\": 25,\n    \"orderby\": \"createdAt[DESC]\",\n    \"depth\": 0,\n    \"filter\": \"name[like]:AIP%\"\n  }\n}\n\nAllowed query keys are:\n\n| Key | Connector schema |\n\n| limit | Integer from 0 through 200 |\n| orderby | String, array, or object |\n| depth | Integer 0 or 1 |\n| filter | String, array, or object |\n| startingafter | RFC 4122 UUID |\n| endingbefore | RFC 4122 UUID |\n\nThe connector does not require a limit or cursor. Applications should choose a\nbounded limit and retain provider pagination state rather than assuming one\nresponse is complete.\n\nGet one record\n\ncap:twenty:record.get requires an object and ID:\n\n{\n  \"object\": \"companies\",\n  \"id\": \"0190c42f-2d5a-7000-8000-000000000010\",\n  \"query\": {\n    \"depth\": 1\n  }\n}\n\ndepth is the only accepted query key. Omit it when nested relationships are\nnot needed. The connector sends the configured workspace credential and does\nnot add a workspace selector to the Action.\n\nFind duplicate candidates\n\ncap:twenty:record.findduplicates accepts exactly one of two body forms.\n\nCompare one through 200 candidate objects:\n\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"data\": [\n      {\n        \"name\": \"AIP Quickstart Company\"\n      }\n    ]\n  },\n  \"query\": {\n    \"depth\": 0\n  }\n}\n\nOr compare one through 200 existing IDs:\n\n{\n  \"object\": \"companies\",\n  \"body\": {\n    \"ids\": [\n      \"0190c42f-2d5a-7000-8000-000000000010\"\n    ]\n  }\n}\n\nThe body cannot contain both alternatives, neither alternative, or additional\ntop-level fields. Each data member must be an object, and each ids member\nmust pass the connector's UUID validation.\n\nThe connector treats this provider POST as read-only. Provider behavior still\nbelongs to the pinned mapping and selected workspace deployment.\n\nGroup records\n\ncap:twenty:record.groupby accepts a bounded provider grouping request:\n\n{\n  \"object\": \"companies\",\n  \"query\": {\n    \"limit\": 20,\n    \"filter\": \"employees[gte]:10\",\n    \"groupby\": \"address.city\",\n    \"aggregate\": \"count\",\n    \"includerecordssample\": false\n  }\n}\n\nAllowed query keys are:\n\n| Key | Connector schema |\n\n| limit | Integer from 0 through 200 |\n| orderby | String, array, or object |\n| filter | String, array, or object |\n| groupby | String, array, or object |\n| includerecordssample | Boolean |\n| orderbyforrecords | String, array, or object |\n| aggregate | String, array, or object |\n| viewId | RFC 4122 UUID |\n\nThe connector forwards allowed values; it does not interpret provider field\npaths or prove that a named view belongs to the selected object.\n\nUnderstand query encoding and bounds\n\nThe connector appends every allowed query member as one URL query pair.\n• strings are forwarded as strings;\n• booleans and numbers use their JSON scalar text;\n• arrays and objects use compact JSON text;\n• null is rejected;\n• one encoded value cannot exceed 32 KiB or contain NUL;\n• the complete encoded URL cannot exceed 128 KiB;\n• no query object can exceed the operation's fixed property set.\n\nThe common append layer has a 64-entry ceiling, while these four schemas admit\nat most eight named keys. Unknown keys fail before provider dispatch.\n\nStructured query values preserve their JSON shape as encoded text. The\nconnector does not transform them into provider filter or order expressions.\n\nInterpret 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\nbody is the decoded provider JSON without record-shape normalization. An\nempty response becomes null. A non-empty response that is invalid JSON or\nexceeds the configured response bound fails as an invalid provider response.\n\nproviderrequestid comes from x-request-id, then traceparent, when the\nheader is valid, contains no control character, and is at most 512 bytes.\n\nHandle failures and retry\n\n| Condition | Connector code | Retry decision |\n\n| Invalid object, UUID, body, query key, value, or bound | connector.twenty.invalidrequest | Correct input |\n| Provider 401 or 403 | connector.twenty.authentication | Repair fixed host credential or workspace binding |\n| Provider 429 or 5xx | connector.twenty.remotetemporary | Retry the same read according to policy |\n| Other provider non-success | connector.twenty.remoterejected | Correct the provider-facing request or deployment |\n| Read transport failure | connector.twenty.transport | Retryable for these operations |\n| Oversized or invalid JSON response | connector.twenty.invalidresponse | Not automatically retryable |\n\nProvider error projection retains only bounded statusCode, error,\nmessage, messages, and code fields. Do not expect the complete provider\nerror body in AIP evidence.\n\nRetry the same read intent with a stable Action identity. A retryable flag does\nnot prove that provider state, schema, or pagination remained unchanged between\nattempts.\n\nSecurity and data boundaries\n\nAll four contracts classify data as confidential, possibly containing personal\ninformation, with redaction required. Inputs and outputs can expose customer,\nrelationship, view, or custom-field data even though the operations are reads.\n\nThe connector fixes origin, workspace, token, and route templates. Query values\ncannot select another host, add a path segment, or replace the provider\ncredential.\n\nRetain only the fields needed for the application task. Apply tenant access,\nlog redaction, pagination bounds, and evidence retention controls outside the\nconnector.\n\nRelated documentation\n• Twenty capability index (README.md)\n• Twenty connector quickstart (../getting-started/quickstart.md)\n• Twenty connector configuration (../reference/configuration.md)\n• Errors and retry decisions (../../../reference/errors.md)\n• Capabilities and contracts (../../../concepts/capabilities.md)\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "a3cd4fc3f566893de6657394bad9004242c266da06eabd11288ee449c67099ec"
  }
}
