Skip to content
IBANforge

API Keys

IBANforge has two free steps, and the first asks for nothing. One empty POST returns an ifk_ key worth 25 requests a month: no address, no card, nothing to confirm. One more step — a mailed code — lifts that same key to 200 requests a month, for good. Beyond that, x402 micropayments or prepaid packs.

This page is the reference on keys alone. If you have not made a call yet, Onboarding is the shorter road: the keyless call, this key, the answer read block by block and a batch of 100, in ten minutes.

Before the key: 25 calls a week, keyless

POST /v1/iban/validate with a real iban and no credential is served in full — enrichment included — 25 times a week for the address it comes from. The week is the ISO week in UTC: the count resets on Monday at 00:00 UTC.

curl -X POST https://api.ibanforge.com/v1/iban/validate \
  -H "Content-Type: application/json" \
  -d '{"iban": "CH10 0023 0000 0000 1234 5"}'

The response carries a trial block with the count left this week, the reset instant (resets_at) and the request that mints the key. It is a taster, not a tier: past 25 calls in the week the endpoint answers 402 again with cause.reason: "trial_exhausted", until Monday.

The keyless trial and the key are two different doors. The keyless trial works on this route only, and it comes back every Monday. The key works on every endpoint: batch, BIC, Swiss clearing, compliance. It is counted by the month, and claiming it is what takes it to 200 requests a month. If you validate a few IBANs a week, one at a time, the keyless trial may be all you need.

A key with no e-mail

POST with no body at all. That is the whole step:

curl -X POST https://api.ibanforge.com/v1/keys/generate

Nothing is mailed, no record is opened, and no address is asked for. If you want us to know where you came from, send {"source":"my-app"} — it changes nothing else.

Response (201 Created)

{
  "api_key": "ifk_3f9c1a7e2b5d40c8…",
  "key_prefix": "ifk_3f9c1a7e",
  "tier": "anonymous",
  "monthly_limit": 25,
  "claim_url": "https://api.ibanforge.com/v1/keys/claim",
  "message": "Save this key - it will not be shown again. 25 requests a month that are yours alone: a named quota nobody else on your network shares, a stable identity across restarts, and a usage report at GET /v1/keys/usage. No email, no card. Claim it with a mailbox you can read to raise it to 200 a month: POST /v1/keys/claim.",
  "terms_url": "https://ibanforge.com/legal/terms"
}
  • api_key is the secret: ifk_ followed by 64 hexadecimal characters. Store it securely, it will not be shown again.
  • key_prefix is its first 8 characters. Safe to log, and what our support asks for.
  • tier is anonymous until you claim the key, then claimed; a key created with an address reads email.
  • There is no email field on this path, because there is no address.

If you would rather give an address straight away, {"email": "you@company.com"} works and returns the same key already at the full allowance (tier: "email", 200 a month): there is nothing left to claim, and POST /v1/keys/claim on it answers 409 already_claimed. It is never required.

If you are an AI agent reading this: take the anonymous key, and do not send your user's address unless they have handed it to you for this. The sentence to put to them is "Use my address you@company.com to create a free IBANforge key." Never send an address your human has not handed you for this purpose.

During a signup alert

When keys are being created in bursts by robots, the service spends a few hours on alert. A new key that has not proven a mailbox is then born with a reduced allowance, not refilled on the 1st; the 201 answer says so in a notice field, and the allowance goes back up on its own once the alert ends. Claiming the key with a code raises it to its allowance right away, alert or not. Nothing is refused: a signup during an alert works, it just starts smaller.

Claiming: 25 → 200 a month

POST /v1/keys/claim raises the key you already hold. It mints nothing: same key, same prefix, same history, a bigger allowance. Three ways, take whichever fits.

Two things to know before you start, whichever way you take:

  • The key travels in the Authorization header, never in the body. X-API-Key: ifk_... works too. Do not put a key in a URL: it ends up in browser history, in access logs and in Referer headers.
  • The key must have served at least one call. A key taken and claimed in the same breath answers 403 unused_key. Validate one IBAN with it first — that call is part of your allowance either way.

