Notes

Create and update approved notes on confirmed customers, suppliers, and products.

Create a note

POST /notes

Required scope:

notes:write

Required header:

Idempotency-Key: <stable-key>

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 approval thread",
    "body": "Jordan approved adding the new contact and 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 approved 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:write

The response includes the latest updatedAt value to use as If-Match for updates.

Update a note

PATCH /notes/{id}

Required header:

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

Sparse patch fields:

{
  "title": "Updated title",
  "body": "Updated approved 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 are not valid note subjects or linked records in v1. Use order search results only as human-readable context in the note body.