Authentication
Meisei publishes two APIs, and they authenticate differently.
The admin API is everything the dashboard can do; the widget API is the smaller set of endpoints behind the storefront widgets, documented separately in the Widget API reference.
Your base URL
Every store answers on its own address, and that address is the base URL for both APIs.
The examples in this reference use https://your-store.meisei.app/api as a
placeholder. Replace your-store with your own store's name.
You will find the exact address on
Settings → General, in the Address
card. It is also where the dashboard itself is served from, so the address in your browser
bar while you are signed in is the same one, with /api on the end.
https://meisei.app is not a base URL for either API. It signs people in, issues
OAuth tokens, hosts the MCP server and receives Stripe's callbacks. It refuses a
secret key or an OAuth token with a 403 naming your store's address, and a widget request
with a plain 403. Point your integration at your store's address and the same request works
unchanged. Shopify's own traffic (the embedded app, its callbacks and its webhooks) goes to
https://shopify.meisei.app, which serves nothing else.
Your store's address also serves the OpenAPI documents this reference is built from:
/api/openapi for the admin API and /api/openapi/widgets for the widget API, for example
https://your-store.meisei.app/api/openapi. No credential is needed to read them.
Every record they describe is a named schema under components.schemas, so a client
generator for any language gives you a Product or a Review type rather than a new
anonymous shape for each endpoint.
If you build for several stores and do not know their addresses in advance, ask for them while a user is signed in:
Each entry carries the organization's id, name, slug and a dashboardUrl, which is
that store's origin. Its API is <dashboardUrl>/api. An OAuth integration gets the same
thing from the organization on the token: call GET /organization on the store's own
address and read dashboardUrl.
Your keys
Create keys in the dashboard under Settings → API keys.
A publishable key created today reads pk_<organization>_<32 letters and digits>. The
middle part is your organization's ID, which lets a widget request go straight to your store
without a lookup. It grants nothing on its own. Meisei still checks the whole key, so a key
with that part changed is refused. Publishable keys created before this format keep working
as they are, so there is nothing to replace in a theme or a storefront that already has one.
A secret key's token is shown once, when you create it and again when you rotate it. Meisei stores only a hash, so a key you did not write down cannot be recovered, so create a replacement instead. Afterwards the dashboard shows the last few characters only, which is enough to tell two keys apart in a list.
There is no separate test mode. To work against data you can throw away, create a second organization and give it its own keys.
While you are signed in to the dashboard, every reference page offers to fill its examples with one of your keys in place of the placeholder. Publishable keys are filled in whole; secret keys are filled in masked, so swap in the token you saved before running a request.
Calling the admin API
Send the secret key as a bearer token:
Requests from your own browser session in the dashboard are authenticated by that session instead, which is why nothing in the dashboard needs a key.
Permissions
Every secret key carries a set of permissions, written as <group>:<read|write>. Each
endpoint in this reference states the one it requires, in the header beside the method and
path. write covers creating, updating and deleting, and does not imply read. A key
that should both list and edit reviews needs reviews:read and reviews:write.
When you create a key you can take every permission, take read on everything, or pick from the list. A secret key must hold at least one. Publishable keys hold none. They reach the widget endpoints and are not scoped further.
Changing a key's permissions takes effect on its next request. Requests already in flight are unaffected.
Two things no key can do, whatever it holds:
- Manage API keys. Creating, re-scoping and rotating keys is available to a signed-in
user only, so a leaked key can never mint another. Those endpoints answer
403with "This endpoint is only available to a signed-in user, not to an API key." - Reach another organization. A key belongs to the organization that created it, and every record it can see belongs to that organization.
Expiry and rotation
A secret key can be issued to last 30, 60 or 90 days, a year, or forever. Publishable keys do not expire.
An expired key stops authenticating but is not deleted, so you can still rotate it. The response says so:
Rotating mints a new token on the same key, keeping its name and permissions, and gives it a fresh expiry window. There is no overlap. The old token stops working the moment rotation completes, so deploy the new one immediately. Rotating or deleting a key takes effect at once where you made the change, and everywhere within 30 seconds. Rotate a leaked key, since nothing else about the key has to change. Publishable keys cannot be rotated. Delete one you no longer trust and create another.
Both creation and rotation are recorded in your audit trail, along with permission and allowlist changes. The trail never records anything that could authenticate as the key.
Restricting a key to your servers
A secret key can carry an allowlist of IP addresses and CIDR blocks. A request from anywhere else is refused before it reaches your data, and before it spends any of your rate limit, so a leaked key cannot be used to deny you service from an address you never allowed.
Leave the list empty for a key that works from anywhere. Both IPv4 and IPv6 are accepted, and an IPv4-mapped IPv6 address matches an IPv4 entry.
If Meisei cannot work out where a request came from, an allowlisted key is refused rather than waved through. Behind a proxy this depends on how many forwarded hops the deployment is configured to trust, so test the allowlist from the address you intend to use before you rely on it.
The order of checks
Meisei checks a request in this order, which helps when you are debugging a response you did not expect:
- Is the key expired?
- Is the key valid?
- Is the caller's address on the key's allowlist?
- Does the organization have rate limit budget left? See Rate limits.
- Does the key hold the endpoint's permission?
The rate limit is checked before permissions on purpose, because a loop calling an endpoint its key cannot reach still needs limiting.
Authenticating the widget API
Widget endpoints are called from a shopper's browser, so they take a publishable key as
the apiPublishableKey query parameter rather than a bearer token.
They also accept a secret key as a bearer token, which is how a server-rendered
storefront reads the same data. Send the key and leave apiPublishableKey off. The key
names your organization, and any secret key is enough, whatever permissions it holds. A
publishable-key request is rate limited per address and a secret-key one draws on your
organization's ceiling, so a server calling for many shoppers should use a secret key. See
Rate limits.
Anything belonging to one shopper (their wishlists, their loyalty balance) also needs
proof of who is signed in, as three more parameters: customerEmail, timestamp, and a
signature that is the HMAC-SHA256 of the email and timestamp keyed with your widget
signing secret. The signature is made on your server and is good for 24 hours.
POST /widget-signing-secrets/sign makes one for you, so a server holding a secret key
never has to hold the signing secret as well. It needs customers:write, and so does reading
or rotating the secret itself.
The signing secret is not a publishable credential. Anyone holding it can sign as any of your customers, so keep it out of theme files and JavaScript bundles. The full recipe, with examples in several languages, is in Identifying the customer.
OAuth
For an application acting on behalf of many merchants, Meisei is an OAuth 2.1 authorization server. The merchant approves your app, and you get an access token instead of holding a key of theirs.
The permission half of an OAuth scope is exactly the vocabulary above, so
reviews:read means the same thing on a token as it does on a key, plus the standard
openid, profile, email and offline_access scopes. Unlike a key, a grant is always
explicit. There is no legacy token with unrestricted access.
An access token is locked to one organization, chosen by the merchant on the consent
screen, and can never reach another. Discovery is at
https://meisei.app/.well-known/oauth-authorization-server.
This is the same mechanism the MCP server uses when you connect Meisei to a chat client.