Payments API reference
The same parsers and verifiers that power the tools, over HTTP. Not a reimplementation — the API calls the same functions, so it cannot give a different answer to the same input.
Free, and keyed
There is no charge and no paid tier to upgrade to. A key exists so the rate limit can be per caller rather than per IP address, and so we can see which endpoints get used — 3 keys per account, 500 requests per key per hour. If you need more than that, the same functions are on npm as pymnt-tools and run locally with no limit at all.
Create a key at /dashboard/api-keys. It is shown once, because only a hash of it is stored.
Authentication
curl -X POST https://tools.moveahead.tech/api/v1/card/inspect \
-H 'Authorization: Bearer pk_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"pan":"4111111111111111"}'Every endpoint is a POST taking a JSON object and returning the same result shape the browser tool renders — including the steps[] breakdown on a signature check, and the issues[] array on a parse. A GET to any endpoint returns its usage instead of an error.
What is never stored
The body. Requests here carry webhook payloads, card numbers and PIN blocks, and the request log records only which endpoint was called and what status it returned — there is no column for the input and there will not be one. This is the same rule the capture product follows, and it is the reason the API is safe to point at the data you are actually debugging.
That said, the browser tools remain the stronger promise: they never transmit anything at all. Use those where you can, and this where you need automation. See privacy.
Endpoints
13 endpoints. Every one is listed by the API itself at GET /api/v1/any/path, generated from the same table that dispatches them.
POST /api/v1/webhook-signature/verifyProviders: razorpay, razorpay-payment, stripe, cashfree, payu. Send the raw request body, never a re-serialised one — a signature is over exact bytes.
{
"provider": "razorpay",
"payload": "{\"event\":\"payment.captured\"}",
"secret": "your_webhook_secret",
"signature": "3f8c…"
}POST /api/v1/iso8583/parseOptional: "input" ("text" or "hex"), "layout" ("text" or "packed"), "chars" ("ascii" or "ebcdic"), "stripLeading", "stripSpaces".
{
"message": "0200723C…",
"input": "hex",
"layout": "packed"
}POST /api/v1/emv-tlv/parseAlso emv-tlv/field55, which takes the same "hex" as it appears in ISO 8583 field 55. Cardholder data is masked unless you pass "maskCardholderData": false.
{
"hex": "77 2F 9F27 01 80 9F36 02 00A5"
}POST /api/v1/pin-block/buildAlso pin-block/decode, taking block and pan. Formats 0 to 3; clear blocks only.
{
"format": "0",
"pin": "1234",
"pan": "4111111111111111"
}POST /api/v1/pin-block/kcvCheck values for a key, one per algorithm its length admits — 16 bytes is double-length 3DES or AES-128, and both are returned because nothing in the bytes says which. A length no algorithm accepts returns an empty list. Also pin-block/combine, taking a components array to XOR.
{
"key": "0123456789ABCDEF FEDCBA9876543210"
}POST /api/v1/emvco/parse{
"payload": "00020101021128…"
}POST /api/v1/upi/parseAlso upi/build, taking a "fields" object of UPI parameters.
{
"link": "upi://pay?pa=merchant@okhdfcbank&am=10.00"
}POST /api/v1/card/inspect{
"pan": "4111111111111111"
}POST /api/v1/ifsc/validate{
"code": "HDFC0000001"
}Errors
Every failure is a JSON object with an error string that says what to change.
401— no key, a malformed key, or a revoked one. The message does not distinguish an unknown key from a revoked one, because that would confirm a key once existed.404— no such endpoint. The response lists the ones that exist.400— the input was wrong, and the message names the field. Note that a parse failure is not a 400: a malformed ISO 8583 message is a successful call whose result reports where alignment broke, because that report is the thing you asked for.413— body over 256 KB.429— over the hourly limit.Retry-Afterand theX-RateLimit-*headers say where you stand.
Stability
The path is /api/v1 and the result shapes are the same TypeScript types the npm package publishes, which are semver-stable at 1.0. A breaking change to either would be /api/v2 rather than a quiet edit. Captured deliveries on a webhook endpoint are kept for 1 day on the free plan; this API stores nothing at all.