Currency

API keys

An API key is the credential pair (keyId + secret) you sign Ripllo requests with. Each workspace can have multiple keys with different names and scopes — one for production, one for staging, one for a one-off back-office script. This page covers ripllo.apiKeys on the Node SDK. For the HTTP surface, see API → API keys; for how a key is plumbed into the SDK constructor, see Authentication.

The Node SDK's types for this namespace are stale. apiKeys.create is typed { description?, scope? } and apiKeys.revoke is typed to resolve { revoked: boolean }; the API actually takes { name, scopes } and returns { apiKey }. The bodies are passed through verbatim, so the calls below work at runtime — in TypeScript you may need a cast until the types are regenerated. Sending the shape the types suggest gets you a 400 VALIDATION.

Namespace

ripllo.apiKeys.list()
ripllo.apiKeys.create(input?)
ripllo.apiKeys.revoke(id)

No update — key metadata is immutable. To "rename" a key, revoke and re-create. Any principal that can write to the workspace can mint keys: a dashboard session, or an API key holding the write scope. There is no separate key-management permission.

Methods

apiKeys.list

Signature. ripllo.apiKeys.list(): Promise<{ apiKeys: ApiKey[] }>

Returns every key in the workspace newest-first, including revoked ones (filter on revokedAt). The secret is never returned on list — you get secretPreview (fulksk_…ab12) instead.

const { apiKeys } = await ripllo.apiKeys.list();
for (const k of apiKeys) {
  const state = k.revokedAt ? `revoked ${k.revokedAt}` : 'active';
  console.log(`${k.keyId} — ${k.name} — [${k.scopes.join(',')}] — ${state}`);
}

apiKeys.create

Signature. ripllo.apiKeys.create(input): Promise<{ apiKey: ApiKey; secret: string }>

Mints a new key. name is required (1–120 chars); scopes is an array defaulting to ['read', 'write']. The SDK auto-generates an Idempotency-Key so retrying a transient failure won't mint duplicates. The response is the only time the secret is returned — and it comes back as a sibling of apiKey, never as a field on it.

const { apiKey, secret } = await ripllo.apiKeys.create({
  name: 'Production server (jakarta)',
  scopes: ['read', 'write'],
});

console.log(apiKey.keyId);  // → 'AKIAFULK…' — the public access key
console.log(secret);        // → 'fulksk_…' — STORE NOW

The secret appears once. Stash it in your secret manager before the call returns. If you lose it, revoke and mint a new one.

Scope values are read, write and admin, and they are enforced on every signed request: read covers GET/HEAD, write covers every mutation. Mint at least the one you need — a key created with ['admin'] alone satisfies neither gate and gets 403 INSUFFICIENT_SCOPE on everything. (The partner superset ripllo:platform:admin, which unlocks X-Ripllo-On-Behalf-Of, is issued by Ripllo out-of-band and cannot be minted here.)

Key ids and secrets currently carry AKIAFULK / fulksk_ prefixes — inherited from the Fulkruma implementation this was copied from. They are valid Ripllo credentials; don't pattern-match on a RIPLLO prefix.

apiKeys.revoke

Signature. ripllo.apiKeys.revoke(id): Promise<{ apiKey: { id: string; revokedAt: string } }>

Revokes a key. Subsequent requests signed with that key fail with REVOKED_KEY. There's no un-revoke.

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

The argument is the record ID (a cuid), not the keyId (AKIAFULK…). Two different identifiers — the record ID is the management primary key; the key ID is the public access key you sign with.

Types

interface ApiKey {
  id: string;                  // cuid — record id, used by revoke()
  name: string;
  keyId: string;               // 'AKIAFULK…' — the public access key
  secretPreview: string;       // 'fulksk_…ab12' — on list
  scopes: string[];            // ['read', 'write'] by default
  createdAt: string;
  lastUsedAt: string | null;
  revokedAt: string | null;
  createdBy: string | null;
}
// create resolves to { apiKey: ApiKey; secret: string } — the plaintext
// secret is NOT a field on apiKey. accountId is implicit (your workspace)
// and is not projected.

For how scopes are checked on the wire, see API → API keys and API → Authentication.

Common patterns

Mint a per-service key and stash it

async function mintServerKey(serviceName: string) {
  const { apiKey, secret } = await ripllo.apiKeys.create({
    name: `Auto-provisioned: ${serviceName} (${new Date().toISOString().slice(0, 10)})`,
    scopes: ['read', 'write'],
  });
  await secretManager.put(`${serviceName}/RIPLLO_KEY_ID`, apiKey.keyId);
  await secretManager.put(`${serviceName}/RIPLLO_KEY_SECRET`, secret);
  return apiKey.keyId;
}

Audit active keys

async function auditActiveKeys() {
  const { apiKeys } = await ripllo.apiKeys.list();
  const active = apiKeys.filter((k) => !k.revokedAt);
  for (const k of active) {
    const ageDays = Math.floor((Date.now() - Date.parse(k.createdAt)) / 86_400_000);
    const lastUsed = k.lastUsedAt ?? 'never used';
    console.log(`  ${k.keyId} — ${k.name} — ${ageDays}d old — ${lastUsed}`);
  }
}

Mint-then-revoke rotation

Mint first, deploy the new credentials, then revoke the old — this gives you an overlap window where both keys work, so a botched deploy doesn't lock you out:

async function rotateKey(oldRecordId: string, name: string) {
  const { apiKey: next, secret } = await ripllo.apiKeys.create({
    name: `${name} (rotation ${new Date().toISOString().slice(0, 10)})`,
    scopes: ['read', 'write'],
  });
  await deployNewKey(next.keyId, secret);
  // After confirming services use the new key:
  await ripllo.apiKeys.revoke(oldRecordId);
}

Translate a keyId to a record ID

revoke needs the record ID (a cuid); you usually have the public AKIAFULK… key ID. Translate via list:

async function revokeByKeyId(publicKeyId: string) {
  const { apiKeys } = await ripllo.apiKeys.list();
  const found = apiKeys.find((k) => k.keyId === publicKeyId);
  if (!found) throw new Error(`No key with keyId ${publicKeyId}`);
  await ripllo.apiKeys.revoke(found.id);
}

Auth header recap

The SDK signs every request for you, but if you're debugging on the wire:

Authorization: Ripllo-HMAC-SHA256 keyId=<keyId>, scope=*, signature=<hex>
X-Ripllo-Timestamp: <unix_seconds>

The string-to-sign is <METHOD>\n<PATH-WITHOUT-QUERY>\n<TIMESTAMP>\n<BODY_SHA256>, optionally with \n<IdempotencyKey> appended. See API → Authentication for the full recipe.

Errors

Code Status Cause
VALIDATION 400 name missing or over 120 chars, or a scopes entry outside read/write/admin.
NO_ACCOUNT 403 The principal carries no accountId.
INSUFFICIENT_SCOPE 403 Calling key lacks read (list) or write (create/revoke).
NOT_FOUND 404 Record ID doesn't exist in this workspace (on revoke).
ALREADY_REVOKED 409 Revoking an already-revoked key.

Next

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