Overview

Self-hosted, multi-tenant bridge that turns a customer lead (name, address, cart) into a Shopify draft order formatted as an anonymous invoice. The operator registers Shopify stores once via OAuth; any landing page then integrates by POSTing one JSON request to /api/checkout and redirecting the buyer to the returned invoice_url.

There's no quota, no billing, no SDK — just a tiny HTTP API. The whole integration fits in one function, see Quick integration.

Authentication

X-Bridge-KeyapiKey (header)

Required on POST /api/checkout. Shared secret. Value lives in the bridge's .env as BRIDGE_API_KEY. Get it from the operator. Never send this from browser JS — call the bridge from your own backend.

curl -H "X-Bridge-Key: <your key>" ...
Bearer JWThttp (header)

Required on every /api/admin/* call. Obtain by POSTing the admin password to /api/admin/login. JWT is valid for 24 hours.

curl -H "Authorization: Bearer <jwt>" ...

Quick integration (what most agents need)

This is the entire landing-page integration. Call it from your server-side controller (Node/PHP/Python/Go — doesn't matter). The request body is stable, the response is a single URL.

// Node (server-side)
const res = await fetch("https://shopify.anonbusiness.com/api/checkout", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Bridge-Key": process.env.BRIDGE_API_KEY,
  },
  body: JSON.stringify({
    store_id: 1,
    customer: {
      firstName: "Mohamed",
      lastName:  "Alaoui",
      email:     "m.alaoui@example.com",
      address:   "12 Rue des Orangers",
      city:      "Casablanca",
      zip:       "20000",
    },
    items: [
      { price: 219, quantity: 1 },
    ],
  }),
});
const { invoice_url } = await res.json();
// redirect the buyer:
response.redirect(invoice_url);
Price trust: whatever price you send is forwarded to Shopify as-is — the bridge does not validate it against a catalog. Compute the price server-side from the product ID / pack size, never from hidden form fields the browser could edit.
Stealth title: line-item title fields you send are ignored. The bridge overwrites them with "Invoice #NNNN" before the request reaches Shopify, so the resulting draft order reads as an invoice rather than named products. Your own price and quantity are preserved.

Checkout

POST/api/checkout X-Bridge-Key

Creates a Shopify draft order for a connected store and returns its invoice_url. The primary integration endpoint for landing pages.

Body

FieldTypeNotes
req store_idintegerBridge's internal store ID (list via /api/admin/stores). Store must have status = "connected".
req customerobjectSee fields below. All required.
customer.firstNamestring
customer.lastNamestring
customer.emailstringEmail format.
customer.addressstringStreet line.
customer.citystring
customer.zipstring
req itemsarray<object>At least 1 item.
items[].pricenumberUnit price, store currency.
items[].quantityinteger ≥ 1

Response 200

{ "invoice_url": "https://my-shop.myshopify.com/12345/invoices/abcdef" }

Error responses

CodeWhen
401Missing or wrong X-Bridge-Key
404store_id doesn't exist, or store status is still pending (OAuth not completed)
500Shopify API returned an error — body includes { "error": ... }

curl

curl -X POST https://shopify.anonbusiness.com/api/checkout \
  -H "Content-Type: application/json" \
  -H "X-Bridge-Key: $BRIDGE_API_KEY" \
  -d '{
    "store_id": 1,
    "customer": { "firstName":"Mohamed","lastName":"Alaoui","email":"a@b.com","address":"12 Rue","city":"Casablanca","zip":"20000" },
    "items": [{ "price": 219, "quantity": 1 }]
  }'

Admin

POST/api/admin/login

Exchanges the admin password for a Bearer JWT. JWT is valid for 24 hours.

Body

{ "password": "your-admin-password" }

Response 200

{ "token": "eyJhbGciOiJIUzI1NiIs..." }
GET/api/admin/storesBearer JWT

Lists every registered store. access_token is included for connected stores — treat the response as sensitive.

Response 200

[
  {
    "id": 1,
    "name": "My Morocco Store",
    "domain": "my-shop.myshopify.com",
    "client_id": "abc123...",
    "access_token": "shpat_...",
    "status": "connected",
    "created_at": "2026-04-22T00:14:00Z"
  }
]
POST/api/admin/storesBearer JWT

Registers a new Shopify store with status pending. Run /api/auth/shopify/{id} afterwards to complete OAuth and flip the status to connected.

Body

{
  "name": "My Morocco Store",
  "domain": "my-shop.myshopify.com",
  "client_id": "abc123...",
  "client_secret": "shpss_..."
}

Response 200

{ "id": 1, "message": "Store added successfully" }
DELETE/api/admin/stores/{id}Bearer JWT

Removes a store and its stored access token. Does not revoke the token inside Shopify — do that separately from the Shopify admin.

Response 200

{ "message": "Store deleted" }

OAuth

GET/api/auth/shopify/{storeId}

Starts the Shopify install flow. Returns a 302 to Shopify's authorize URL. After the merchant approves, Shopify sends them to the callback below.

Scopes requested: write_draft_orders, read_customers, read_products.

GET/api/auth/shopify/callback

Called by Shopify, not by you. Verifies the HMAC signature, cross-checks the shop parameter against the registered store domain, exchanges the code for an access token, and marks the store as connected.

Health

GET/healthz

Zero-auth liveness probe. Use in uptime monitors.

Response 200

{ "ok": true, "service": "shopify-bridge" }

Machine-readable

Point your AI agent / codegen tool at these URLs rather than this HTML — they're tighter and more reliable to parse.