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

# Order Merge Pro API overview

> Externally reachable HTTP surface, webhooks, and job status values for Order Merge Pro.

# Order Merge Pro API overview

## Endpoints

| Endpoint | Method | Auth | Description |
| - | - | - | - |
| `/health` | GET | None | Worker/binding readiness and deployed release |
| `/api/merge` | POST | Admin | Create a merge job from the app |
| `/api/public/merge` | POST | API key | Pro: create a merge job from an external integration |
| `/api/admin-merge-context` | POST, OPTIONS | Admin token | Merge context for admin UI extensions; CORS preflight handled in the loader |
| `/api/embedded-navigation` | POST, OPTIONS | Admin token | Resolve a Shopify-admin App Home URL for admin extensions |
| `/portal/merge` | GET, POST | App proxy (signed) | Pro customer self-service merge portal |
| `/webhooks` | POST | Webhook HMAC | Shopify webhook receiver |
| `/auth/login` | GET | None | Standalone OAuth entry; an empty `shop` is rejected before OAuth |
| `/auth/*` | GET | None | OAuth callback |
| `/support` | GET | None | Public support page |
| `/privacy`, `/terms` | GET | None | 301 redirects to `https://docs.pcnaid.com/order-merge-pro/{privacy,terms}` |

`GET /health` returns HTTP 200 with `status: "ok"` when every required binding and public variable is present, or HTTP 503 with `status: "degraded"` otherwise. It does not expose secret values, and there is no `/healthz` route.

The authenticated legacy routes `/jobs` and `/jobs/:id` redirect (`302`, and `307` for `POST /jobs/:id`) to their `/app/jobs` equivalents while preserving the query string.

## Create a merge job

`POST /api/merge` creates a new merge job and starts the Durable Object state machine. The body is `application/x-www-form-urlencoded` (FormData):

| Field | Type | Required | Description |
| - | - | - | - |
| `primaryOrderId` | string | Yes | Shopify GID of the primary order |
| `secondaryOrderIds` | string | Yes | JSON array of secondary order GIDs |
| `mergeNote` | string | No | Optional merchant note, normalized and capped at 255 characters |

The authenticated session is the authoritative tenant; client-supplied shop values cannot select another shop.

Responses:

* `202 Accepted` — job created, `{ "jobId", "status": "PENDING", "message" }`. An idempotent request for an existing active job also returns `202` with `"Merge job already in progress"`.
* `200 OK` — deduplicated terminal job already exists.
* `409 Conflict` — one or more orders are already part of an active merge job; the body lists `conflictingJobs`.
* `400 Bad Request` — validation error.

The idempotency key is `{shop}:{primaryOrderId}:{sortedSecondaryIds joined by comma}`.

`POST /api/merge` is called by the embedded admin UI and only accepts requests carrying a valid Shopify embedded-admin session; it cannot be called directly with `curl`. To create merge jobs from an external system, use the [public merge API](#public-merge-api) below.

## Public merge API

`POST /api/public/merge` is the Pro-only endpoint for creating merge jobs from external systems. Authenticate with an API key in the `Authorization: Bearer <key>` header or the `X-OrderMerge-Api-Key` header. Generate the key in the app under **Integrations** › **API keys**. The body is `application/json`:

| Field | Type | Required | Description |
| - | - | - | - |
| `primaryOrderId` | string | Yes | Shopify GID of the primary order |
| `secondaryOrderIds` | string\[] | Yes | Array of secondary order GIDs (max 10) |

Responses:

* `200 OK` — a terminal merge job with the same idempotency key already exists (deduplicated).
* `202 Accepted` — job created successfully (same shape as `/api/merge`).
* `400 Bad Request` — invalid JSON body or malformed request schema.
* `401 Unauthorized` — missing or invalid API key.
* `402 Payment Required` — shop is not on Pro.
* `409 Conflict` — one or more orders are already part of an active merge job.
* `503 Service Unavailable` — no offline Shopify session exists for the shop.

```bash theme={null}
curl --fail-with-body \
  -H "Authorization: Bearer ${ORDER_MERGE_API_KEY:?Set ORDER_MERGE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"primaryOrderId":"gid://shopify/Order/100","secondaryOrderIds":["gid://shopify/Order/200"]}' \
  "${APP_ORIGIN:?Set APP_ORIGIN}/api/public/merge"
```

## Job status values

| Status | Description | Terminal |
| - | - | - |
| `PENDING` | Job created, Durable Object not yet started | No |
| `VALIDATING` | Checking order eligibility | No |
| `STAGING_EDITS` | Adding line items to the primary order edit | No |
| `VERIFYING_INVENTORY` | Confirming tracked inventory and auditing source/staged price drift | No |
| `COMMITTING_MERGE` | Committing the verified primary-order edit | No |
| `CANCELLING_SECONDARIES` | Sending cancel requests after the primary edit commits | No |
| `WAITING_CANCELLATIONS` | Waiting for webhook confirmations | No |
| `REFUNDING_SHIPPING` | Creating shipping refunds | No |
| `FINALIZING` | Tagging orders | No |
| `RECOVERY_REQUIRED` | Automatic post-commit recovery exhausted; owner retry required | No (paused) |
| `COMPLETED` | Merge finished successfully | Yes |
| `FAILED` | Merge failed with error | Yes |

## Outbound webhooks

Pro merchants can subscribe to `merge.completed` and `merge.failed` events. To register an endpoint, open **Integrations** › **Webhooks** in the app, enter the **Endpoint URL**, select the events, and choose **Add subscription**. The app then shows the webhook signing secret once; copy it before you dismiss the banner. Each subscription receives a signed `POST` to the registered URL. Subscription endpoints must use HTTPS and a public destination; registration and delivery reject loopback, private, link-local, reserved, and documentation IP literals (IPv4 and IPv6), single-label hosts, and local or internal DNS suffixes. Delivery uses manual redirect handling, so a 3xx response is reported as a failed delivery and is not followed.

Delivery headers:

| Header | Value |
| - | - |
| `Content-Type` | `application/json` |
| `X-OrderMerge-Signature` | `sha256=<base64url HMAC-SHA256 of the raw JSON body>` |
| `X-OrderMerge-Event` | `merge.completed` or `merge.failed` |
| `X-OrderMerge-Shop` | The myshopify.com domain |

Payload:

```json theme={null}
{
  "version": "2026-09-27",
  "event": "merge.completed",
  "shop": "store.myshopify.com",
  "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "primaryOrderId": "gid://shopify/Order/100",
  "secondaryOrderIds": ["gid://shopify/Order/200"],
  "status": "COMPLETED",
  "shippingRefundAmount": "5.99",
  "occurredAt": "2026-09-27T12:34:56Z"
}
```

See [Outbound webhooks and Shopify topics](https://docs.pcnaid.com/nodes/shopify/order-merge-pro/developer/webhooks/index.md) for the inbound Shopify topics the app handles.

[Pcnaid Documentation](https://docs.pcnaid.com/namespace/index.md) › [Shopify Ecosystem](https://docs.pcnaid.com/hubs/shopify-ecosystem/index.md) › [Order Merge Pro](https://docs.pcnaid.com/nodes/shopify/order-merge-pro/index.md) › [Developer](https://docs.pcnaid.com/nodes/shopify/order-merge-pro/developer/index)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.