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

TypePrefixHeaderWhere it goesWhat it can do
Secret (UAT)sk_test_X-Api-KeyYour server onlyEvery merchant endpoint its scopes allow, on UAT test data
Secret (production)sk_live_X-Api-KeyYour server onlyEvery merchant endpoint its scopes allow, on real orders
Publishable (UAT)pk_test_X-Public-KeyWeb pages, mobile appsOnly GET /api/v1/widget/plans (see Plans widget)
Publishable (production)pk_live_X-Public-KeyWeb pages, mobile appsOnly 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

EnvironmentAPI base URLMerchant PortalKeys
UAT (testing)https://api-uat.qasimha.comhttps://portal-uat.qasimha.comsk_test_, pk_test_
Productionhttps://api.qasimha.comhttps://portal.qasimha.comsk_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.

  1. 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.

  2. 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-backend or mobile-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:read only, 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.
  3. 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

ScopeGrants
orders:readList and read orders and checkout links; download order contracts and exports
orders:writeEverything in orders:read, plus create orders (sessions, links, QR, POS), capture, fulfil, refund and cancel
settlements:readRead settlements and the settlement overview
webhooks:manageRegister, read, change and delete webhook endpoints; rotate secrets; ping; read and replay deliveries
widget:readPublishable 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.

EndpointRequired
POST /pay/session/create, POST /pay/pos/create, POST /pay/qr/createorders:write
POST /merchant/checkout-linksorders:write
POST /merchant/checkout-links/{id}/cancelorders:write
GET /merchant/checkout-links, GET /merchant/checkout-links/{id}orders:read
GET /merchant/orders, GET /merchant/orders/counts, GET /merchant/orders/export.xlsxorders:read
GET /merchant/orders/{id}, GET /merchant/orders/by-reference/{orderReferenceId}, GET /merchant/orders/{id}/contractorders:read
POST /merchant/orders/{id}/capture, /fulfill, /refund, /cancelorders:write
GET /merchant/settlements, GET /merchant/settlements/overviewsettlements:read
Every /merchant/webhooks endpoint, reads includedwebhooks:manage
GET /widget/plansPublishable 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

SituationResponse
No X-Api-Key, an unknown, revoked or expired key, a key from the other environment, or a pk_ key in X-Api-Key401 with an empty body and the header WWW-Authenticate: ApiKey realm="merchant-api"
Valid key without the required scope403 with the envelope, code: "FORBIDDEN", and a message saying the key's scope doesn't allow the call
Too many requests for this key429 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:

  1. Create a replacement key with the same scopes as the old one.
  2. Deploy it everywhere. Update every server and service that uses the old key, and let them all pick up the new one.
  3. 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.
  4. 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 publishable pk_ 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




Did this page help you?