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_vgOwKeys 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.
| Resource | READ | WRITE |
|---|---|---|
ENVIRONMENTS | payment.environment.find_one, find_many | READ + payment.environment.create (sandbox only), delete |
WALLETS | payment.wallet.find_one, find_many | READ + payment.wallet.create, delete |
PAYMENTS | payment.transaction.find_one, find_many | READ + payment.transaction.create_receive, create_send |
WEBHOOKS | payment.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:
- Create a new key with the same permissions.
- Update consumers to use the new key.
- 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 code | Meaning |
|---|---|
UNAUTHENTICATED | Header missing, key is invalid, or key has been revoked |
FORBIDDEN | Key is valid but lacks the required resource:level |
Both surface as standard GraphQL errors with extensions.code.