API keys
An API key is the credential pair (keyId + secret) you sign Fulkruma requests with. Every workspace can have multiple keys, each with its own name — one for production, one for staging, one for a back-office script. Fulkruma keys carry the AKIAFULK* prefix; there's no test-mode/live-mode split (unlike Plugipay) because the merchant subscription model is single-environment. The Python SDK wraps three endpoints behind fulkruma.api_keys. For the HTTP surface see API → API keys, and for how keys are used in the SDK constructor see API → Authentication.
Namespace
fulkruma.api_keys # ApiKeysResources
No update — key metadata is immutable. To "rename" a key, revoke and re-create.
Methods
list
fulkruma.api_keys.list(*, on_behalf_of: str | None = None) -> dict
Returns {"apiKeys": [...]}: every key in the workspace, newest first, including revoked ones (filter on revokedAt to find active). The secret is never returned on list — only a secretPreview for recognising a key by eye.
result = fulkruma.api_keys.list()
for k in result["apiKeys"]:
state = f"revoked {k['revokedAt']}" if k.get("revokedAt") else "active"
print(f"{k['keyId']} — {k['name']} — {state}")
create
fulkruma.api_keys.create(
*,
name: str,
scopes: list[str] | None = None,
on_behalf_of: str | None = None,
) -> dict
Mints a new key. name is required (1–120 characters) — make it specific so you can identify the key later in the dashboard. scopes is any of "read", "write", "admin"; the server defaults to ["read", "write"]. The response is {"apiKey": {...}, "secret": "..."} — this is the only call that returns the secret. The SDK auto-mints an idempotency key.
result = fulkruma.api_keys.create(name="Production server (jakarta-1)", scopes=["read", "write"])
print(result["apiKey"]["keyId"]) # "AKIAFULK..."
print(result["secret"]) # STORE NOW — never returned again
The secret appears once. Fulkruma shows the secret in the response to
createand never again. Stash it in your secret manager (AWS Secrets Manager, Vault, env file on a locked-down box) before the function returns. If you lose it, revoke the key and mint a new one.
revoke
fulkruma.api_keys.revoke(key_id: str, *, on_behalf_of: str | None = None) -> dict
Revokes a key and returns {"apiKey": {"id": ..., "revokedAt": ...}}; the request carries no body. Subsequent requests signed with that key fail immediately with 401 REVOKED_KEY. There's no un-revoke; mint a fresh key.
fulkruma.api_keys.revoke("clx4q8k2b0001")
Note the argument is the record id, not the AKIAFULK* keyId. They're different — the record ID is what the management API uses; the keyId is what you sign requests with.
Types
Each method returns the envelope's data as a dict. Key shape:
# api_keys.list()["apiKeys"][0]
{
"id": "clx4q8k2b0001", # record ID, used by .revoke()
"name": "Production server",
"keyId": "AKIAFULK...", # the access key
"secretPreview": "fulksk_a…b3z9",
"scopes": ["read", "write"],
"createdAt": "...",
"lastUsedAt": "..." | None,
"revokedAt": "..." | None,
"createdBy": "..." | None,
}
api_keys.create() returns {"apiKey": {"id", "name", "keyId", "scopes", "createdAt"}, "secret": "fulksk_..."}.
scopes is recorded on the key and shown in the dashboard; the API does not yet check read / write / admin route by route, so treat any key as full access to its workspace. See API → API keys.
Common patterns
Mint a per-service key and stash it. Per-service keys are easier to revoke individually than one shared key:
from datetime import datetime
def mint_server_key(fulkruma, service_name: str, secret_manager) -> str:
result = fulkruma.api_keys.create(
name=f"Auto-provisioned: {service_name} ({datetime.utcnow().strftime('%Y-%m-%d')})",
)
key_id = result["apiKey"]["keyId"]
secret_manager.put(f"{service_name}/FULKRUMA_KEY_ID", key_id)
secret_manager.put(f"{service_name}/FULKRUMA_KEY_SECRET", result["secret"])
return key_id
Audit active keys. For periodic security reviews:
from datetime import datetime, timezone
def audit_active(fulkruma):
result = fulkruma.api_keys.list()
active = [k for k in result["apiKeys"] if not k.get("revokedAt")]
now = datetime.now(timezone.utc)
for k in active:
created = datetime.fromisoformat(k["createdAt"].replace("Z", "+00:00"))
print(f"{k['keyId']} — {k['name']} — {(now - created).days}d old — last used {k.get('lastUsedAt') or 'never'}")
Rotate a key with overlap. Mint-then-revoke is safer than revoke-then-mint — you have a window where both keys work, so you can deploy the new credentials before invalidating the old:
from datetime import datetime
def rotate_key(fulkruma, old_record_id: str, name: str, deploy_fn):
# 1. Mint the new one
result = fulkruma.api_keys.create(
name=f"{name} (rotation {datetime.utcnow().strftime('%Y-%m-%d')})",
)
# 2. Deploy new credentials to your services (your CI's job)
deploy_fn(result["apiKey"]["keyId"], result["secret"])
# 3. Revoke the old key after confirming the new one works everywhere
fulkruma.api_keys.revoke(old_record_id)
Translate access keyId → record ID. revoke needs the record ID:
def revoke_by_access_key(fulkruma, access_key_id: str):
result = fulkruma.api_keys.list()
found = next((k for k in result["apiKeys"] if k["keyId"] == access_key_id), None)
if not found:
raise ValueError(f"No key found with keyId {access_key_id}")
fulkruma.api_keys.revoke(found["id"])
Errors
err.status |
err.code |
Cause |
|---|---|---|
400 |
VALIDATION |
Missing or oversized name, or a scope outside read / write / admin. |
403 |
NO_ACCOUNT |
The credentials resolve to no workspace. |
404 |
NOT_FOUND |
Record ID doesn't exist in this workspace (on revoke). |
409 |
ALREADY_REVOKED |
Revoking an already-revoked key. |
Next
- API → Authentication — the HMAC signing recipe.
- API → API keys — HTTP reference.
- Audit log — every key-management action shows up here.