agentlyleads docs
Notes API

POST /api/v1/notes

Create or update up to 1,000 notes in a single request. Each row is matched and upserted by externalId; rows with no match are created.

Request

POST /api/v1/notes HTTP/1.1
Host: agentlyleads.com
Authorization: Bearer alk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "notes": [
    {
      "body": "Follow up next week about the renewal.",
      "pinned": true,
      "externalId": "hs_note_321"
    },
    {
      "body": "Sent the revised proposal.",
      "dealExternalId": "hs_deal_789"
    }
  ]
}

The notes 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 note 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": "dealExternalId: no deal found for \"NOPE\"."
    }
  ]
}

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 note (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_note_321", "pinned": false } updates the pinned state without touching body, or any linked contact/deal/company.

Linking a contact, deal, or company

A note can be linked to a contact, deal, and/or company either by their agentlyleads record ID (contactId/dealId/companyId) or by an external system's ID (contactExternalId/dealExternalId/companyExternalId), which the server resolves against that record's own externalId:

{
  "notes": [
    {
      "body": "Champion moved teams, keep an eye on the deal.",
      "contactExternalId": "hs_contact_123",
      "dealExternalId": "hs_deal_789"
    }
  ]
}

If an *ExternalId is supplied but no matching record is found in the workspace, that row fails — it does not create the note unlinked.

Recommended push order: the contact, deal, or company a note links to must already exist in the workspace before you push the note. If you're syncing multiple entity types from an external system, push companies → contacts → deals first, then notes, so every *ExternalId resolves on the first pass.

Quickstart

export AL_KEY="alk_live_xxx"

# Push your notes — run this on a schedule to keep them in sync
curl -s -X POST https://agentlyleads.com/api/v1/notes \
  -H "Authorization: Bearer $AL_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notes": [
      {
        "body": "Follow up next week about the renewal.",
        "dealExternalId": "hs_deal_789",
        "externalId": "hs_note_321"
      }
    ]
  }'

A typical integration is a nightly job that reads notes/activity from an external CRM and POSTs them here, linking each one to the contact, deal, or company it was already synced against. The first run creates everything; every later run updates by externalId.

On this page