To sync enriched accounts from a go-to-market pipeline into Attio, run Cargo’s Attio upsertRecords action with domains as the matching attribute: it updates the company with that domain, creates one when none exists, and fails the row instead of writing when the value matches more than one record. When you already store the Attio record_id, update by ID with updateRecords.
Both actions call Attio’s own endpoints: upsert is Attio’s assert endpoint (PUT /v2/objects/{object}/records with a matching_attribute), and update is a PATCH on one record ID. Neither carries a credit price, and Cargo’s Attio connector paces its calls at 25 per second, Attio’s write limit, so a large batch is spread out rather than rejected.
The write actions #
| Action | Matches on | No match | One match | Several matches |
|---|---|---|---|---|
upsertRecords | matchingPropertyName + matchingValue | Creates a record | Updates it | Attio rejects the write; the row fails |
updateRecords | id (the Attio record ID) | The row fails | Updates it | Not possible: an ID names one record |
insertRecord | Nothing | Creates a record | n/a | n/a (fails if a unique attribute clashes) |
All three take an objectType and a list of mappings, each an Attio attribute slug (propertyName) and the value to write. The object can be given by slug (companies, people) or by object ID.
Three details decide how you use them:
- Match on a unique attribute. Attio accepts any attribute as the match key but rejects a value that matches more than one record, with the error code
multiple_match_results. For companies,domainsis the only unique attribute; for people, useemail_addresses. Matching onnameworks until two companies share one, and then that row fails rather than writing onto either. record_idas the match key routes to an update. Pass"matchingPropertyName":"record_id"toupsertRecordsand Cargo updates that record by ID instead of asserting. It never creates a record in that case, and an ID that no longer exists fails the row.- Multi-value attributes behave differently by action. On upsert, the matching attribute only gains values, but every other multi-select attribute you map is replaced to match what you sent. On update, multi-select values you send are added to the existing ones. There is no skip-if-filled option on Attio mappings, so map only the attributes your pipeline owns.
Run it from the CLI #
Install the CLI, sign in, and connect Attio. Without --config, connector create opens Attio’s OAuth consent in a browser and waits:
npm install -g @cargo-ai/cli
cargo-ai login --email you@company.com
cargo-ai connection connector create --integration-slug attio
cargo-ai orchestration action list upsert --kind connector --integration-slug attio
The action list result carries the action object, including your Attio connector, to pass to the commands below.
1. Accounts without an Attio ID: upsert by domain.
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"attio","actionSlug":"upsertRecords"}' \
--records '[{"objectType":"companies","matchingPropertyName":"domains","matchingValue":["acme.com"],"mappings":[{"propertyName":"name","value":"Acme"},{"propertyName":"description","value":"Payroll API for marketplaces"}]}]' \
--limit 5 --wait-until-finished
--limit 5 runs a pilot on the first five records so you can check them in Attio before the full set. Drop it for the full run.
2. Accounts with an Attio ID: update by ID.
cargo-ai orchestration action execute-batch \
--action '{"kind":"connector","integrationSlug":"attio","actionSlug":"updateRecords"}' \
--records '[{"objectType":"companies","id":"bf071e1f-6035-429d-b874-d83ea64ea13b","mappings":[{"propertyName":"description","value":"Payroll API for marketplaces"}]}]' \
--wait-until-finished
3. Collect what failed. Download the results with cargo-ai orchestration batch download --uuid <batch uuid>. A row that failed on multiple_match_results is a duplicate in Attio: send it to a person rather than retrying it with a weaker key, because picking one of two duplicates by rule is how a sync writes enrichment onto the wrong account.
What comes back #
Each action returns one flat record: record_id, object_id, then one key per attribute of the object, by its slug. Multi-select attributes come back as arrays, single-value ones as a value or null. Store record_id on your side after the first sync, so the next run can update by ID and skip the match altogether.
To check whether a record exists without writing, the connector also has matchRecord, which returns match (true or false) and the first matching record, and findRecords, which returns up to 100 records matching any of several attribute values.
The same job on HubSpot, where duplicates are all updated rather than refused, is in sync enriched accounts to HubSpot. The broader rules for keeping a CRM clean once data flows into it are in CRM hygiene as code, and the CRM enrichment cookbook fills the identifiers this matching depends on.
Frequently asked questions #
Yes. The Attio connector’s upsertRecords action calls Attio’s assert
endpoint: it updates the record whose matching attribute equals your value
and creates one when none does. For companies, match on domains.
Nothing is written. Attio rejects an assert whose value matches more than
one record with multiple_match_results, and that row of the Cargo batch
fails. Resolve the duplicate in Attio, then rerun the row.
No. Both updateRecords and upsertRecords with record_id as the match
key send a PATCH to that ID, and an ID that no longer exists fails the
row.
No. The Attio actions carry no credit price. You need an Attio workspace connected through OAuth.