# Pharmagistic Partner API > Live inventory and Certificates of Analysis from a real peptide manufacturer, > and for fulfilment partners, holds and orders. If you are an AI agent > integrating a storefront, read this file and then the OpenAPI document — > between them they are the whole contract. > > GENERATED from the API schemas by `npm run openapi`. Do not hand-edit. Base URL: https://api.pharmagistic.com Machine-readable contract: https://api.pharmagistic.com/openapi.json (OpenAPI 3.1) Human docs: https://api.pharmagistic.com/developers ## Authentication Every request: Authorization: Bearer pk_XXXXX:sk_YYYYYYYY The key is `pk_:` and is issued by the manufacturer's operator. Keys carry scopes and may be bound to one brand. A sandbox key behaves identically but touches only rehearsal stock that can never ship. ## Versioning X-Pharmagistic-Version: 2026-09-06 Optional; omit it for the current version. A date newer than the deployment is a 400 unsupported_version. Dates are added, never rewritten: a shape that exists on a date keeps that shape on that date. ## Start here: inventory + COA, read only Most partners begin with the read-only feed: show our live availability and our Certificates of Analysis on your own site. No pricing (prices are yours), no holds, no orders. A read-only key carries the scopes `atp.products`, `atp.inventory` and `atp.check`; every other endpoint answers 403. 1. GET /api/v1/products every SKU you may sell, with availability 2. GET /api/v1/inventory?sku=… the live count for one SKU 3. COA feed (below) the certificate for each product - Map each product on your site to exactly one of our SKUs. Unmapped = not sellable. - Refresh counts at least every 5 minutes. Our number is the truth; yours is a mirror. - The count is "available as of our last sync". Orders you ship through us reach our system via ShipStation and draw the count down; you never post an order. - A 401 or 403 means STOP SHOWING STOCK and alert a human. Never treat it as zero. ## Certificates of Analysis The COA feed is served by our storefront platform and accepts the same key: GET https://usamadepeptides.us/api/partner/v1/coa GET https://usamadepeptides.us/api/partner/v1/coa/lot/{lot} Query: `?status=current|archived|retired|all` (default current), `?product=` (repeatable), `?updated_since=` for incremental sync, `?format=json|csv`, `?limit=1..500&cursor=`. Each record carries `product{slug,name}`, `batch_number`, `vial_size`, `tested_on`, `lab_name`, `purity_pct`, `status`, `certificate_url` (PDF), `verify_url` and `updated_at`. A retired lot is a tombstone with an empty `certificate_url`: take it down. Match COAs to products by `product.slug`; your operator sends the slug ↔ SKU map with your key. Anyone can verify one lot without a key at `https://usamadepeptides.us/coa/lookup?lot=`. ## Go-live Build against a sandbox key first. When the checks below pass, tell your operator; we read your sandbox traffic, then issue a production key bound to your brand and revoke the sandbox key 30 days later. - Your mirror shows the same count as GET /api/v1/inventory for every mapped SKU. - Every listed product shows its current COA and links the PDF. - With your key revoked (we do this for ten minutes), your site hides stock and COAs and alerts you. It does not show stale or zero counts as if they were true. - You stay under 60 requests a minute. ## The one flow that matters (fulfilment keys only) 1. reserve hold units, get a reservation_token 2. charge your gateway, your rules 3. place turn the hold into an order we pick and ship or consume keep the units and ship them yourself Reserve BEFORE you charge. The check endpoint is advisory and can be stale; the reserve is the only answer that holds physical vials. If the reserve comes back short, do not take the money. ## Endpoints - POST /api/v1/check — Available-to-promise for one or more SKUs (no hold) - POST /api/v1/reserve — Hold inventory (brand pool first, then shared per access) - POST /api/v1/release — Release a hold - POST /api/v1/consume — Consume a hold WITHOUT creating an OMS order - POST /api/v1/consume/reverse — Give back the units a consume took, when the sale is reversed - GET /api/v1/inventory — ATP for one SKU by brand plus totals - GET /api/v1/products — Every sellable SKU, with availability for this key - POST /api/v1/orders — Place the pickable OMS order from a hold (one transaction) - GET /api/v1/orders/{ref} — An order this key placed, by order id or Idempotency-Key - POST /api/v1/orders/{ref}/cancel — Cancel an order this key placed, before it is picked - POST /api/v1/shipments/combine — Ship several of this key's orders in one box (combined shipments) - POST /api/v1/keys/introspect — Is this token a live OMS key, and what may it do? - GET /api/v1/shipments — This key's brand's ShipStation queue, by modification time - GET /api/v1/inventory/summary — What the OMS can ship for this key's brand, one row per formula - GET /api/v1/receipts — Units received into this key's brand's own stock, by batch and day - POST /api/v1/reserve-cache — Push the console's prepaid-reserve mirror for one brand - POST /api/v1/confirm (deprecated) — Legacy: place an OMS order from a hold (use POST /api/v1/orders) ## Errors { "error": { "code": "reservation_gone", "message": "..." } } Codes are stable strings; match on `code`, never on `message`. Worth handling by name: - `reservation_gone` — the hold is over. Reserve again. - `reservation_expired` — the hold aged out of its window. Reserve again. - `reservation_used` — already consumed or placed. You have been served once. - `idempotency_mismatch` — that Idempotency-Key was used with a different hold. - `order_not_cancellable` — picked already; it needs a return authorization. - `rate_limited` — 429. `Retry-After` carries the seconds. - `unsupported_version` — the X-Pharmagistic-Version date is newer than this deployment. Every code the contract documents: `bad_request`, `brand_mismatch`, `forbidden`, `gone`, `hold_race`, `idempotency_mismatch`, `idempotency_required`, `internal_error`, `invalid_customer`, `invalid_input`, `invalid_item`, `kit_incomplete`, `no_inventory`, `not_consumed`, `not_found`, `not_reversible`, `order_forbidden`, `order_not_cancellable`, `order_not_found`, `rate_limited`, `reservation_empty`, `reservation_expired`, `reservation_forbidden`, `reservation_gone`, `reservation_not_found`, `reservation_used`, `retry`, `shipstation_error`, `unauthorized`, `unknown_brand_code`, `unresolved_stack`, `unsupported_version`, `wrong_state` ## Webhooks We send `order.shipped` and `order.held` to the URL you register, signed in Svix format: `svix-id`, `svix-timestamp` (Unix seconds) and `svix-signature` = `v1,` keyed by your `whsec_` secret. - Reject anything more than five minutes old. - Dedupe on `svix-id`: a retry carries the same one. - During a secret rotation both signatures arrive, space separated. Any match is valid. - Answer 2xx to stop the retries. Answer 410 and we deactivate the endpoint. ## Sandbox A sandbox key draws on the SANDBOX brand: fake units, no shared-pool access, nothing that can ship. Same endpoints, same errors, same shapes. Rehearse the reserve, charge and order path there before you touch real stock. ## Status Beta. The endpoints and error codes above are stable and versioned by date. A breaking change gets a new version date and ninety days of overlap, during which the old date keeps working.