# Notes

List, read, create, and update CRM notes.

Canonical HTML: https://developer.shelfcycle.com/guides/notes

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

## List subject notes

```text
GET /notes
```

Required scope:

```text
notes:read
```

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

```bash
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

```text
POST /notes
```

Required scope:

```text
notes:write
```

Required header:

```text
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:

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

## Request

```bash
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

```json
{
  "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

```text
GET /notes/{id}
```

Required scope:

```text
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

```text
PATCH /notes/{id}
```

Required scope:

```text
notes:write
```

Required header:

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

Sparse patch fields:

```json
{
  "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.21. Use order document context as plain text in the note body.
