Customers and sales channels reselling MedGrid products.

Customer API

Pull your own catalog and pricing, and place orders from your own system.

Authentication

Credentials are issued by a MedGrid administrator from your Customer record — there is no self-service signup. You exchange them once for a one-hour token, then send that token on every call. A raw token client_id:client_secret header is rejected with 401; the Bearer token is the only accepted method.

EnvironmentBase URLUse it for
UAThttps://uat.medgrid.comBuilding and testing.
Productionhttps://medgrid.comReal orders, real money.
The two are separate installations with separate databases, so credentials issued on one are unknown to the other. Ask for UAT credentials as well as production ones.
Cache the token; don't fetch one per call. auth_token is limited to 20 requests a minute counted per source IP, not per account — every server behind the same egress address shares that budget. An integration that authenticates before each request locks itself out inside a minute. Hold the token for its hour and re-authenticate only on a 401.

POSTauth_token

Exchange client credentials for a one-hour Bearer token.

https://medgrid.com/api/method/medgrid.customer_api.auth_token

Parameters

FieldTypeDescription
client_idstringrequiredPublic client identifier.
client_secretstringrequiredThe secret shown once at generation.

Request

curl -X POST \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  "$BASE/api/method/medgrid.customer_api.auth_token"

Response

{ "message": {
    "ok": true,
    "access_token": "eyJhbGciOi…",
    "token_type": "bearer",
    "expires_in": 3600,
    "scope": "customer_api",
    "customer": "CUST-00042"
} }
An unknown client_id and a wrong client_secret return the same 401, so the endpoint cannot be used to discover which accounts exist.

Conventions

Every response is wrapped by the framework in a message key, so the body is {"message": <result>} and each result carries an ok flag. The catalog endpoints are GET. auth_token, create_order and request_payment_link are POST-only; get_shipping_options and get_order_status are reads and accept GET.

Failure — never a raw stack trace

{ "message": { "ok": false, "error": "Human-readable message.",
              "code": "PermissionError", "status": 403 } }
The customer parameter on every endpoint is optional — your token already identifies your account. It exists for admin tokens that manage several customers.

Catalog

Seven read endpoints under medgrid.customer_api.*, each scoped to your account: you only ever see your own catalog, your own prices and your own warehouses.

GETwhoami

Connectivity check. Echoes the price list your prices come from, your currency, your markup and your allowed warehouses. Start here — everything downstream is computed from it.

https://medgrid.com/api/method/medgrid.customer_api.whoami

Response

{ "message": {
    "ok": true,
    "customer": "CUST-00042",
    "status": "Active",
    "item_selection": "All",
    "price_list": "Sunrise Wholesale",
    "currency": "USD",
    "markup_pct": 0.0,
    "warehouses": ["Main Warehouse - MG", "East DC - MG"]
} }

GETget_catalog

Paginated, priced catalog of the items available to your account, with stock summed across your allowed warehouses.

https://medgrid.com/api/method/medgrid.customer_api.get_catalog

Parameters

FieldTypeDescription
pageintoptional1-based page number. Default 1.
page_lengthintoptionalItems per page. Default 50, maximum 200.
in_stock_only0 / 1optionalWhen 1, only items with available stock.
item_groupstringoptionalFilter to one item group.
searchstringoptionalMatch on item name or code.
count is the number of products on this page; there is no grand total. Page until a response comes back with fewer than page_length products.

GETget_catalog_item

Full detail for one product, including per-warehouse stock — only if that SKU is in your catalog.

https://medgrid.com/api/method/medgrid.customer_api.get_catalog_item

Parameters

FieldTypeDescription
skustringrequiredThe item code to look up.
A SKU outside your catalog returns 404, not an empty result. Omitting sku returns a 417 asking for one.

GETget_pricing

Your account's prices. Pass skus to price a specific set, or omit it to price the whole catalog (capped at 500).

https://medgrid.com/api/method/medgrid.customer_api.get_pricing

Parameters

FieldTypeDescription
skusCSV / JSON listoptionalOmit for the whole catalog.
A requested SKU outside your catalog comes back with price: null and in_catalog: false rather than an error.

GETget_availability

Available-to-sell quantities per warehouse.

https://medgrid.com/api/method/medgrid.customer_api.get_availability

Parameters

FieldTypeDescription
skusCSV / JSON listoptionalLimit to these SKUs.
warehousestringoptionalOne of your allowed warehouses; anything else is 403.
Unlike get_pricing, a SKU outside your catalog is silently omitted from levels and totals rather than flagged. A SKU with no stock row anywhere is absent too, not reported as 0.

GETget_warehouses

The warehouses you may report availability for — the valid values for the warehouse parameter above.

https://medgrid.com/api/method/medgrid.customer_api.get_warehouses

Ordering

Four endpoints under medgrid.customer_api_orders.*. They use the same Bearer token as the catalog, but must be switched on separately — ask your administrator to tick Allow API Ordering. Until then every call here returns 403.

  1. You call get_shipping_options

    Tells you which shipping methods your basket qualifies for and what each costs.

  2. You call create_order

    MedGrid validates the SKUs against your catalog, checks stock and credentials, prices every line and builds a draft. Nothing is charged and no stock moves.

  3. MedGrid emails the customer

    The same call emails the draft as a quotation carrying a payment link, valid for 48 hours.

  4. The customer pays the link

    Payment triggers automatic order submission and invoicing. If submission fails after settlement, the payment stays Paid and the order remains a draft for recovery.

  5. You poll get_order_status

    Check payment_status and docstatus separately, then monitor the order's delivery and billing progress. A paid draft needs MedGrid support, not another payment.

