Orbit Commerce
Plugin guides

Testing your plugin

Install your plugin on a real store and exercise it end to end — before it is listed, reviewed, or seen by any merchant.

You do this with an install link: a one-time URL you generate for a plugin you own, which installs it on a store directly. It is not a shortcut around the platform. An install link runs the same install path as a marketplace install, so scope consent, tenant isolation and auditing all behave identically. What you test is what merchants will get.

What you need

Two things, and they are separate accounts because they do different jobs.

WhereWhat it gives you
Partner accountpartner.myorbitcommerce.netYour plugin: identity, scopes, versions, install links
A storeA real store context and the tokens your plugin calls the API with

The partner dashboard does not create stores, and development stores are not self-serve yet — that is on our roadmap. In the meantime, if you are working with us on an integration, talk to your Orbit contact about arranging a store to develop against.

1. Make your plugin unlisted

Install links are the distribution channel for plugins that are not on the public marketplace, so your plugin's visibility must be unlisted or private.

VisibilityWho can installInstall route
publicany merchantmarketplace listing
unlistedanyone with a linkinstall link
privateone named storeinstall link bound to that store

Creating a link for a public plugin returns 400 — public plugins are installed from the listing, and a side-channel for them would only blur the review story.

While you are building, unlisted is what you want. Switch to public and submit for review when you are ready to be listed — see Publishing.

2. Point the plugin at your local app

Your plugin is your own app, and the dashboard embeds it over HTTPS. Browsers will not embed an insecure origin alongside a secure one, so http://localhost will not load in the dashboard.

Run a tunnel — ngrok, Cloudflare Tunnel, or any HTTPS dev proxy — and set your manifest's extensionPoints[].url to the tunnel URL:

{
  "extensionPoints": [
    {
      "target": "dashboard.menu",
      "url": "https://your-tunnel.ngrok.app/embed",
      "label": "My Plugin"
    }
  ]
}

Your app must also allow the embed:

Content-Security-Policy: frame-ancestors 'self'
  https://*.orbitcommerce.net https://*.myorbitcommerce.net
POST /api/plugins/mine/{pluginId}/install-links
Authorization: Bearer <partner JWT>
Content-Type: application/json

Every field is optional. An empty body gives you a reusable link that never expires.

FieldTypeEffect
expiresAtISO 8601Link stops working after this instant. Must be in the future
maxUsesinteger ≥ 1Cap on installs through this link. 1 makes it single-use
targetStoreIduuidBinds the link to one store — only that store can resolve or install through it
curl -X POST \
  'https://api.myorbitcommerce.net/api/plugins/mine/my-plugin/install-links' \
  --header 'Authorization: Bearer <partner JWT>' \
  --header 'Content-Type: application/json' \
  --data '{ "maxUses": 1, "expiresAt": "2026-12-31T23:59:59Z" }'

The response contains the raw token and the shareable URL. This is the only time either is returned — only a SHA-256 hash of the token is stored, so a database leak never exposes a usable link. If you lose it, revoke the link and generate another.

The URL looks like:

https://<vendor-dashboard>/plugins/install-link/<token>

4. Install it on the store

Open that URL while signed in to the store. You will see the same consent screen a merchant sees: your plugin's identity and the exact scopes it is asking for. Accept, and the plugin is installed and activated.

The granted scopes become your plugin's token scopes for that store. If you later change oauth.scopes in your manifest, reinstall — consent is fixed at install time, and an existing install keeps the scopes it was granted.

Under the hood the dashboard calls:

GET  /api/plugins/install-links/{token}          # consent summary
POST /api/plugins/install-links/{token}/install  # install + activate

Both require x-store-id and a store session. You rarely call them directly — the dashboard does it for you — but they are there if you are scripting a test environment.

5. Exercise it

Your plugin now loads in the dashboard. The iframe handshake fires, the SDK receives the session token and store id, and your API calls are scoped to that store.

From here the loop is fast: edit locally, your tunnel serves the change, refresh the iframe. You only return to the partner dashboard when the manifest changes.

Calling the API outside the iframe

For Postman, curl, or the API reference, mint a session token for the store:

POST /api/plugins/{pluginId}/session-token
Authorization: Bearer <admin JWT for the store>
x-store-id: <store uuid>

That token is valid for one hour. For longer runs, exchange it:

POST /oauth/token/exchange
Authorization: Bearer <session token>

which returns an access token (one hour) plus a refresh token (90 days). Refresh via POST /oauth/token/refresh. There is no long-lived access token — see Authentication.

GET    /api/plugins/mine/{pluginId}/install-links            # list, with a usable flag
DELETE /api/plugins/mine/{pluginId}/install-links/{linkId}   # revoke

Revoking is idempotent. Revoke a link the moment it has served its purpose — anyone holding it can install your plugin on a store they control until it expires or hits its use cap.

Unknown, revoked, expired and exhausted links all return the same 404, so a link that stops working gives no signal about which of those it was. A link bound to a different store returns 403.

Troubleshooting

SymptomCause
400 creating a linkThe plugin's visibility is public, or expiresAt is in the past
403 creating a linkYour partner account does not own that plugin
404 opening a linkUnknown, revoked, expired, or out of uses
403 opening a linkThe link is bound to a different store
Iframe stays blankYour app is not reachable over HTTPS, or is missing the frame-ancestors header
401 from /v1/*Bad or expired token — or the install is missing that scope. Both return 401; check the granted scopes before assuming the token is at fault

Next steps