Currency

API keys

The API keys resource lets you mint, list, and revoke HMAC credentials programmatically — the same keys you'd otherwise create from Dashboard → Settings → API keys.

This page is about managing keys. For the signing recipe, see Authentication.

The secret is shown exactly once, on creation. Ripllo stores only a one-way hash. If you lose the secret, the only recovery is to revoke the key and mint a new one — there is no fetch-secret endpoint and there never will be.

Endpoints

Method Path Operation
POST /api/v1/api-keys Create a key
GET /api/v1/api-keys List keys
POST /api/v1/api-keys/:id/revoke Revoke a key

These endpoints are not gated on the admin scope. Any authenticated principal in the workspace can reach them: a portal session, the partner proxy, or an HMAC key with read (to list) or write (to mint and revoke). A write key can therefore mint another key — including one carrying admin. Treat every non-read-only key as able to reproduce itself, and revoke rather than downgrade.

Create a key

POST /api/v1/api-keys

Mints a new key under the caller's workspace and returns the plaintext secret. The secret is not retrievable later.

Request body

Field Type Required Notes
name string (1–120) yes Human label. Use something specific (Production server, CI — GitHub Actions).
scopes enum[] no Subset of ["read", "write", "admin"]. Defaults to ["read", "write"]. See Scopes.

Response — 201 Created

{
  "data": {
    "apiKey": {
      "id": "cl9x2k7t40000qz8f...",
      "name": "Production server",
      "keyId": "AKIAFULK0123456789ABCDEF",
      "scopes": ["read", "write"],
      "createdAt": "2026-05-13T10:42:00.123Z"
    },
    "secret": "fulksk_AbCdEf12...verylongbase64url..."
  },
  "error": null,
  "meta": { "requestId": "...", "timestamp": "..." }
}

secret is the HMAC secret. It is present only on this 201 response; subsequent GET calls never include it. Capture it synchronously — pipe to your secrets manager, don't log it, don't keep it in shell history.

The minted key's creation is audited under the operating user.

Errors

Status error.code When
400 VALIDATION Shape wrong, name too long, scope not in the allowed set.
403 NO_ACCOUNT Caller's token has no accountId.

Examples

const created = await ripllo.apiKeys.create({
  name: 'Production server',
  scopes: ['read', 'write'],
});
console.log(created.apiKey.keyId);    // AKIAFULK…
console.log(created.secret);          // fulksk_…  (only here, only now)
created = ripllo.api_keys.create(name='Production server', scopes=['read', 'write'])
print(created['apiKey']['keyId'], created['secret'])
created, err := client.ApiKeys.Create(ctx, &ripllo.ApiKeyCreateParams{
    Name:   "Production server",
    Scopes: []string{"read", "write"},
})
ripllo_curl POST '/api/v1/api-keys' \
  '{"name":"Production server","scopes":["read","write"]}'

List keys

GET /api/v1/api-keys

Returns every key in the workspace, newest first. The secret is never included; instead a secretPreview shows the first 8 and last 4 chars for visual confirmation.

Response

{
  "data": {
    "apiKeys": [
      {
        "id": "cl9x2k7t40000qz8f...",
        "name": "Production server",
        "keyId": "AKIAFULK0123456789ABCDEF",
        "secretPreview": "fulksk_A…ef12",
        "scopes": ["read", "write"],
        "createdAt": "2026-05-13T10:42:00.000Z",
        "lastUsedAt": "2026-05-13T11:03:14.000Z",
        "revokedAt": null,
        "createdBy": "usr_huudis_01HX..."
      }
    ]
  },
  "error": null,
  "meta": { "requestId": "...", "timestamp": "..." }
}

Revoked keys are still returned (with revokedAt populated) so you can audit history. They no longer authenticate.

Revoke a key

POST /api/v1/api-keys/:id/revoke

Revokes a key immediately. Any request signed with it starts returning 401 REVOKED_KEY within seconds — no grace period.

Errors

Status error.code When
404 NOT_FOUND No such key in this workspace.
409 ALREADY_REVOKED The key was already revoked.

Revocation is irreversible and propagates within seconds. Always rotate before revoking — never the other way round.

await ripllo.apiKeys.revoke('cl9x2k7t40000qz8f...');

The API key object

Field Type Notes
id string Internal ID — a bare cuid, no type prefix. Use this in URLs.
name string What you passed on create.
keyId string Public access key ID: the literal prefix AKIAFULK (inherited from Fulkruma, shared across the family) followed by 16 uppercase hex characters. Goes in the Authorization header. Safe to log. Don't validate against AKIARPLO — no key is ever minted with that prefix.
secretPreview string First-8/last-4 of the secret. Visual confirmation only; cannot be used to authenticate.
scopes enum[] Subset of ["read", "write", "admin"].
createdAt ISO 8601
lastUsedAt ISO 8601 | null Most recent signed request. Updates within seconds of first use.
revokedAt ISO 8601 | null Revocation time, or null for live keys.
createdBy string | null Huudis user ID of the operator.

Scopes

Pick the narrowest scope set that works. Scopes can't be widened — mint a new key.

Scopes are enforced by HTTP method, on HMAC-signed requests only. Portal sessions are not scope-limited — scopes are an API-key concept.

Scope What it allows
read GET and HEAD. A request with any other method gets 403 INSUFFICIENT_SCOPE.
write Every other method — POST / PATCH / PUT / DELETE — across all merchant resources.
admin Nothing extra. It is accepted at mint time and stored on the row, but no endpoint checks for it: API-key and webhook-endpoint management need only write.

The split is method-based, not resource-based, so write is a single blast radius: a key that can update one merchant resource can update all of them, mint further keys, and register webhook endpoints.

ripllo:platform:admin — the capability that gates X-Ripllo-On-Behalf-Of and acts as a superset of read+write — is a distinct literal scope string, not something admin implies. It also can't be issued through this API: POST /api/v1/api-keys only accepts read, write and admin, so partner-tier keys are provisioned out-of-band by writing the value directly onto the key row.

Programmatic rotation

Always: mint → verify → cut over → revoke. Never the reverse.

// 1. Mint a replacement.
const next = await ripllo.apiKeys.create({
  name: `Production server (rotated ${new Date().toISOString().slice(0, 10)})`,
  scopes: ['read', 'write'],
});

// 2. Push next.secret into your secrets manager. Wait for consumers
//    to reload and confirm at least one request signs successfully.
await secrets.set('RIPLLO_KEY_ID', next.apiKey.keyId);
await secrets.set('RIPLLO_KEY_SECRET', next.secret);
await waitForConsumersToReload();

// 3. Once lastUsedAt advances on the new key, revoke the old one.
await ripllo.apiKeys.revoke(process.env.OLD_RIPLLO_AKEY_ID);

lastUsedAt is the simplest verification signal — if it advances on the new key within 60 seconds of cutover, you're safe to revoke. Schedule this as a quarterly cron.

Events

The API keys resource is intentionally not broadcast on the event stream — we don't want webhook subscribers enumerating or correlating credential lifecycle. Key creation and revocation do show up in the audit log. Capture a signal in your own systems at the point you call apiKeys.create / apiKeys.revoke.

Next

CurrencyRupiah is paid by transfer or QRIS; US dollars settle through PayPal.