API Keys

Create and manage service API keys for the Amboss Payments API, scoped per environment with explicit resource permissions.

Service API keys authenticate backend calls to the Payments API. Every key is scoped to a single environment and carries an explicit resource permission set.

Payments uses the x-api-key header, not Authorization: Bearer. The other Amboss products (Space, Reflex, Magma) use Bearer tokens — see Developer → Authentication for the per-product table.

x-api-key: amb_live_PvDaoBz6TXhuMKxk0VkckiPUTX9BTSDyCT2Pqc_vgOw

Keys look like amb_test_… (sandbox environments) or amb_live_… (live environments). The prefix tells you which environment the key belongs to at a glance.

Permissions

Permissions are <resource>:<level> pairs. WRITE implies READ. Grant the minimum set your integration needs.

ResourceREADWRITE
ENVIRONMENTSpayment.environment.find_one, find_manyREAD + payment.environment.create (sandbox only), delete
WALLETSpayment.wallet.find_one, find_manyREAD + payment.wallet.create, delete
PAYMENTSpayment.transaction.find_one, find_manyREAD + payment.transaction.create_receive, create_send
WEBHOOKSpayment.webhook_endpoint.find_*, payment.webhook_event.*READ + payment.webhook_endpoint.create, update, rotate_secret, delete

WRITE implies READ. Grant the minimum set your integration needs.

You can additionally scope a key to a single wallet_id to limit blast radius — the key will only work against that wallet.

Creating live environments still requires the master account. A key with ENVIRONMENTS: WRITE can create SANDBOX environments programmatically (useful for spinning up per-tenant or per-CI-run envs), but payment.environment.create rejects type: LIVE from API keys — only the master-account dashboard JWT can promote to live.

Create a key

Key creation requires master-account dashboard auth (Authorization: Bearer <dashboard-token>), not another API key. This is intentional: a leaked API key cannot mint a fresh one, which blocks privilege-escalation.

Likewise, listing and revoking keys go through the dashboard JWT — no service_api_keys:write permission exists on keys themselves.

mutation CreateApiKey {
  service_api_key {
    create(input: {
      name: "backend-prod"
      environment_id: "81b73615-ddf3-46e3-943a-467c3e442e04"
      permissions: [
        { resource: WALLETS, level: WRITE }
        { resource: PAYMENTS, level: WRITE }
        { resource: WEBHOOKS, level: WRITE }
      ]
    }) {
      plaintext_key
      key {
        id
        name
        key_prefix
        key_suffix
        permissions { resource level }
      }
    }
  }
}
curl -X POST https://rails.amboss.tech/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AMBOSS_DASHBOARD_TOKEN" \
  -d '{
    "query": "mutation($input: CreateServiceApiKeyInput!) { service_api_key { create(input: $input) { plaintext_key key { id name key_prefix permissions { resource level } } } } }",
    "variables": {
      "input": {
        "name": "backend-prod",
        "environment_id": "81b73615-ddf3-46e3-943a-467c3e442e04",
        "permissions": [
          { "resource": "WALLETS",  "level": "WRITE" },
          { "resource": "PAYMENTS", "level": "WRITE" },
          { "resource": "WEBHOOKS", "level": "WRITE" }
        ]
      }
    }
  }'
import { GraphQLClient, gql } from "graphql-request";

const client = new GraphQLClient("https://rails.amboss.tech/graphql", {
  headers: { Authorization: `Bearer ${process.env.AMBOSS_DASHBOARD_TOKEN}` },
});

const CREATE_API_KEY = gql`
  mutation CreateApiKey($input: CreateServiceApiKeyInput!) {
    service_api_key {
      create(input: $input) {
        plaintext_key
        key { id name key_prefix permissions { resource level } }
      }
    }
  }
`;

const { service_api_key } = await client.request(CREATE_API_KEY, {
  input: {
    name: "backend-prod",
    environment_id: process.env.AMBOSS_ENV_ID,
    permissions: [
      { resource: "WALLETS", level: "WRITE" },
      { resource: "PAYMENTS", level: "WRITE" },
      { resource: "WEBHOOKS", level: "WRITE" },
    ],
  },
});

// Save plaintext_key in your secret manager — it will never be shown again.
const apiKey = service_api_key.create.plaintext_key;

Example response:

{
  "data": {
    "service_api_key": {
      "create": {
        "plaintext_key": "amb_test_PvDaoBz6TXhuMKxk0VkckiPUTX9BTSDyCT2Pqc_vgOw",
        "key": {
          "id": "28826356-6213-4aad-ae56-8c86e7cf8923",
          "name": "backend-prod",
          "key_prefix": "amb_test_PvDa",
          "key_suffix": "vgOw",
          "permissions": [
            { "resource": "WALLETS",  "level": "WRITE" },
            { "resource": "PAYMENTS", "level": "WRITE" },
            { "resource": "WEBHOOKS", "level": "WRITE" }
          ]
        }
      }
    }
  }
}

plaintext_key is returned once at creation. It's never retrievable afterwards. Store it in your secret manager immediately. If you lose it, revoke the key and create a new one.

List keys

query ListApiKeys($environment_id: String!) {
  service_api_keys {
    get_many(environment_id: $environment_id) {
      list {
        id
        name
        key_prefix
        key_suffix
        active
        last_used_at
        expires_at
        permissions { resource level }
      }
    }
  }
}

Keys never expose their plaintext on listing — only the prefix/suffix and metadata. Use the prefix to identify keys in logs.

Rotate a key

There's no rotate_secret mutation for API keys (unlike webhook endpoints). The pattern is:

  1. Create a new key with the same permissions.
  2. Update consumers to use the new key.
  3. Revoke the old key once traffic has shifted.
mutation RevokeKey($id: String!) {
  service_api_key {
    revoke(id: $id) {
      id
      active
    }
  }
}

Revoked keys can no longer authenticate; outstanding requests in flight will return UNAUTHENTICATED on the next call.

Auth errors

GraphQL error codeMeaning
UNAUTHENTICATEDHeader missing, key is invalid, or key has been revoked
FORBIDDENKey is valid but lacks the required resource:level

Both surface as standard GraphQL errors with extensions.code.

Next steps