API Key Provisioning
Issue API keys for a merchant so their developers can integrate with the Miracle PSP payment API. API keys authenticate every request the merchant's application makes to the platform.
How to Issue Keys
Keys are issued with POST /v1/api-keys. Two credentials can call it, and both follow the same rules:
- An operator session: the Portal, or a JWT from the same login.
- A tenant secret key (
sk_test_orsk_live_) that holds theidentity-access:api-key:createpermission. Your own backend can then issue keys for your merchants with no person in the loop. The key grants only the permissions it holds itself, so give it every permission its merchant keys will carry.
A merchant key in the stripe_compat format needs the four permissions that format checks: payment-experience:checkout:create and payment-experience:checkout:view for checkout sessions, payment-processing:transaction:view to read a payment intent and payment-processing:transaction:refund to create a refund. A native merchant key that creates payments with POST /v1/payments needs payment-processing:transaction:create.
Portal: open API Keys and choose Create key. An operator decides whether the key belongs to the tenant or to a merchant and finds the merchant by name among your tenant's merchants. A merchant user issues keys only for their own merchant. For a secret key the wizard lists only the permissions that the owner level allows and that you hold yourself. The key is issued in the mode the Portal is switched to: test keys in test mode, live keys in live mode.
curl -X POST https://<gate-host>/v1/api-keys \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: 0f4d1c9e-3a52-4c1b-9e1d-6b0f7a2c8d11" \
-H "Content-Type: application/json" \
-d '{
"name": "acme-backend",
"ownerType": "merchant",
"merchantId": "<merchantId>",
"keyType": "secret",
"livemode": true,
"permissions": [
"payment-processing:transaction:create",
"payment-processing:transaction:view",
"payment-processing:transaction:refund"
]
}'| Field | Required | Meaning |
|---|---|---|
name | yes | Human-readable label, 1 to 256 characters. |
ownerType | yes | merchant (one merchant's data) or tenant (the whole tenant). See Key Scope. |
merchantId | for merchant keys | A merchant of your tenant. |
keyType | no | secret (default) issues an sk_ and pk_ pair. |
inboundFormat | no | API format the key calls the platform in. miracle_native (default) is the native API. stripe_compat, adyen_hosted and checkoutcom_hpp let a merchant's existing integration call the platform in that provider's format; such a key must be secret, only an operator session or a tenant key can issue it, and the format never changes. |
livemode | yes | false issues test keys, true issues live keys. |
permissions | yes | Permission codes the key carries. See Permissions. |
ipAllowlist | no | Up to 50 CIDR ranges the key may be used from. Empty means no restriction. |
expiresAt | no | ISO 8601 expiry. Omit it for a key that never expires. |
A secret key is issued as a pair:
| Key | Prefix | Purpose |
|---|---|---|
| Secret key | sk_test_ or sk_live_ | Authenticates server-side API requests. Must be kept confidential. |
| Publishable key | pk_test_ or pk_live_ | Used in client-side code (e.g., payment forms). Safe to expose in frontend. |
| Client secret | cs_ | Session-scoped token for HPP and widget flows. Generated with each CheckoutSession, short-lived and bound to that session; it is never issued here. |
The response returns the secret key in data.secretKey and the publishable key in data.publicKey.
The secret key is shown only once. Copy it immediately and store it securely. The platform keeps only hashes of it, so neither the API nor the Portal can show it again.
The client secret is returned in the POST /v1/checkout-sessions response and is used by the payer's browser to authenticate requests within that checkout session.
Issuing Rules
Idempotency-Keyis required, 1 to 128 characters, unique per issuing request. Without it the call answers 400IDEMPOTENCY_KEY_REQUIRED.- A retry never issues a second key. The same
Idempotency-Keywith the same body answers 409API_KEY_ALREADY_ISSUED, anderror.instanceholds the id of the key already issued. The sameIdempotency-Keywith a different body answers 409DUPLICATE_TRANSACTION. - A retry never shows the secret again. If the first response was lost, revoke the key named in
error.instanceand issue a new one under a newIdempotency-Key. - A secret key issues keys only in its own mode.
sk_test_issues test keys andsk_live_issues live keys; a differentlivemodein the body answers 403. An operator session picks the mode in the body. - Every key records who issued it.
createdByis the id of the issuing user or API key, andcreatedByTypesays which (userorapi_key). Revoking an issuing key, or disabling an issuing user, leaves the keys they issued active; find them withGET /v1/api-keys?createdBy=<id>and revoke them explicitly. - Keys stop with their owner. While a merchant, or the tenant above it, is suspended or disabled, its keys answer 401 and no key can be issued or rotated for it (409
CONFLICT). The same keys work again once the merchant or tenant is reinstated; a suspension reaches every key within 30 seconds. - Key management is rate limited: up to 30 issuing or changing requests and 120 reads a minute. Over the limit the call answers 429.
Key Types
| Environment | Secret Key | Publishable Key | Purpose |
|---|---|---|---|
| Test | sk_test_... | pk_test_... | Sandbox transactions only. Use for integration development and testing. |
| Live | sk_live_... | pk_live_... | Production transactions. |
Always start with test keys. Issue and share live keys only after the merchant has tested their integration in sandbox mode and passed the go-live checklist.
Key Scope
API keys can be scoped to different levels of the entity hierarchy:
| Scope | Access | Use Case |
|---|---|---|
| Merchant-scoped | Operations for a single merchant only | Standard merchant integration: one key pair per merchant. |
| Tenant-scoped | Cross-merchant access within the tenant | Bulk operations, reporting, settlements, and administrative APIs that span multiple merchants. |
ownerType has no default: choose the scope on every key. Issue tenant-scoped keys only to your own engineering team, never to a merchant. A merchant user can issue keys only for their own merchant. A key for a merchant outside your tenant answers 403.
Permissions
Every key carries an explicit list of permission codes in the {domain}:{resource}:{action} format, for example payment-processing:transaction:create. The list is required when you issue a key. An empty list issues a key that authenticates but can do nothing.
The list must fit inside two bounds:
- The owner level. A merchant key carries only merchant-level permissions, never operator ones. A code outside the owner level answers 400.
- The issuer. You can grant only permissions you hold yourself, whether you call from the Portal or with a secret key. A code you do not hold answers 403.
The platform rejects a request that exceeds either bound; it never trims the list silently.
Examples of restricted keys:
- A key that can only create payments but not issue refunds
- A read-only key for reporting and transaction lookups
- A key limited to webhook management
To change a key's permissions, send PATCH /v1/api-keys/{id} with a new permissions list. The new list replaces the old one and is checked against the same two bounds.
Sharing Keys with Merchant Developers
When providing API keys to a merchant's development team:
- Share the secret key through a secure channel: a secrets manager, encrypted message, or secure file transfer. Never send secret keys over email or unencrypted chat.
- Share a link to the merchant documentation so they can start with the integration guides.
- Recommend starting with test keys: the merchant should build and verify their integration in sandbox mode before switching to live keys.
Managing Keys
View Keys
GET /v1/api-keys lists the keys within your scope in the caller's mode, with cursor pagination and filters status, merchantId and createdBy. Each key comes with its name, publishable key, mode, status, permissions, creation and expiry dates, and who issued it. The secret key and its hashes are never returned. In the Portal, open API Keys.
Rotate Keys
POST /v1/api-keys/{id}/rotate with an Idempotency-Key and a body such as { "graceHours": 24 } issues a successor with the same name, scope, permissions, IP allowlist and expiry. The new secret is returned once. The old key keeps working for graceHours (0 to 720, default 24) and then expires, so the merchant can switch without downtime; 0 cuts over at once.
- Only an active key can be rotated; a revoked or expired one answers 409.
- Rotation stays in the caller's mode, and the caller must hold every permission the key carries (403 otherwise).
- A retry with the same
Idempotency-Keyanswers 409API_KEY_ALREADY_ISSUEDwith the successor's id inerror.instance.
Revoke Keys
DELETE /v1/api-keys/{id}, or POST /v1/api-keys/{id}/revoke to get the revoked key back in the response. In the Portal, select the key and choose Revoke. A revoked key stops working within 30 seconds, and any request made with it receives an authentication error. Revocation cannot be undone.
If a secret key is compromised, revoke it immediately and issue a replacement, or rotate it with "graceHours": 0.