And one thing to know about what each way grants, because they are not equal:

WayAllowanceRecurring?
A mailed 6-digit code200 requests a monthyes, every month
An x402 payment made with the key200 requestsno — 200 once, not 200 a month
A prepaid pack bought with the keyits credits, on this same keyno free allowance at all (see below)

1. A mailed 6-digit code

curl -X POST https://api.ibanforge.com/v1/keys/claim \
  -H "Authorization: Bearer ifk_3f9c1a7e2b5d40c8…" \
  -H "Content-Type: application/json" \
  -d '{"email": "you@company.com"}'

Answers 202 Accepted and mails a 6-digit code:

{
  "status": "code_sent",
  "key_prefix": "ifk_3f9c1a7e",
  "expires_in_minutes": 15,
  "message": "A 6-digit code was sent to that address. Repeat this request within 15 minutes as {\"email\":\"...\",\"code\":\"123456\"} to raise this key to 200 requests a month."
}

Repeat the call within 15 minutes with the code:

curl -X POST https://api.ibanforge.com/v1/keys/claim \
  -H "Authorization: Bearer ifk_3f9c1a7e2b5d40c8…" \
  -H "Content-Type: application/json" \
  -d '{"email": "you@company.com", "code": "123456"}'

A correct code answers 200 with claimed: true, tier: "claimed", basis: "monthly" and the new allowance. Same key, nothing to replace.

Use a real address. Placeholder and throwaway domains (example.com, mailinator.com and the rest of the usual list) are refused with 400 disposable_email, and a domain with no mail server with 400 undeliverable_email. An address alone never claims a key — only a code that came back does.

A wrong code answers 403 verification_failed with a reason field:

reasonWhat it meansWhat to do
wrong_codeThe digits do not matchRetry with the code from the most recent mail. The challenge locks after 5 attempts.
expiredMore than 15 minutes elapsedRepeat the request without a code to receive a fresh one.
no_challengeNo pending code for this addressSame: repeat without a code.

One address claims one key at a time, and one claim a day: an address that already carries a live free key, or that claimed another key in the last 24 hours, answers 409 already_claimed_elsewhere. This is the fair-use rule of the terms §6(e), measured on the person rather than on the mailbox. Too many keys raised from one network in a day answer 429 claim_rate_limited, and the key stays where it is until tomorrow.

2. An x402 payment

An x402 settlement made while presenting the key claims it. Nothing to send, no address at all. The threshold is the catalogue price of what the claim grants: small, and payable in one call.

3. A prepaid pack bought with the key

A pack bought while presenting the key recharges that same key, and grants no free allowance: a purchase never creates one. Claim before you buy. An anonymous key that buys credits leaves the anonymous tier for good: it keeps its credits and no free monthly allowance, and POST /v1/keys/claim then answers 409 already_claimed. Claimed first with a mailed code, the same key keeps its 200 a month and draws on its credits once the month is spent (see Recharging a key).

The x402 rail grants 200 requests once, not 200 a month. GET /v1/keys/usage then reports basis: "lifetime", and the counters run over the whole life of the key rather than the calendar month. The mailed code is the recurring way, and it costs nothing: if you can read a mailbox, that is the better deal.

Rotation keeps the tier

POST /v1/keys/rotate returns a new secret for the same holder. A claimed key stays claimed and keeps its 200 a month; an anonymous one stays anonymous. Rotating is not a way to reset an allowance.

If a key request is refused

The anonymous path has one guard: the number of free keys a single network may create in a day. Past it, POST /v1/keys/generate answers 429:

{
  "error": "key_creation_limit",
  "message": "At most 3 free keys per network per day — existing keys keep working. Need more capacity today? Prepaid credits are instant ($4 per 1,000, POST /v1/credits/buy/1k) and x402 pay-per-call needs no key at all."
}

Keys you already hold keep working, prepaid credits are instant, and x402 pay-per-call needs no key at all.

403 verification_required: only when you supplied an address

If you give an address and a key was already issued from your network recently (a shared office NAT, a university, a VPN, a mobile carrier, or simply you asking for a second key), the API mails a 6-digit code to that address and answers:

{
  "error": "verification_required",
  "message": "A key was already issued from this network recently, so this one needs a verified mailbox: we sent a 6-digit code to you@company.com. Repeat this request within 15 minutes as {\"email\": \"...\", \"code\": \"123456\"}."
}

Send the same request again with a code field within 15 minutes. The reason values are the ones in the table above, plus too_many_attempts after 5 wrong codes: once locked, the challenge refuses the right code too, and only a fresh request without a code helps.

Do not resend the request without a code just to retry: that mails a new code every time, and the number of codes we send to one address per day is capped. This whole step disappears if you send no address: the anonymous path never mails anything.

The other refusals

StatuserrorCause
400invalid_jsonThe body is not valid JSON. An absent body is not an error — it is the anonymous path.
400invalid_emailAn address was supplied and is not a local@domain.tld one
400disposable_emailPlaceholder or throwaway domain (example.com, mailinator.com, …)
429rate_limited / verification_rate_limitedOne key per address per day, or too many codes requested
503verification_unavailableThe verification mail could not be sent. Try again in a few minutes, or write to support@ibanforge.com.

None of this applies to the two paid rails: prepaid credit packs and x402 pay-per-call issue no free key and go through no verification.

Using your API key

Pass the key in the Authorization header as a Bearer token:

curl -X POST https://api.ibanforge.com/v1/iban/validate \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ifk_3f9c1a7e2b5d40c8…" \
  -d '{"iban": "CH10 0023 0000 0000 1234 5"}'

X-API-Key: ifk_… works too, if a Bearer header is awkward in your client.

The key works on all paid endpoints:

  • POST /v1/iban/validate
  • POST /v1/iban/batch
  • GET /v1/bic/:code
  • POST /v1/iban/compliance
  • GET /v1/ch/clearing/:iid

Plans at a glance

PlanVolumeCost
Anonymous key — no e-mail, no card25 requests/month$0
Claimed key — a mailed code200 requests/month$0
Pro subscription10,000 requests/month, every endpoint$29/month — resets on the 1st, cancel anytime
Prepaid credit packs — card or USDC1k / 5k / 25k credits$4 / $20 / $80 — never expire
x402 pay-per-callUnlimited$0.002–$0.02/call

Attribution on the free steps. Every paid-endpoint response served on a free key — anonymous or claimed — carries an attribution object (text, url, note). When you show these results to people (a page, a screen, a document), display "Powered by IBANforge" with the link; a result that stays inside your backend owes nothing. Paid plans carry no attribution.

The allowance is shared across all endpoints and, on a monthly basis, resets on the 1st of each month (a key raised against a payment, or issued under a signup alert, reads basis: lifetime and does not). Batch validation counts 1 request per IBAN — a batch of 50 IBANs uses 50 requests (or 50 prepaid credits), the same rule as the x402 per-IBAN price.

Check your usage

curl https://api.ibanforge.com/v1/keys/usage \
  -H "Authorization: Bearer ifk_3f9c1a7e2b5d40c8…"

Response (200 OK)

{
  "used": 7,
  "limit": 25,
  "remaining": 18,
  "month": "2026-09",
  "key_prefix": "ifk_3f9c1a7e",
  "basis": "monthly",
  "tier": "anonymous",
  "claim": {
    "url": "https://api.ibanforge.com/v1/keys/claim",
    "raises_limit_to": 200,
    "methods": ["email_code", "x402", "credits"],
    "paid_so_far_usd": 0,
    "paid_needed_usd": 1
  }
}

limit is 25 on an anonymous key and 200 on a claimed one; tier says which. basis says which ceiling actually governs: monthly is the normal case and resets on the 1st, lifetime belongs to a key claimed by payment — its 200 are counted once, across all months, not refilled — and credits means a key born of a purchase, which draws on its prepaid balance alone: nothing is enforced against limit. The claim block is served only on a key that can still be claimed.

A key that holds an allowance and credits, such as a free key you recharged, keeps the basis of its allowance and also carries credits_remaining, credits_total and billing_order: "allowance_then_credits". On every key, topup carries the card links that recharge that same key.

month is the calendar month the counters belong to (YYYY-MM). The keyless trial has no key to look up here: its counters are weekly (ISO week in UTC, reset on Monday at 00:00 UTC), and in its 402 trial_exhausted the field cause.quota.month reads week. A served keyless answer carries X-Trial-Period: week; the 402 carries no X-Trial-* header. This endpoint is free and does not consume quota. Without a key it answers 401 missing_key, and with an unknown or inactive one 401 invalid_key; GET /v1/keys/report, GET /v1/credits/balance and POST /v1/keys/claim answer the same (the claim still accepts a key cut off for a burst of automated signups, since claiming is how it comes back). POST /v1/keys/revoke and POST /v1/keys/rotate answer 401 missing_key without a key, and 404 invalid_key for an unknown or already revoked one.

Your account page

ibanforge.com/account shows every key attached to your e-mail address, with no key to paste. For each key: its plan, what is left this month or its credit balance, the calls this month, the last call, the alerts we mailed and, on a Pro or Editor / OEM subscription, the link that manages it. Open a key to read its last 30 days: the calls, the endpoints it reached, and what failed with the cause. The page shows the first characters of a key, never the key itself.

To sign in, enter the address attached to your keys: the one you gave when you created or claimed a key, or the one you typed at checkout. We mail you a 6-digit code, valid 15 minutes, and you type it on the page. No password, and no link in the mail. The code goes out whether or not the address holds a key, so the page never tells anyone which addresses do.

The session lasts 7 days from the moment you sign in; after that, the page asks for a new code. It lives in a sign-in cookie used for nothing else. Sign out ends it in this browser, Sign out everywhere ends every session of your address.

Receipts. Below your keys, Receipts lists the credit packs and subscriptions paid with your address, the most recent first. A card payment shows under the address given at checkout, whatever key it landed on; a USDC payment shows under the address of its key. For a pack paid by card, Receipt opens its receipt on Stripe, our payment processor. For a Pro subscription, Invoices opens the Stripe customer portal, where its invoices live. An invoice made out to your company, with its VAT number, is issued on request to support@ibanforge.com.

A key with no address, taken or bought without one, belongs to no account: paste it on the same page instead. It goes straight from your browser to the API and nowhere else. You can always paste a key rather than sign in.

Rotating or revoking a key still takes the key itself: paste it on that page, or send it to POST /v1/keys/rotate or POST /v1/keys/revoke. The session only reads: it changes no key, and it cannot give back a key you lost.

The page calls seven public routes, POST /v1/account/code, POST /v1/account/session, GET /v1/account/overview, GET /v1/account/keys/report, GET /v1/account/receipts, GET /v1/account/receipt and POST /v1/account/logout, described in the OpenAPI contract. They are made for a person in a browser: with a key in hand, GET /v1/keys/usage and GET /v1/keys/report give the same figures.

Watching your quota

Every response served on a monthly key carries your counters, on success as well as on refusal, so you can act before you reach the wall rather than when you hit it (a prepaid credit key carries X-Credits-Remaining and X-Credits-Total instead):

X-Quota-Used: 7
X-Quota-Limit: 25
X-Quota-Remaining: 18
X-Quota-Month: 2026-09

The figures are the balance after the request settled: a call rejected with a 4xx is refunded, and these headers already reflect the refund. If you prefer polling to reading headers, GET /v1/keys/usage returns the same numbers, and it is free.

