UnifyCart Developers

The App API

Every endpoint an installed app can call, how it authenticates, and what each one is for.

Separate from the merchant REST API, under /api/apps on the shop's own domain.

Authentication

An installation has its own access token, issued when the merchant installs your app.

GET /api/apps/shop HTTP/1.1
Host: shop.example.com
X-UnifyCart-Access-Token: YOUR_INSTALLATION_TOKEN
Accept: application/json

A bearer token in Authorization is accepted as well. The token identifies one installation in one shop, so if your app serves a hundred merchants you hold a hundred tokens.

Scopes

An installation carries the scopes the merchant granted when they installed. A call without the scope it needs is refused with a 403 that names what was wanted and what you hold, which makes this the easiest class of error to diagnose:

{
  "message": "Insufficient scope for this endpoint.",
  "required_scopes": ["read_shop"],
  "granted_scopes": ["read_products", "write_orders"]
}
Endpoint Reading needs Writing needs
/api/apps/shop read_shop n/a
/api/apps/products read_products write_products
/api/apps/orders read_orders write_orders
/api/apps/customers read_customers write_customers
/api/apps/inventory read_inventory write_inventory
/api/apps/webhooks none write_webhooks
/api/apps/script-tags none write_script_tags
/api/apps/extensions none write_extensions

Reading Your Own Registrations Needs No Scope

The last three rows are not a typo. Listing a shop's webhooks, script tags or extensions is allowed to any installed app, whatever it was granted. Do not treat the fact that you can read them as permission to rely on them, and do not assume another app cannot see yours.

Ask for the narrowest set that makes your app work. A merchant looking at an install screen asking for everything is a merchant deciding whether to trust you, and the request is the only evidence they have.

Knowing Which Shop You Are In

GET /api/apps/shop
curl -s https://shop.example.com/api/apps/shop \
  -H "X-UnifyCart-Access-Token: YOUR_INSTALLATION_TOKEN" \
  -H "Accept: application/json"
Full response
{
  "shop": {
    "name": "UnifyCart",
    "email": null,
    "domain": "demo.unifycart.com",
    "currency": "USD",
    "timezone": "America/Los_Angeles"
  }
}

Returns the shop this token belongs to. Call it first on any request where you need to know which merchant's data you are about to touch.

Do not infer the shop from the hostname you were called on, from a parameter, or from anything a caller supplied. The token is the only thing that decides it.

Extensions

An extension is a point where your app appears inside the shop.

GET    /api/apps/extensions
POST   /api/apps/extensions
DELETE /api/apps/extensions/{id}
{
  "type": "checkout_block",
  "callback_url": "https://your-app.example.com/render",
  "timeout_ms": 5000
}
Field Notes
type Where it appears
callback_url Where the shop calls you to render it
timeout_ms How long the shop waits, default 5000
curl -s -X POST https://shop.example.com/api/apps/extensions \
  -H "X-UnifyCart-Access-Token: YOUR_INSTALLATION_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "type": "checkout_block", "callback_url": "https://your-app.example.com/render", "timeout_ms": 5000 }'
Listing them back
{
  "extensions": [
    {
      "id": 1,
      "app_installation_id": 1,
      "type": "checkout_block",
      "callback_url": "https://example.com/render",
      "is_active": true,
      "timeout_ms": 5000,
      "created_at": "2026-10-03T22:53:46.000000Z",
      "updated_at": "2026-10-03T22:53:46.000000Z"
    }
  ]
}

Your callback is on the shopper's critical path

A checkout extension that takes four seconds adds four seconds to somebody's checkout. If your callback exceeds the timeout the shop carries on without you, which is the right behaviour and means your extension silently does not appear.

Answer in well under a second. Do the slow work elsewhere and have the callback read a result you prepared earlier.

Script Tags

GET    /api/apps/script-tags
POST   /api/apps/script-tags
DELETE /api/apps/script-tags/{id}
{
  "src": "https://your-app.example.com/widget.js",
  "location": "body_end"
}

location is head, body_start or body_end. Use body_end unless you have a reason: a script in the head blocks the page from rendering.

Listing them back
{
  "script_tags": [
    {
      "id": 1,
      "app_installation_id": 1,
      "src": "https://example.com/widget.js",
      "location": "body_end",
      "is_active": true,
      "created_at": "2026-10-03T22:53:46.000000Z",
      "updated_at": "2026-10-03T22:53:46.000000Z"
    }
  ]
}

A script tag can be blocked by the shop's content security policy

Merchants can restrict which external scripts load. Your script then does not run, no error appears on the page, and the merchant sees a feature that does nothing.

Document the exact domains your script loads from so merchants can allow them, and say so in your listing rather than waiting to be asked.

Webhooks

GET    /api/apps/webhooks
POST   /api/apps/webhooks
DELETE /api/apps/webhooks/{topic}
curl -s -X POST https://shop.example.com/api/apps/webhooks \
  -H "X-UnifyCart-Access-Token: YOUR_INSTALLATION_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{ "topic": "order.placed", "address": "https://your-app.example.com/hooks/orders" }'
Full response
{
  "message": "Webhook registered.",
  "webhook": {
    "topic": "order.placed",
    "address": "https://example.com/hooks/orders",
    "created_at": "2026-10-03T15:53:23-07:00"
  }
}

An app registers its own webhooks by topic rather than asking the merchant to configure them. Register on install, remove on uninstall.

Everything in Webhooks applies: verify the signature, answer quickly, expect duplicates, expect gaps.

The Envelope Is Not the Same as the REST API

The REST API wraps everything in data. This one names the thing: shop, webhooks, script_tags, extensions for lists, and the singular form for a single record. An app built against both will need to handle each shape rather than one helper for the pair.

Rate Limits

An installation carries a rate limit per minute. Stay well inside it.

If you need the whole catalogue, fetch it once and keep it, then use webhooks to learn about changes. An app that re-reads everything on a timer will hit the limit on the merchant's largest shop, which is the one you least want to fail on.

Shop Data

For products, orders, customers and inventory, an app uses the same /api/v1 endpoints described elsewhere in these pages, with the permissions granted at install.

Uninstalling

When a merchant uninstalls, your token stops working.

Handle a sudden run of 401s as an uninstall rather than an outage: stop calling, mark the installation inactive at your end, and stop any scheduled work for that shop. An app that keeps hammering a shop it was removed from is an app that gets removed from the marketplace.