POST /api/v1/deals
Create or update up to 1,000 deals in a single request. Each row is matched and upserted by externalId; rows with no match are created.
Request
POST /api/v1/deals HTTP/1.1
Host: agentlyleads.com
Authorization: Bearer alk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"deals": [
{
"name": "Acme Corp — Annual Contract",
"value": 12000,
"stage": "QUALIFIED",
"expectedCloseDate": "2026-09-30",
"externalId": "hs_deal_789"
},
{
"name": "Big Order",
"value": 500,
"stage": "NEW",
"contactExternalId": "hs_contact_123"
}
]
}The deals array must contain at least 1 item and at most 1,000.
Response — 200 (all rows succeeded)
When every row in the batch succeeds, the response status is 200:
{
"created": 1,
"updated": 1,
"errors": 0,
"results": [
{ "index": 0, "status": "created", "id": "cmq1abc2def3ghi4jkl5" },
{ "index": 1, "status": "updated", "id": "cmq6mno7pqr8stu9vwx0" }
]
}Each entry in results corresponds to the deal at that position in your request array (zero-indexed).
Response — 207 Multi-Status (partial success)
When at least one row fails validation, the response status is 207. Failed rows appear in results with "status": "error":
{
"created": 1,
"updated": 0,
"errors": 1,
"results": [
{ "index": 0, "status": "created", "id": "cmq1abc2def3ghi4jkl5" },
{
"index": 1,
"status": "error",
"error": "name: Required"
}
]
}A row that fails validation does not abort the batch. Valid rows are written; failed rows are skipped and reported per-index in results.
Merge on update
When a row matches an existing deal (by externalId), only the fields present in your request body are updated. Fields you omit retain their current values — this is a merge update, not a full replace.
For example, sending only { "externalId": "hs_deal_789", "value": 15000 } updates the value without touching name, stage, notes, or any other field.
Linking contacts, companies, and offerings
A deal can be linked to a contact, company, and/or offering either by their agentlyleads record ID (contactId/companyId/offeringId) or by an external system's ID (contactExternalId/companyExternalId/offeringExternalId), which the server resolves against that record's own externalId:
{
"deals": [
{
"name": "Big Order",
"value": 500,
"contactExternalId": "hs_contact_123",
"companyExternalId": "hs_company_456"
}
]
}If an *ExternalId is supplied but no matching record is found in the workspace, that row fails — it does not create the deal unlinked.
Quickstart
export AL_KEY="alk_live_xxx"
# Push your deals — run this on a schedule to keep them in sync
curl -s -X POST https://agentlyleads.com/api/v1/deals \
-H "Authorization: Bearer $AL_KEY" \
-H "Content-Type: application/json" \
-d '{
"deals": [
{
"name": "Acme Corp — Annual Contract",
"value": 12000,
"stage": "QUALIFIED",
"externalId": "hs_deal_789"
}
]
}'A typical integration is a nightly job in your CRM or e-commerce backend that reads its deal pipeline and POSTs it here. The first run creates everything; every later run updates by externalId.