Skip to content
MOONIPdocs
IntegrationManual
PTEN
Go to dashboard

API Keys

API keys are how every server-to-server Moonip request authenticates. This page is for dashboard/admin users managing keys for their account — if you just need to use a key, see Getting Started.

#Key format

text
ak_live_<24-byte-base64url>      ← production
ak_test_<24-byte-base64url>      ← sandbox / test
  • The cleartext is shown exactly once, at issuance time.
  • Server-side we keep only the SHA-256 fingerprint and a 12-character prefix (e.g. ak_live_xyz) for display in the dashboard.
  • Lost a key? You can't recover it — rotate to issue a new one.

The exact prefix is brand-specific (here ak_live_ / ak_test_); what is universal is the shape <prefix>_live_… / <prefix>_test_…. Never hardcode a prefix in code that classifies a key — the official SDKs accept any brand's prefix and expose isTestKey helpers that read the _test_ segment instead.

#Using a key on the API

Server-to-server calls to /v1/* carry the key in the canonical Authorization: Bearer header:

text
Authorization: Bearer ak_live_...

The legacy apikey: ak_live_... header is still accepted for compatibility (the gateway rewrites Bearer to the internal apikey form). New integrations should send Bearer and nothing else. Missing or invalid keys return 401.

Never log a key. Not in app logs, not in CI output, not in an error report — the cleartext is the credential, and fingerprints exist so you can talk about a key without quoting it.

#IP allowlist

An account can restrict API-key traffic to a list of CIDR ranges. On the routes the guard covers, a request from an address outside the list is refused with 403 before the route runs — same permission_error envelope as Errors, and the key itself is not at fault.

Scope: the allowlist is enforced on the core account routes — wallets, withdrawals, transactions, MED cases, api-keys and neighbours. The money-movement routes /v1/charges, /v1/pix/charges, /v1/payments and /v1/webhooks are not covered by the per-account allowlist (the gateway's ip-restriction protects only /v1/admin).

Include every egress address your integrations use: production servers, CI runners, failover hosts, and the NAT addresses of your cloud regions. A wrong entry here fails closed — the request never reaches the key check, so a "valid key, 403 anyway" report is usually the allowlist.

Ask support (suporte@moonip.io) to add, change or remove a range; the list is maintained per account.

#Auth — these endpoints use JWT, not an API key

All /v1/api-keys/* endpoints are JWT-authenticated (the same dashboard login token returned by POST /v1/auth/login). They are not gated by Kong's apikey plugin — that would be a chicken-and-egg.

text
Authorization: Bearer <dashboard JWT>

The JWT's account claim scopes every operation — you can only manage your own account's keys.

Who can: any active member of the account can list keys. Issuing, rotating and revoking require the account OWNER or ADMIN role; any other role gets 403 with code API_KEY_ROLE_REQUIRED.

#Endpoints

#Issue a key

text
POST /v1/api-keys
bash
curl -X POST https://api.moonip.io/v1/api-keys \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{ "label": "production-backend", "test": false }'
FieldTypeRequiredDescription
labelstringnoFree-form, ≤ 64 chars. Shown in dashboard for humans.
testbooleannotrue issues a ak_test_… key. Default false.

Response 201 Created

json
{
  "id": "5b3a9c1d-...",
  "apiKey": "ak_live_F8A2K7M3N9PQRSTUVWXYZAB",
  "fingerprint": "9a3f7e1c5d8b2a40",
  "prefix": "ak_live_F8A",
  "label": "production-backend",
  "createdAt": "2026-04-25T18:32:11.000Z",
  "mode": "live"
}

mode is the key's environment: test (sandbox, the charge never reaches a real provider) or live. It is the same rule the gateway uses to route the charge; read this field instead of comparing the prefix.

apiKey is shown only here. Store it in a secret manager immediately. The dashboard does not display it on subsequent loads.

#List your keys

text
GET /v1/api-keys

Returns metadata only — no cleartext.

json
{
  "data": [
    {
      "id": "5b3a9c1d-...",
      "prefix": "ak_live_F8A",
      "label": "production-backend",
      "createdAt": "2026-04-25T18:32:11.000Z",
      "mode": "live"
    },
    {
      "id": "7f1c4e9a-...",
      "prefix": "ak_test_QRX",
      "label": "ci-tests",
      "createdAt": "2026-04-12T09:14:08.000Z",
      "mode": "test"
    }
  ],
  "access": { "canManage": true }
}

access.canManage tells whether the caller can issue, rotate and revoke (account owner or admin).

#Rotate a key

text
POST /v1/api-keys/{id}/rotate

Atomically revokes the named key and issues a fresh one. Use this on a regular cadence (every 90 days is reasonable) or whenever you suspect compromise.

bash
curl -X POST https://api.moonip.io/v1/api-keys/5b3a9c1d-.../rotate \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{ "label": "production-backend (rotated 2026-04-25)" }'

Response shape is identical to Issue a key — including the new cleartext.

The new key keeps the old key's environment: rotating a test key issues another test key, and rotating a live key issues another live key. The label is kept too, unless you send a new label. An id that is not in your account returns 404, and nothing is revoked.

Operational note: the old key stops working the moment this call completes. Deploy the new key to your servers BEFORE calling rotate, or schedule a brief overlap by issuing a parallel key first and revoking the old one once the new one is in place.

#Revoke a key

text
DELETE /v1/api-keys/{id}

Permanent — there is no undo.

bash
curl -X DELETE https://api.moonip.io/v1/api-keys/5b3a9c1d-... \
  -H "Authorization: Bearer $JWT"

Response 200 OK

json
{ "revoked": true }

#Security checklist

  • Treat ak_live_… like a password — never commit, never log, never paste into Slack.
  • Store in a secret manager (1Password, Vault, AWS Secrets Manager, GitHub Actions secrets, etc.).
  • Different keys per environment (ak_live_… for prod, ak_test_… for staging/CI).
  • One key per service / one key per developer machine — easier to revoke a single key when something leaks.
  • Rotate on a schedule. Quarterly minimum; monthly for high-sensitivity workloads.
  • Revoke immediately when an employee leaves.
  • Set up alerting on the dashboard's "key issued" / "key rotated" audit events.

#What's stored where

StorageWhat's there
Kong (key-auth plugin)The cleartext key — used to authenticate inbound requests.
Moonip database (merchant record)kongConsumerUsername — the merchant slug, for reverse lookup. Never the cleartext.
Audit logIssue / rotate / revoke events, with the issuing user's id and the key's fingerprint.
DashboardPrefix + label only.

#CLI alternative

For ops/onboarding from a workstation, the same operations are available through an internal provisioning CLI (ops-only tooling; not part of the public API or SDK). The CLI talks to the Kong Admin API directly, and keys it issues are interchangeable with HTTP-issued keys.

#FAQ

Q: Can I have multiple live keys at once? A: Yes — useful for zero-downtime rotation and per-service isolation. No hard limit.

Q: I lost the cleartext immediately after creation. A: It's gone. Rotate (or revoke + re-issue) — there is no recovery path by design.

Q: How do I tell which key is making which request? A: The dashboard's request logs show the key fingerprint on every call. Filter by apiKeyFingerprint to attribute traffic.

Q: Can I restrict a key to certain endpoints / methods? A: Not yet. Per-key scopes are on the Phase 2 roadmap. Today, every key has full account access.