There is no customer-callable confirm endpoint. Payment triggers submission automatically, but Paid alone does not prove that submission succeeded.

GETget_shipping_options

The shipping methods available for a specific basket, with the exact amount that will be charged.

https://medgrid.com/api/method/medgrid.customer_api_orders.get_shipping_options

Parameters

FieldTypeDescription
itemsJSON arrayrequiredThe basket, e.g. [{"sku":"VD3-5000-120","qty":2}].
The shipping_rule value it returns is what create_order expects; anything else is rejected. A basket containing a cellular or acellular biologic collapses to the single cold-chain overnight method.

POSTcreate_order

Creates a draft order, returns its server-computed total, and emails the customer a payment link. Nothing is charged until they pay.

https://medgrid.com/api/method/medgrid.customer_api_orders.create_order

Parameters

FieldTypeDescription
itemsJSON arrayrequiredOrder lines. Repeated SKUs are merged. Maximum 200 lines.
shipping_rulestringrequiredA value from get_shipping_options.
external_refstringrequiredYour own order id, 100 characters or fewer. This is the idempotency key.
shipping_addressstringoptionalAn Address on your account. Defaults to your saved shipping address.
po_nostringoptionalYour purchase-order number, printed on the invoice.
notesstringoptionalFree text stored as a comment on the order.

Request

curl -X POST -H "Authorization: Bearer $TOKEN" \
  --data-urlencode 'items=[{"sku":"VD3-5000-120","qty":2}]' \
  -d "shipping_rule=Standard Shipping" \
  -d "external_ref=PO-2026-0184" \
  "$BASE/api/method/medgrid.customer_api_orders.create_order"
Re-send the same external_ref and you get the original order back with duplicate_of_request: true — a timed-out request is always safe to retry. Retrying under a NEW ref is how you end up with two orders.
Stock is all-or-nothing. If any line exceeds what is available, nothing is created and the error names the SKU, what you asked for and what is actually available.

GETget_order_status

Current state of one of your orders, by MedGrid order id or by your own external_ref.

https://medgrid.com/api/method/medgrid.customer_api_orders.get_order_status

Orders that aren't yours return a plain 404 — we never confirm that another account's order id exists.
payment_status reports settlement; docstatus reports the order document: 0 is draft, 1 is submitted, 2 is cancelled. If payment_status is Paid while docstatus is 0, contact MedGrid support for recovery. Do not create or pay a replacement order.

Payment lifecycle

The terminal values below end payment polling. Order submission and fulfilment have their own state: continue checking docstatus and the order status separately.

ValueTerminalWhat it means
UnpaidnoNo payment link has been emailed yet.
PendingnoThe link is out and nobody has paid it. This does not mean a payment is on its way.
ProcessingnoThe customer confirmed a payment and the debit is clearing. Cards normally settle straight to Paid; this is where a bank/ACH debit sits, and it can last days.
PaidyesThe money landed. Submission can still fail, leaving docstatus 0 (a paid draft); check docstatus separately before treating the order as submitted.
FailedyesA debit that was clearing bounced. Nothing is charged and the order will not proceed on its own.
ExpiredyesThe payment session lapsed unpaid. Call request_payment_link for a fresh one.
RefundedyesA settled payment was returned afterwards.
An integration that polls for Paid and nothing else waits forever on a bounced debit. Treat Failed and Expired as stop conditions and surface them to a human.
A paid draft needs recovery, not another charge. If payment_status is Paid and docstatus is 0, contact MedGrid support. A submitted order (docstatus 1) still needs fulfilment monitoring; payment settlement does not mean delivery is complete.

Rate limits

Each limit is a fixed 60-second window. Go over one and you get 429 with a Retry-After header naming the seconds until that window rolls over.

CallsLimitCounted per
auth_token20 / minutesource IP
Every authenticated call — catalog and ordering120 / minuteaccount
The four ordering endpoints, additionally20 / minuteaccount
request_payment_link5 / minuteaccount
The two budgets stack; they are not separate pools. An ordering call is checked against the 120/minute limit first, then against the 20/minute one, so it spends both. Burn 120 catalog reads in a minute and the next create_order returns 429 before it reaches its own allowance. get_order_status polling costs against both too.

Errors

Failures return a clean, human-readable message — never a raw stack trace. The HTTP status and a stable code let you branch in software; the error string is safe to show a person.

StatusCodeWhat it means
401AuthenticationErrorMissing or invalid credentials, or an expired token.
403PermissionErrorAccess inactive, a warehouse you're not allowed, ordering not switched on, or a credential (NPI/DEA/license) that doesn't clear for the items ordered.
404DoesNotExistErrorThe SKU isn't in your catalog, or the order isn't one of yours.
417ValidationErrorA required parameter is missing or malformed, not enough stock, or an invalid shipping rule.
429TooManyRequestsErrorSee Rate limits above.
500InternalErrorUnexpected server error. Safe to retry.
A successful create_order that reports payment_email: "blocked" is not a failure — the order exists and is valid, it just cannot be paid until the account issue named in next_step is resolved. Fix the account and call request_payment_link; don't create a second order.