Idempotency key format check
The idempotency key format check takes the string you plan to send as an Idempotency-Key header and tells you whether it follows common format guidance before it ever reaches your API.
Run — free
It verifies that the key is not empty, that it is long enough to be unique in practice, that it stays within a sane maximum length, and that every character is URL-safe so the key survives headers, logs and query strings without encoding surprises. You send one string, you get back a clear valid flag, the measured length, and a list of concrete issues when something is wrong.
Why idempotency keys need a format check
Idempotency keys exist so that a retried request — a payment submitted twice, an order created after a timeout — is processed once and only once. But the safety they promise depends on the key itself being well formed. A key that is too short collides with another client's key and silently deduplicates two different operations into one. A key that contains characters outside the URL-safe alphabet gets mangled somewhere between your client, a proxy, a log pipeline and the server, so the retry arrives with a different string than the original and is charged twice. A key that is empty is rejected outright by most APIs, often with a generic error that takes an afternoon to trace. Running the key through this idempotency key format check at the edge of your system catches all three failure modes at development time, in a test suite, or in pre-flight validation inside your own service, instead of in a reconciliation report weeks later.
What exactly is validated
The check applies the format guidance that payment processors and deduplication middleware document most often. First, the key must be a non-empty string; an empty key is an error, not a warning, because no server will accept it. Second, length: by default the key should be at least 16 characters, which is the floor below which uniqueness stops being plausible, and at most 255 characters, the ceiling most stores accept — both bounds are configurable per call. Third, the alphabet: every character must come from the RFC 3986 unreserved set — letters, digits, hyphen, dot, underscore and tilde. Those characters pass through HTTP headers, URL segments and log shippers without encoding, which is exactly where idempotency keys travel. When a character fails, the response lists each distinct offending character so you can see whether someone embedded a space, a slash or an emoji, and the issues array names the problem in machine-readable form: too_short, too_long or unsafe_characters.
Where the check fits in your stack
Most teams wire this into two places. The first is the client that generates keys: right after you build a key from a UUID, a timestamp and a user id, validate it once and log a warning if it fails, so a generator bug surfaces in staging instead of production. The second is contract testing: run a batch of keys from every integration you own through the endpoint in CI, so a library upgrade that changes encoding behaviour fails the build. The endpoint is deterministic and stateless — nothing is stored, no list of seen keys is consulted, and the same input always produces the same output — which means it is safe to call on real keys and cheap enough, at $0.002 per request, to run on every deploy. The same validation also runs free in your browser on this page, so a developer can paste a suspicious key during an incident and get the same answer the API would give.
What you can do with it
Validate keys in a payment retry client
Check the generated key before attaching the Idempotency-Key header, so a malformed generator fails fast instead of double-charging a customer.
Contract-test integrations in CI
Send the keys each of your services generates through the check on every build and fail the pipeline when a library change breaks the format.
Debug a deduplication incident
Paste a key from the logs into the free browser check to see whether encoding or length explains why two retries were treated as different requests.
FAQ
What does it cost?
$0.002 per request. The same check runs free in your browser on this page.
Is the key stored or checked against previously seen keys?
No. The check is purely about format: length and characters. Nothing is stored and no deduplication state is consulted.
Why is an empty key an error instead of a failed check?
Because an empty key is never a formatting choice — it is a bug in the caller. The API rejects it as invalid input so the problem surfaces immediately.
Which characters are considered URL-safe?
The RFC 3986 unreserved set: uppercase and lowercase letters, digits, hyphen, dot, underscore and tilde. Anything else is reported in invalid_chars.
Can I change the length limits?
Yes. Pass min_length and max_length to override the defaults of 16 and 255, for example to match a provider that documents a 64-character ceiling.
Does a valid result guarantee the key is unique?
No. The check only verifies format. Uniqueness comes from how you generate the key — a UUID or a similar high-entropy source is the usual answer.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/dev/idempotency-key-format-check \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"key":"order-7f3a9c2e-2026-07-25"}'const res = await fetch("https://api.kit.forhosting.com/dev/idempotency-key-format-check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"key": "order-7f3a9c2e-2026-07-25"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/dev/idempotency-key-format-check",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"key": "order-7f3a9c2e-2026-07-25"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/dev/idempotency-key-format-check", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"key":"order-7f3a9c2e-2026-07-25"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"key":"order-7f3a9c2e-2026-07-25"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/dev/idempotency-key-format-check", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"key": "order-7f3a9c2e-2026-07-25"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "dev.idempotency_key_format_check",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |