Miracle Docs

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_ or sk_live_) that holds the identity-access:api-key:create permission. 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"
    ]
  }'
FieldRequiredMeaning
nameyesHuman-readable label, 1 to 256 characters.
ownerTypeyesmerchant (one merchant's data) or tenant (the whole tenant). See Key Scope.
merchantIdfor merchant keysA merchant of your tenant.
keyTypenosecret (default) issues an sk_ and pk_ pair.
inboundFormatnoAPI 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.
livemodeyesfalse issues test keys, true issues live keys.
permissionsyesPermission codes the key carries. See Permissions.
ipAllowlistnoUp to 50 CIDR ranges the key may be used from. Empty means no restriction.
expiresAtnoISO 8601 expiry. Omit it for a key that never expires.

A secret key is issued as a pair:

KeyPrefixPurpose
Secret keysk_test_ or sk_live_Authenticates server-side API requests. Must be kept confidential.
Publishable keypk_test_ or pk_live_Used in client-side code (e.g., payment forms). Safe to expose in frontend.
Client secretcs_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-Key is required, 1 to 128 characters, unique per issuing request. Without it the call answers 400 IDEMPOTENCY_KEY_REQUIRED.
  • A retry never issues a second key. The same Idempotency-Key with the same body answers 409 API_KEY_ALREADY_ISSUED, and error.instance holds the id of the key already issued. The same Idempotency-Key with a different body answers 409 DUPLICATE_TRANSACTION.
  • A retry never shows the secret again. If the first response was lost, revoke the key named in error.instance and issue a new one under a new Idempotency-Key.
  • A secret key issues keys only in its own mode. sk_test_ issues test keys and sk_live_ issues live keys; a different livemode in the body answers 403. An operator session picks the mode in the body.
  • Every key records who issued it. createdBy is the id of the issuing user or API key, and createdByType says which (user or api_key). Revoking an issuing key, or disabling an issuing user, leaves the keys they issued active; find them with GET /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

EnvironmentSecret KeyPublishable KeyPurpose
Testsk_test_...pk_test_...Sandbox transactions only. Use for integration development and testing.
Livesk_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:

ScopeAccessUse Case
Merchant-scopedOperations for a single merchant onlyStandard merchant integration: one key pair per merchant.
Tenant-scopedCross-merchant access within the tenantBulk 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:

  1. 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.
  2. Share a link to the merchant documentation so they can start with the integration guides.
  3. 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-Key answers 409 API_KEY_ALREADY_ISSUED with the successor's id in error.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.

On this page