Authentication & API keys
Every server-to-server request is authenticated with a merchant secret key in the X-Api-Key header. This page lists every key type and scope, which endpoints each scope opens, and how to create, rotate and protect keys.
POST /api/v1/pay/session/create HTTP/1.1
Host: api.qasimha.com
Content-Type: application/json
X-Api-Key: sk_live_...The key identifies your merchant account. You never send a merchant ID; all data is scoped to the key's merchant automatically. A merchantId in a request body is ignored.
Key types
| Type | Prefix | Header | Where it goes | What it can do |
|---|---|---|---|---|
| Secret (UAT) | sk_test_ | X-Api-Key | Your server only | Every merchant endpoint its scopes allow, on UAT test data |
| Secret (production) | sk_live_ | X-Api-Key | Your server only | Every merchant endpoint its scopes allow, on real orders |
| Publishable (UAT) | pk_test_ | X-Public-Key | Web pages, mobile apps | Only GET /api/v1/widget/plans (see Plans widget) |
| Publishable (production) | pk_live_ | X-Public-Key | Web pages, mobile apps | Only GET /api/v1/widget/plans |
A publishable key always has the single scope widget:read. Every other endpoint refuses it: sent in X-Api-Key, it gets 401. The widget endpoint never takes a secret key
Environments
| Environment | API base URL | Merchant Portal | Keys |
|---|---|---|---|
| UAT (testing) | https://api-uat.qasimha.com | https://portal-uat.qasimha.com | sk_test_, pk_test_ |
| Production | https://api.qasimha.com | https://portal.qasimha.com | sk_live_, pk_live_ |
Start in UAT, then flip a switch to go live. Build and test everything against the UAT environment first. When you're ready for production, switch the base URL and the API key together, since each environment only accepts its own key. Keep both values in configuration, never hardcoded, so promoting to live is a config change rather than a code change.
The server SDKs make this hard to get wrong: they derive the host from your key's prefix and refuse to start if an explicit environment setting contradicts it.
Treat the two environments as completely separate systems. Keys, webhook endpoints, and their signing secrets exist only in the environment where you created them. A key from one environment calling the other gets a 401, so you'll need to register your webhook endpoint and store a fresh secret in each environment.
Create a key
API keys are managed in the Merchant Portal. A few rules apply before you start only users with the owner or admin role can view, create, or revoke keys.
-
Sign in to the right environment. Open the Merchant Portal for the environment you need (UAT or production). Keys don't carry over between environments, so make sure you're in the right one before creating anything.
-
Go to Settings → API Keys and create a key. You'll choose:
- a name (required, up to 100 characters) that identifies which system uses the key, such as
web-backendormobile-app; - the type: secret for server-side calls, or publishable for the widget and promo views;
- the scopes, secret keys only. Scopes limit what the key can do. A secret key created with no scope gets
orders:readonly, so grant exactly what each system needs; - an optional expiry in days. When a key expires, the API refuses it exactly as if it had been revoked.
- a name (required, up to 100 characters) that identifies which system uses the key, such as
-
Copy the key immediately. The full key is shown once, at creation, and can never be read back. Store it in your secrets manager and load it at runtime from an environment variable such as
QASIMHA_API_KEY. If you lose it, revoke the key and create a new one.
Rules and limits:
- Maximum 10 active keys per merchant, counting secret and publishable keys together. Creating an eleventh returns
400 LIMIT_EXCEEDED, so revoke unused keys before adding new ones. - Keys can't be managed by API. No endpoint accepts an API key for creating, rotating, or revoking keys or managing your team. Key administration stays in the portal, behind the owner/admin roles above.
- The key list never shows full keys. It displays each key's prefix, type, environment, scopes, last use, and expiry. The prefix is enough to identify a key in logs and support requests without exposing it.
Scopes
| Scope | Grants |
|---|---|
orders:read | List and read orders and checkout links; download order contracts and exports |
orders:write | Everything in orders:read, plus create orders (sessions, links, QR, POS), capture, fulfil, refund and cancel |
settlements:read | Read settlements and the settlement overview |
webhooks:manage | Register, read, change and delete webhook endpoints; rotate secrets; ping; read and replay deliveries |
widget:read | Publishable keys only: read plan quotes for the widget |
Give each key only the scopes its system needs. For example, a reporting job needs orders:read and settlements:read, not orders:write.
Which endpoints each scope opens
All paths are under /api/v1.
| Endpoint | Required |
|---|---|
POST /pay/session/create, POST /pay/pos/create, POST /pay/qr/create | orders:write |
POST /merchant/checkout-links | orders:write |
POST /merchant/checkout-links/{id}/cancel | orders:write |
GET /merchant/checkout-links, GET /merchant/checkout-links/{id} | orders:read |
GET /merchant/orders, GET /merchant/orders/counts, GET /merchant/orders/export.xlsx | orders:read |
GET /merchant/orders/{id}, GET /merchant/orders/by-reference/{orderReferenceId}, GET /merchant/orders/{id}/contract | orders:read |
POST /merchant/orders/{id}/capture, /fulfill, /refund, /cancel | orders:write |
GET /merchant/settlements, GET /merchant/settlements/overview | settlements:read |
Every /merchant/webhooks endpoint, reads included | webhooks:manage |
GET /widget/plans | Publishable key (widget:read) in X-Public-Key |
Wherever orders:read is required, a key with orders:write also works.
What a refused request looks like
| Situation | Response |
|---|---|
No X-Api-Key, an unknown, revoked or expired key, a key from the other environment, or a pk_ key in X-Api-Key | 401 with an empty body and the header WWW-Authenticate: ApiKey realm="merchant-api" |
| Valid key without the required scope | 403 with the envelope, code: "FORBIDDEN", and a message saying the key's scope doesn't allow the call |
| Too many requests for this key | 429 TOO_MANY_REQUESTS with a Retry-After header (see Rate limits) |
Don't parse a JSON body on 401; there isn't one. Every error is listed in Errors and rate limits.
Rotate a key
Revocation takes effect immediately, on the next request the old key makes. That makes rotation a zero-downtime operation if you overlap old and new:
- Create a replacement key with the same scopes as the old one.
- Deploy it everywhere. Update every server and service that uses the old key, and let them all pick up the new one.
- Wait for traffic to drain. Watch the old key's last used time in the portal and confirm it stops moving. That tells you nothing is still calling with it.
- Revoke the old key in Settings → API Keys.
If a key leaks, reverse the order: revoke it first, then issue a replacement. Speed matters more than overlap when the key is compromised.
Portal sessions
The merchant endpoints also accept a Merchant Portal session token (JWT bearer). This exists mainly for testing from the portal, and it follows the person's team role: a viewer can browse orders but can't create them, matching what they can do in the UI.
Server-to-server integrations should always use API keys. Session tokens expire with the login and inherit a human's role, so they don't belong in automated systems.
Never expose a secret key. Never put an
sk_key in a browser, mobile app, source repository, log file, support ticket or AI chat assistant. Use a publishablepk_key for anything that runs on a customer's device. If a secret key leaks, revoke it in the portal and issue a new one.
- Use a separate key per system (store, ERP, reporting job), so you can revoke one without stopping the others. Each key also gets its own rate-limit budget.
- Set an expiry on keys used for short projects or contractors.
- The SDKs never log a full key;
maskedKey()shows the prefix and the last four characters. Do the same in your own logs.
Troubleshooting
I get 401 with my new key
Check that the key prefix matches the host: sk_test_ with https://api-uat.qasimha.com, sk_live_ with https://api.qasimha.com. Check for spaces or line breaks copied with the key, and that the header name is exactly X-Api-Key. A key that was revoked or has passed its expiry also gets 401.
I get 403 FORBIDDEN on one endpoint but not others
The key lacks that endpoint's scope. Check the table above. Scopes are fixed when the key is created, so create a new key with the scopes you need and revoke the old one.
400 LIMIT_EXCEEDED when creating a key
You already have 10 active keys. Revoke keys you no longer use.
Related
Updated 1 day ago
