Ask for an invitation
Menu

Endpoint

Create an API key

POST/v1/keys

The response is the only time secret is shown. In live mode a key cannot do this: it answers passkey_approval_required with reason live_key_dashboard_only, and a live key is made and rolled in the dashboard with a passkey. A key that is limited to IP addresses or has an expires_at makes and rolls only keys inside its own list and ending no later (parameter_invalid, reason wider_than_caller); with neither given, the new key takes its maker's. A replay of the same request (Idempotent-Replayed: true) answers the key without secret, which is never kept.

Scope a restricted key needs
keys:write
Publishable key
Not allowed: secret or restricted key only
Idempotency-Key
Recommended. It makes a retry safe.

Parameters

Parameters of Create an API key
ParameterType and placeMeaning
Orrerie-VersionstringheaderAPI version date. Default: the version the key was created with. Example 2026-10-11.
Idempotency-KeystringheaderMakes a retry safe. Kept 24 hours. 1 to 255 characters.

Request body

A JSON object. The body is required. A field the call does not list is refused with parameter_unknown.

Request body of Create an API key
FieldTypeMeaning
namerequiredstringUp to 100 characters.
typerequiredstringA full secret key can only be created in the dashboard. One of restricted, publishable.
scopesarray of valuesRequired for restricted. Each must be held by the calling key.
allowed_ipsarray of valuesUp to 20 IPv4/IPv6 addresses or CIDR ranges. Empty: any.
expires_atstring (date-time)Optional end of life.

Example request

curl

curl https://api.orrerie.com/v1/keys \
  -H "Authorization: Bearer $ORRERIE_SECRET_KEY" \
  -H "Idempotency-Key: 8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Shop backend",
  "type": "restricted",
  "scopes": [
    "checkout_sessions:write",
    "orders:read",
    "refunds:write",
    "events:read"
  ]
}'

JavaScript

const response = await fetch("https://api.orrerie.com/v1/keys", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ORRERIE_SECRET_KEY}`,
    "Idempotency-Key": "8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "name": "Shop backend",
    "type": "restricted",
    "scopes": [
      "checkout_sessions:write",
      "orders:read",
      "refunds:write",
      "events:read"
    ]
  }),
});
const result = await response.json();
if (!response.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
console.log(result);

Python

import os
import requests

response = requests.post(
    "https://api.orrerie.com/v1/keys",
    headers={"Authorization": f"Bearer {os.environ['ORRERIE_SECRET_KEY']}", "Idempotency-Key": "8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a"},
    json={
        "name": "Shop backend",
        "type": "restricted",
        "scopes": [
            "checkout_sessions:write",
            "orders:read",
            "refunds:write",
            "events:read",
        ],
    },
    timeout=30,
)
result = response.json()
if not response.ok:
    raise RuntimeError(f"{result['error']['code']}: {result['error']['message']}")
print(result)

PHP

<?php
$request = curl_init('https://api.orrerie.com/v1/keys');
curl_setopt_array($request, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('ORRERIE_SECRET_KEY'),
        'Idempotency-Key: 8f14e45f-ceea-467f-a9d2-6f1c2b3d4e5a',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name' => 'Shop backend',
        'type' => 'restricted',
        'scopes' => [
            'checkout_sessions:write',
            'orders:read',
            'refunds:write',
            'events:read',
        ],
    ]),
]);
$body = curl_exec($request);
if ($body === false) {
    throw new RuntimeException(curl_error($request));
}
$result = json_decode($body, true);
if (curl_getinfo($request, CURLINFO_HTTP_CODE) >= 400) {
    throw new RuntimeException($result['error']['code'] . ': ' . $result['error']['message']);
}
print_r($result);

Responses

201 Created

The answer is the api_key object. Headers: Orrerie-Request-Id.

Example response, 201

{
  "id": "key_6e3gg2c6ea176xxdek2nar6v",
  "object": "api_key",
  "livemode": true,
  "created": "2026-09-03T09:50:00.000Z",
  "name": "Shop backend",
  "type": "restricted",
  "prefix": "sk_live_",
  "last4": "x9Tq",
  "scopes": [
    "checkout_sessions:write",
    "orders:read",
    "refunds:write",
    "events:read"
  ],
  "allowed_ips": [],
  "status": "active",
  "api_version": "2026-10-11",
  "created_by": {
    "type": "api_key",
    "id": "key_856gx1r2c4aj2rjf2yjtze55"
  },
  "last_used_at": null,
  "expires_at": null,
  "rolled_from": null,
  "secret": "sk_live_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAx9Tq"
}

Any other status: an error

A status of 400 or above answers with the error object. Branch on its code, never on its message.

Errors

Beside the errors every call can answer with (a bad key, a missing scope, the rate limit), this call answers with:

Errors of Create an API key
CodeHTTPMeaning
key_limit_reached4032 (Unverified) or 20 (Verified) live keys. Test keys: 20 in either tier.
insufficient_scope403The key lacks the scope the endpoint needs; error.scope names it.
passkey_approval_required403The action can only be done by a signed-in member in the dashboard (settlement asset, key-holding roles, approvals). No API key can do it.