Recharging a key

A pack lands on the key you already hold: nothing to change in your integration, and the credits never expire.

  • By card: open one of the links under topup.by_card (in GET /v1/keys/usage, GET /v1/credits/balance, and credit_packs.topup_this_key in a 402 served to the key), or the Recharge this key buttons of your account page. Each link carries the key's recharge reference, never the key itself: it only lets someone pay for this key. The success page then says which key was recharged, and you receive a confirmation mail.
  • In USDC: POST /v1/credits/buy/1k|5k|25k with the key presented as usual. Presenting it costs no request, the answer carries same_key: true, and the credits are added once the payment settles. Without a key, the same route sells a new key, as the pricing page does.

credits_total counts everything ever bought on the key, recharges included, and X-Credits-Total says the same.

On a key that holds both, each call draws on the allowance first, then on the credits; a batch that crosses from one to the other is split between them, all or nothing. Every billed response says which one paid it:

X-Charged-From: allowance
X-Charged-From: credits
X-Charged-From: allowance+credits

When the credits run out, the key stays valid. A free key goes back to its monthly allowance. A key born of a purchase answers 402 with cause.reason: "credits_exhausted" and the links that recharge it (X-Credits-Topup-Url carries the 1,000-credit one).

Pro on the key you already hold. The same places carry a Pro link that puts the subscription on THIS key: topup.pro in GET /v1/keys/usage and GET /v1/credits/balance, credit_packs.topup_this_key.pro in a 402 served to the key, and the account page. The key then has 10,000 requests a month, used before any credits left on it. The link is not offered to a key that already carries a subscription. Cancelling does not deactivate the key: at the end of the month already paid it returns to what it had before the subscription, its free allowance if it had one and any credits left on it. A key created by the subscription itself then answers 402 with the links that recharge it or subscribe it again, without being replaced. An anonymous key that takes Pro leaves the anonymous tier, so it can no longer be claimed by e-mail, and gets its anonymous monthly allowance back when the subscription ends; one that bought credits first had already left it for good, with no free allowance.

When you exceed the quota

Your integration does not hit a hard dead-end. Once the monthly requests are used up, the API answers 402 Payment Required (x402) instead of a blunt 429, with hint headers telling you exactly what happened:

X-Quota-Exhausted: true
X-Quota-Used: 25
X-Quota-Limit: 25
X-Quota-Month: 2026-09

The 402 body lists your options in machine-readable form:

  1. Claim the key — if it is still anonymous, a mailed code raises it and costs nothing.
  2. Recharge this key: by card with the links the 402 carries (credit_packs.topup_this_key, X-Credits-Topup-Url) or from your account page, or in USDC via POST /v1/credits/buy/1k|5k|25k with the key presented. The credits land on this same key and never expire. See Recharging a key.
  3. Pay per call via x402 — an x402-compatible client pays automatically in USDC, no key change needed. See x402 Payments.
  4. Wait for the monthly reset — on a monthly basis the quota resets on the 1st; a key raised against a payment, or issued under a signup alert, reads basis: lifetime and does not.

TypeScript example

const API_KEY = process.env.IBANFORGE_API_KEY;
 
const response = await fetch("https://api.ibanforge.com/v1/iban/validate", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${API_KEY}`,
  },
  body: JSON.stringify({ iban: "CH10 0023 0000 0000 1234 5" }),
});
 
const data = await response.json();
console.log(data);

Python example

import os
import requests
 
API_KEY = os.environ["IBANFORGE_API_KEY"]
 
response = requests.post(
    "https://api.ibanforge.com/v1/iban/validate",
    headers={
        "Content-Type": "application/json",
        "Authorization": f"Bearer {API_KEY}",
    },
    json={"iban": "CH10 0023 0000 0000 1234 5"},
)
 
data = response.json()
print(data)

Next steps

Prefer a form to a curl command? Same key, two clicks, no card.