---
title: "Find and cancel orders"
description: "List, filter and read orders, read their status history and shipments, and cancel an order."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.affinityrx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Find and cancel orders

## List orders

```sh
curl "https://api.affinityrx.com/v1/orders?status=shipped&limit=25" \
  -H "Authorization: Bearer $AFFINITY_API_KEY" \
  -H "Affinity-Version: 2026-10-28"
```

The list contains the orders of your organization in the mode of your key, newest first.
Each response reads one snapshot. All returned orders match your filters in that snapshot.
Each page reads a new snapshot.

| Parameter | Result |
| --- | --- |
| `practiceId` | Limit results to the practice with this prac_ ID, within your authorized practices. |
| `externalOrderId` | Match the external order ID exactly, including case. Combine with practiceId when your platform reuses IDs across practices. |
| `updatedAfter` | Return orders whose updatedAt is strictly later than this ISO 8601 timestamp. Sorting stays by creation time and ID unless sort is supplied. Keep this cutoff unchanged while paging. |
| `patientId` | Limit results to the patient with this pat_ ID. |
| `status` | Return only orders with this status. Omit to include all statuses. |
| `cancelRequested` | true returns only submitted or processing orders with a cancel the pharmacy has not confirmed yet (cancelRequestedAt set). false leaves those orders out. Omit to include both. |
| `recentlyShipped` | true returns only orders whose status is shipped and that shipped in the last 60 days; delivered orders are excluded. false leaves those orders out. Omit to include both. |
| `query` | Search saved order snapshots: patient names, email, phone and external ID; order external ID; prescriber name and NPI; medication names and brands; pharmacy name. Whole ord_ and pat_ IDs and six-character order numbers also match. Text matches ignore case and allow substrings, word prefixes and similar spellings for alphanumeric words of four or more characters. Every one of the first eight words containing letters or numbers must match; punctuation-only words are ignored. An empty or punctuation-only query leaves the list unfiltered. Date of birth, separate strength fields, practice names, prac_ IDs and rx_ IDs are not searched. Restart pagination when filters change. |
| `sort` | patient sorts the lowercase text firstName + space + lastName A to Z, then order ID; created sorts oldest first. A leading - reverses the order. Defaults to -created. Restart pagination after changing sort. |
| `limit` | Maximum records per page, from 1 to 100. Defaults to 25. |
| `startingAfter` | Page forward using the previous response's nextCursor. Keep the same filters, sort, organization and mode. Use only one cursor parameter. |
| `endingBefore` | Page backward before this opaque cursor. Keep the same filters, sort, organization and mode. Send either startingAfter or endingBefore. |

`sort=patient` sorts the saved patient's first and last name as one lowercase string, first name first.
The order ID breaks ties. `sort=-patient` reverses both. `sort=created` sorts oldest first; the default is `-created`.

`query` searches the saved order snapshot:

- Patient names, email, phone and external ID; order external ID.
- Prescriber name and NPI; medication names and brands; pharmacy name.
- Whole `ord_` and `pat_` IDs.
- Six-character order numbers and parts of those numbers, including prefixes.

Text matches ignore case and support substrings, word prefixes and similar spellings for alphanumeric words of four or more characters.
Every one of the first eight words containing letters or numbers must match. Punctuation-only words are ignored.
An empty or punctuation-only query leaves the list unfiltered.
Search does not match date of birth, separate strength fields, practice names, `prac_` IDs or `rx_` IDs.

The response is a list: `{ "object": "list", "url", "data", "hasMore", "nextCursor" }`.
Responses carry only `nextCursor`.
When `hasMore` is `true`, send `nextCursor` as `startingAfter` to get the next page.
`endingBefore` takes a cursor you saved while paging forward.
Use the same filters on each page. A cursor with different filters returns `400` `invalid_cursor`.

To recover after missed webhooks, poll `GET /v1/orders` with a saved `updatedAfter` cutoff.
Follow every `nextCursor`. Keep the cutoff fixed for the whole traversal.
The default order stays newest creation time first, with the order ID as a tie-breaker.
Updates do not move that cursor.
Deduplicate each returned order by its order ID and `updatedAt`.
A slow transaction can commit an update with a slightly earlier `updatedAt` than a later-committed
change.
After all pages finish, save the run's start time as the next cutoff.
For the next poll, send that cutoff minus one minute as `updatedAfter`.
The one-minute overlap catches those late commits.
Keep your clock synchronized.
Increase the margin if clock skew can exceed one minute.
Repeated orders are expected.
Use `externalOrderId` for an exact lookup after an uncertain create response.

```sh
curl "https://api.affinityrx.com/v1/orders?updatedAfter=2026-10-21T00%3A00%3A00Z&limit=25" \
  -H "Authorization: Bearer $AFFINITY_API_KEY" \
  -H "Affinity-Version: 2026-10-28"
```

## Read an order

Send `GET /v1/orders/{orderId}`. The response is the whole order, the same as the
[create response](/guides/send-an-order/#the-response). An order of another organization returns
`404`.

`statusHistory` lists every status of the order, oldest first:

```json
"statusHistory": [
  { "status": "submitted", "createdAt": "2026-10-28T15:04:05.000Z" },
  { "status": "processing", "createdAt": "2026-10-28T16:10:00.000Z" },
  { "status": "shipped", "createdAt": "2026-10-29T18:30:00.000Z" }
]
```

`shipments` lists the packages, oldest first. A split shipment or a reship adds a shipment. A voided
label removes it. `carrier` and `trackingUrl` can be `null`.

Each shipment's `trackingUrl` is the carrier's own tracking page for that package, with the
tracking number filled in (FedEx, UPS, USPS and OnTrac). For another carrier it is the pharmacy's
tracking link, or `null`. The order's `trackingUrl` is the newest package's link, or `null` before
the order ships. You can send these links to the patient.

The order status comes from the pharmacy. Affinity does not change the status from carrier scans:
an order becomes `delivered` when the pharmacy reports the delivery.

```json
"shipments": [
  { "carrier": "UPS", "trackingNumber": "1Z999AA10123456784", "trackingUrl": "https://www.ups.com/track?tracknum=1Z999AA10123456784" }
]
```

## Status timing

Pharmacies push status updates to Affinity. Affinity also checks orders on a schedule based on their status, with less frequent checks as orders age.

If the pharmacy never reports a result, the order stays open. Affinity staff follow up on Live orders open longer than three business days.

## Cancel an order

A `processing` order can already be filled at the pharmacy. PerfectRx refuses cancellation after filling and verification.
A default Test order reaches this cutoff about 2 minutes after the test pharmacy receives it.
Use [Test outcomes](/guides/test-and-live-mode/#test-outcomes) to test a refusal without waiting for that cutoff.

```sh
curl -X POST https://api.affinityrx.com/v1/orders/ord_01j9z3k4m5n6p7q8r9s0t1v2w7/cancel \
  -H "Authorization: Bearer $AFFINITY_API_KEY" \
  -H "Affinity-Version: 2026-10-28" \
  -H "Idempotency-Key: cancel-order-42"
```

Send no body with a cancel request. Cancelling an already-cancelled order returns `200` with the order and sends no second `order.cancelled` event.

If the pharmacy never received the order, Affinity cancels it and returns `200` with `status: "cancelled"`.
If Affinity is sending the order at that moment, you get `202`. If the send did not reach the pharmacy,
Affinity cancels the order within seconds and sends `order.cancelled`.
Otherwise, Affinity sends the cancel to the pharmacy. The response tells you what the pharmacy did:

| Response                       | Meaning                                                                   | Then                                                                                                                                                                 |
| ------------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`, `status: "cancelled"`   | Affinity cancelled an unsent order, or the pharmacy confirmed the cancel. | Affinity sends `order.cancelled` on the first confirmation only.                                                                                                     |
| `202`, `cancelRequestedAt` set | The pharmacy did not confirm yet, or it is not available.                 | Affinity tries again. You get `order.cancelled` when the pharmacy confirms. If the pharmacy refuses, `cancelRequestedAt` becomes `null` and you get `order.updated`. |
| `409`                          | The cancel failed, the pharmacy refused, or it is too late.               | The order keeps its status. An immediate `409` `cancel_refused` also sends one `order.updated`.                                                                      |

If Affinity cannot reach the pharmacy for some minutes, the cancel stays pending. Affinity staff then
resolve it with the pharmacy, and you get `order.cancelled` or `order.updated`. To find orders with a
pending cancel, list orders with `cancelRequested=true`.

`cancelRequestedAt` returns to `null` when the cancel is confirmed or refused. Read `status` to
tell the results apart. A null value alone does not mean refusal.

`200` and `202` return the whole order. A `409` has the code `cancel_refused` (the cancel request failed or the pharmacy refused)
or `cannot_cancel` (the order is `shipped`, `delivered` or `failed`).

If the response is uncertain, retry with the same `Idempotency-Key`. If the first response was `200` or `202`, the retry returns that status code with the order as it is now.
If the first response was `409`, or the request stopped before it answered, the retry computes its
answer from the current order and cancel job. Its status code can differ from the first response.
The retry does not ask the pharmacy again or send another event.
Read [Retry safely](/guides/#retry-safely).

A cancel with a new key on an order whose cancel is already waiting returns `202` and does not ask
the pharmacy again.

After the pharmacy refuses a cancel, a request with a new `Idempotency-Key` asks the pharmacy again.

A pharmacy can ship after it confirms cancellation. Continue to apply newer shipment and delivery events to a cancelled order.
Check the latest order before you send a replacement.

Source: https://docs.affinityrx.com/guides/find-and-cancel-orders/index.mdx

If a response or a page is wrong or unclear, send `POST /v1/feedback` with an `Idempotency-Key`, a short message and the `requestId` from the response. Do not include patient information.
