Notes

List, read, create, and update supported CRM notes on confirmed customers, suppliers, and products.

List subject notes

GET /notes

Required scope:

notes:read

GET /notes requires a visible subject and returns metadata only. It is not a global note search.

curl "$SHELFCYCLE_API_BASE_URL/notes?primarySubjectType=customer&primarySubjectId=customer-id&limit=10" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"

Fetch the body only after selecting one visible note with GET /notes/{id}.

Create a note

POST /notes

Required scope:

notes:write

Required header:

Idempotency-Key: <stable-key>

Add dryRun=true to check the exact intended note write without mutating records.

primarySubject can be customer, supplier, or product.

linkedRecords can be customer, supplier, product, contact, or location. ShelfCycle renders those records as mention and backlink rows and dedupes them with the primary subject.

Supported noteType values:

NOTE, EMAIL, PHONE_MEETING, VIRTUAL_MEETING, PHYSICAL_MEETING, SAMPLE_REQUEST, OPPORTUNITY, QUOTE

Request

curl "$SHELFCYCLE_API_BASE_URL/notes" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: gmail-thread-123:customer-id:create-note" \
  -d '{
    "primarySubject": { "type": "customer", "id": "customer-id" },
    "linkedRecords": [
      { "type": "contact", "id": "contact-id" },
      { "type": "product", "id": "product-id" },
      { "type": "supplier", "id": "supplier-id" }
    ],
    "noteType": "EMAIL",
    "title": "Inbound product thread",
    "body": "Jordan asked for follow-up on acetone availability.",
    "happenedAt": "2026-05-28T16:00:00.000Z"
  }'

Response

{
  "data": {
    "id": "note-id",
    "type": "note",
    "url": "https://app.shelfcycle.com/org-northstar/notes/note-id",
    "createdAt": "2026-05-29T14:35:00.000Z",
    "updatedAt": "2026-05-29T14:35:00.000Z",
    "createdBy": { "type": "user", "id": "user-id", "displayName": "Jordan Buyer" },
    "createdVia": { "type": "public_api", "actorUserId": "user-id" },
    "requestedVia": { "type": "api_key", "id": "api-key-id", "purpose": "Workflow for creating notes from source emails" },
    "idempotencyStatus": "created"
  }
}

Repeating the same route plus Idempotency-Key with the same request body in the same org returns the existing note with idempotencyStatus: "replayed" and does not mutate the existing note.

Read a note

GET /notes/{id}

Required scope:

notes:read

For v1 compatibility, notes:write can read API-created notes. The response includes the latest updatedAt value to use as If-Match for updates.

Update a note

PATCH /notes/{id}

Required scope:

notes:write

Required header:

If-Match: <updatedAt from latest GET /notes/{id}>

Sparse patch fields:

{
  "title": "Updated title",
  "body": "Updated note body.",
  "happenedAt": "2026-05-28T18:00:00.000Z",
  "noteType": "PHONE_MEETING",
  "linkedRecords": [
    { "type": "customer", "id": "customer-id" },
    { "type": "contact", "id": "contact-id" }
  ]
}

PATCH only updates notes created through the public API for the same org. It cannot change the primary subject, creator, org, idempotency identity, archive state, or deletion state.

Warning

Orders and purchase orders are not valid note subjects or linked records in v1.20. Use order document context as plain text in the note body.