API keys

An API key is the credential pair (keyId + secret) you sign Plugipay requests with. Each workspace can have multiple keys with different scopes — one for your production server, one for staging, one for a one-off back-office script. This page covers the plugipay.apiKeys namespace; for the underlying HTTP surface see API: API keys, and for how keys are used in the SDK constructor see Authentication.

Namespace

plugipay.apiKeys — every method on this namespace:

plugipay.apiKeys.list()
plugipay.apiKeys.create(input)
plugipay.apiKeys.revoke(id)

There's no update — key metadata is immutable. To "rename" a key, revoke and re-create. Note that using this namespace requires an admin-level key (plugipay:apikey:write scope) — you can't mint keys with a key that doesn't have key-management permissions.

Methods

apiKeys.list

Signature. plugipay.apiKeys.list(): Promise<ApiKey[]>

Returns every key in the workspace, including revoked ones (filter on revokedAt to find active). The secret field is never returned on list — it's only available on create.

import { PlugipayClient } from '@forjio/plugipay-node';

const plugipay = new PlugipayClient({
  keyId: process.env.PLUGIPAY_ADMIN_KEY_ID!,
  secret: process.env.PLUGIPAY_ADMIN_SECRET!,
});

const keys = await plugipay.apiKeys.list();
for (const k of keys) {
  const state = k.revokedAt ? `revoked ${k.revokedAt}` : 'active';
  console.log(`${k.keyId} — ${k.description ?? '(no description)'} — ${state}`);
}

apiKeys.create

Signature. plugipay.apiKeys.create(input): Promise<ApiKey>

Mints a new key. Pass a human-readable description (so you can identify it later in the dashboard) and optionally a scope to narrow what it can do. The returned ApiKey includes the secret — this is the only call that ever returns it.

const newKey = await plugipay.apiKeys.create({
  description: 'Production server (us-east-1)',
  scope: 'plugipay:checkout:* plugipay:webhook:read',
});

console.log(newKey.keyId);    // → 'pk_live_...'
console.log(newKey.secret);   // → 'sk_live_...' — STORE NOW

The secret appears once. Plugipay shows the secret in the response to create and never again. Stash it in your secret manager before the function returns. If you lose it, revoke the key and mint a new one.

apiKeys.revoke

Signature. plugipay.apiKeys.revoke(id): Promise<void>

Revokes a key. Subsequent requests signed with that key fail immediately with invalid_signature / key_revoked. There's no un-revoke; mint a fresh key if you need to restore access.

await plugipay.apiKeys.revoke('apk_01HX...');

Note the argument is the ApiKey.id (the apk_* record identifier), not the keyId (the pk_live_* access key). They're different — the record ID is what the management API uses; the key ID is what you sign requests with.

Types

interface ApiKey {
  id: string;                  // 'apk_...' — the record ID, used by .revoke()
  accountId: string;
  keyId: string;               // 'pk_live_...' / 'pk_test_...' — the public access key
  description: string | null;
  scope: string;               // space-separated permission list
  /** Only returned on create. */
  secret?: string;             // 'sk_live_...' / 'sk_test_...'
  createdAt: string;
  revokedAt: string | null;
}

For the full scope vocabulary (what permissions exist, which scopes group together), see API: API keys and API: Authentication.

Common patterns

Mint a per-service key and stash it

Per-service keys are easier to revoke individually than one shared key:

async function mintServerKey(serviceName: string) {
  const key = await plugipay.apiKeys.create({
    description: `Auto-provisioned: ${serviceName} (${new Date().toISOString().slice(0, 10)})`,
    scope: 'plugipay:checkout:* plugipay:customer:* plugipay:webhook:read',
  });

  await secretManager.put(`${serviceName}/PLUGIPAY_KEY_ID`, key.keyId);
  await secretManager.put(`${serviceName}/PLUGIPAY_SECRET`, key.secret!);

  console.log(`Provisioned ${key.keyId} for ${serviceName}`);
  return key.keyId;
}

Audit active keys

For periodic security reviews:

async function auditActiveKeys() {
  const keys = await plugipay.apiKeys.list();
  const active = keys.filter((k) => !k.revokedAt);

  console.log(`${active.length} active keys:`);
  for (const k of active) {
    const ageDays = Math.floor((Date.now() - Date.parse(k.createdAt)) / 86_400_000);
    console.log(`  ${k.keyId} — ${k.description ?? '(none)'} — ${ageDays}d old`);
  }
}

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:

async function rotateKey(oldRecordId: string, description: string) {
  // 1. Mint the new one.
  const next = await plugipay.apiKeys.create({
    description: `${description} (rotation ${new Date().toISOString().slice(0, 10)})`,
    scope: '*',
  });

  // 2. Deploy new credentials to your services. (This step is your CI's job.)
  await deployNewKey(next.keyId, next.secret!);

  // 3. After confirming all services use the new key, revoke the old.
  await plugipay.apiKeys.revoke(oldRecordId);
}

Look up the record ID for a key you have the keyId for

The revoke method needs the record ID (apk_...), but you usually have the public keyId (pk_live_...) handy. Translate via list:

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

Test-mode vs live-mode keys

Plugipay namespaces keys into test (pk_test_* / sk_test_*) and live (pk_live_* / sk_live_*) modes. Test keys hit a sandbox; live keys hit production. The mode is determined at mint time and reflected in the keyId prefix. Always assert the prefix in your config loader:

function loadPlugipayConfig() {
  const keyId = process.env.PLUGIPAY_KEY_ID!;
  const isProd = process.env.NODE_ENV === 'production';
  const expectedPrefix = isProd ? 'pk_live_' : 'pk_test_';
  if (!keyId.startsWith(expectedPrefix)) {
    throw new Error(`Expected ${expectedPrefix} key, got ${keyId.slice(0, 12)}`);
  }
  return { keyId, secret: process.env.PLUGIPAY_SECRET! };
}

Errors

Code Status Cause
validation_error 400 Bad scope value, description too long.
forbidden 403 Calling key lacks plugipay:apikey:write scope (can't manage other keys).
not_found 404 Record ID doesn't exist (on revoke).
conflict 409 Revoking an already-revoked key.

Next

Plugipay — Payments that don't tax your success