Shadow / Demo Mode — all data is simulated for evaluation. paykx.co.uk ↗

PAYKX Decision API

Independent pre-execution intelligence for high-friction payment corridors. Send a corridor and amount, get back a clear GO / DEGRADED / NO-GO decision — proceed, pause, or reroute — with a confidence score and a seven-signal breakdown.

Checking API status… View status →
What PAYKX does not do: PAYKX does not move money or hold funds. It provides an independent pre-transaction decision so your existing payment infrastructure remains unchanged.

Base URL

https://api.paykx.co.uk
Version 1.2 Shadow / Demo Mode Response < 200ms Corridors: GB-NG, NG-GB Contact: taseenrayed@paykx.co.uk
Deterministic Same inputs, same output
Idempotent Safe agent retries
< 200ms Sub-second response
99.9% uptime Shadow mode SLA
REST + JSON No SDK required
Versioned /v1 No breaking changes

Make your first call

Get an API key

Request access and we'll issue a live key. Until then, use the public demo endpoint. No key required.

Call the verify endpoint

Send a POST request with your corridor and amount, authenticated with your key.

curl https://api.paykx.co.uk/api/v1/verify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"corridor": "GB-NG", "amount": 1500, "currency": "GBP"}'

Read the decision

The response gives you a decision (GO / DEGRADED / NO-GO), a score, and the seven signals behind it. See Decisions for how to act on each.

{
  "decision": "GO",
  "score": 0.94,
  "confidence": 0.97,
  "recommended_partner": "Partner A",
  "reasons": [
    { "code": "HEALTHY_PARTNER", "message": "All monitored execution signals are healthy." }
  ],
  "reason_codes": ["HEALTHY_PARTNER"],
  "request_id": "req_m3a7fzk2p1r4",  // ← req_… format = demo only, not auditable (see note below)
  "evaluated_at": "2026-07-15T11:30:47Z",
  "decision_model": "v1.2",
  "probe_count": 7,
  "corridor": "GB-NG",
  "currency": "GBP",
  "corridor_supported": true,
  "response_ms": 112,
  "signals": {
    "balance_sufficient": true,
    "operation_valid": true,
    "network_healthy": true,
    "historical_failure_rate": 0.04,
    "cop_overridden": false,
    "fx_volatility": "low",
    "fraud_flags": 0
  },
  "probe_details": {
    "fraud_flags":             { "signal": "CLEAR", "risk_value": 1.00 },
    "network_healthy":         { "signal": "CLEAR", "risk_value": 0.92 },
    "balance_sufficient":      { "signal": "CLEAR", "risk_value": 1.00 },
    "fx_volatility":           { "signal": "CLEAR", "risk_value": 0.88 },
    "operation_valid":         { "signal": "CLEAR", "risk_value": 0.95 },
    "historical_failure_rate": { "signal": "CLEAR", "risk_value": 0.96 },
    "cop_overridden":          { "signal": "CLEAR", "risk_value": 1.00 }
  },
  "environment": "fca-sandbox-validated",
  "api_version": "1.2"
}
req_… IDs are demo-only and not auditable. The request_id in this example uses the demo format. It is ephemeral — demo calls are never persisted, so passing this ID to GET /api/v1/audit/:id returns a 404. Auditable IDs use the format PAYKX-{year}-{id} and are only issued by the authenticated POST /api/v1/verify endpoint.

Authentication & API Keys

Every call to a protected endpoint requires your API key, passed as a Bearer token. There are two key types — test keys for shadow mode and live keys for production.

Key types

TypeFormatEnvironmentBehaviour
Test keypk_test_xxxxxShadow / demoFull scoring, no live rails. Responses are deterministic and safe to run repeatedly.
Live keypk_live_xxxxxProductionReal corridor data, live partner probes. Requires approved access.
All keys currently issued are test / shadow keys. Live keys will be issued when the corridor exits shadow mode. The response shape is identical in both environments.

How to authenticate

Pass your key in the Authorization header as a Bearer token, or alternatively in the X-API-Key header:

curl https://api.paykx.co.uk/api/v1/verify \
  -H "Authorization: Bearer pk_test_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{"corridor": "GB-NG", "amount": 1500, "currency": "GBP"}'
curl https://api.paykx.co.uk/api/v1/verify \
  -H "X-API-Key: pk_test_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{"corridor": "GB-NG", "amount": 1500, "currency": "GBP"}'
const res = await fetch("https://api.paykx.co.uk/api/v1/verify", {
  method: "POST",
  headers: {
    "Authorization": "Bearer pk_test_xxxxx",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ corridor: "GB-NG", amount: 1500, currency: "GBP" })
});
const { decision, score, confidence, reason_codes } = await res.json();
import requests

resp = requests.post(
    "https://api.paykx.co.uk/api/v1/verify",
    headers={"Authorization": "Bearer pk_test_xxxxx"},
    json={"corridor": "GB-NG", "amount": 1500, "currency": "GBP"},
)
data = resp.json()
print(data["decision"], data["confidence"], data["reason_codes"])

Key lifecycle

TopicDetail
ExpiryTest keys do not expire. Live keys expire after 90 days and are reissued on request.
RotationTo rotate, submit a new access request noting you need a replacement. Old keys are revoked within 24 hours of issuing the new one.
ErrorsA missing or invalid key returns 401 Unauthorized. There are no fallback or default keys — the API always fails closed.
ScopeKeys scope verification history — your key only reads results it created. Two callers cannot see each other's records.
Keep your key server-side. Never embed it in browser JavaScript, mobile app code, or public repositories. Always load it from an environment variable.

Onboarding flow

From zero to a verified, scoped API key in five steps:

1
Submit access request

Go to /request-access and fill in your name, email, and a one-line description of your use case. No credit card or contract needed.

2
Receive your sandbox key immediately

A paykx-sandbox-… key is issued on submission. Use it to call any protected endpoint right now. Responses are identical in shape to production — you can build your full integration today.

3
Integrate and test

Send real request shapes. The sandbox scoring engine runs the same seven-signal pipeline as production. Use GET /api/verifications (with your key) to retrieve your verification history for QA.

4
Admin review and live key issuance

The PAYKX team reviews your request. On approval, a paykx-live-… key is issued directly to you. The raw key is shown once — copy it immediately and store it as an environment variable.

5
Go live — no code changes required

Swap the environment variable value from your sandbox key to your live key. The request and response shape is identical. Your integration keeps working without modification.

Permissions

PermissionSandbox keyLive key
Call /api/v1/verify
Call /api/v1/demo-verifyNot needed — publicNot needed — public
Call /api/v1/demo-compareNot needed — publicNot needed — public
Call /api/v1/batch-assess
Read own verification history
Read other keys' verifications
Admin panel access

Keys are scoped to the caller. Each key can only read the verification records it created — two API consumers are never able to see each other's data.

Sandbox / Demo

PAYKX currently runs in shadow mode. This is a safe test environment where every decision is computed with the real scoring engine, but no live payment rails are touched. It's a good way to integrate and test before you go live.

Shadow mode is a feature. You get the same response shapes and decisions you would see in production, with zero risk. The same inputs always produce the same outputs, so your integration tests stay stable.

Public demo endpoint

Want to try PAYKX without a key? The demo endpoint needs no authentication and returns a full seven-signal decision for the GB-NG corridor.

curl -X POST https://api.paykx.co.uk/api/v1/demo-verify \
  -H "Content-Type: application/json" \
  -d '{"corridor": "GB-NG", "amount": 1500, "currency": "GBP"}'

Profile comparison endpoint

Want to see how different risk profiles score the same payment? POST /api/v1/demo-compare needs no authentication and scores a payment against conservative, balanced, and speed-optimised profiles in one call. Use amount: 85000 to see profiles diverge.

curl -X POST https://api.paykx.co.uk/api/v1/demo-compare \
  -H "Content-Type: application/json" \
  -d '{
    "payment": {"corridor": "GB-NG", "amount": 85000, "currency": "GBP"},
    "profiles": ["conservative", "balanced", "optimise_speed"]
  }'

See the full endpoint reference in the API Reference section below, or explore it interactively in the API Explorer ↗.

API Reference

Full request and response detail for every endpoint. Expand a card to see parameters, an example response, and a live “Try it out” console.

POST /api/v1/verify Verify a payment corridor

Runs a deterministic risk assessment on the specified corridor using seven weighted signals: fraud_flags (30%), network_healthy (25%), balance_sufficient (15%), fx_volatility (10%), operation_valid (10%), historical_failure_rate (5%), cop_overridden (5%).
Score ≥ 0.80 → GO  |  0.48–0.79 → DEGRADED  |  < 0.48 → NO-GO

NameInTypeDescription
modequerystringlive (default) or shadow
Authorization*headerstringBearer <your-api-key>
FieldTypeRequiredDescription
corridorstringYESPayment corridor in FROM-TO format, e.g. GB-NG, NG-GB
amountnumberoptionalTransfer amount in units of currency. Triggers policy cap for large transfers (> 50,000 → DEGRADED; > 500,000 → NO-GO)
currencystringoptionalISO 4217 currency code, e.g. GBP, USD, NGN. Defaults to GBP
idempotency_keystringoptionalA unique identifier (UUID recommended) that makes retries safe. Repeated calls with the same key and body return the cached result. Mismatched body returns 409
"decision": "GO",
"score": 0.87,
"volatility": 0.09,
"confidence": 0.87,
"probe_count": 7,
"corridor_supported": true,
"validation_note": "Corridor fully validated with deep data.",
"idempotency_hit": false,
"signals": {
  "balance_sufficient":      true,
  "operation_valid":         true,
  "network_healthy":         true,
  "historical_failure_rate": 0.04,
  "cop_overridden":          false,
  "fx_volatility":           "low",
  "fraud_flags":             0
},
"probe_details": {
  "balance_sufficient":      { "signal": "CLEAR", "risk_value": 1.00 },
  "operation_valid":         { "signal": "CLEAR", "risk_value": 0.95 },
  "network_healthy":         { "signal": "CLEAR", "risk_value": 0.92 },
  "historical_failure_rate": { "signal": "CLEAR", "risk_value": 0.96 },
  "cop_overridden":          { "signal": "CLEAR", "risk_value": 1.00 },
  "fx_volatility":           { "signal": "CLEAR", "risk_value": 0.88 },
  "fraud_flags":             { "signal": "CLEAR", "risk_value": 1.00 }
},
"currency": "GBP",
"environment": "fca-sandbox-validated"
"decision": "DEGRADED",
"score": 0.61,
"confidence": 0.88,
"recommended_partner": "Partner B",
"reasons": [
  { "code": "HIGH_FX_VOLATILITY", "message": "Foreign exchange spread or rate volatility is elevated." },
  { "code": "HIGH_FAILURE_RATE", "message": "Historical failure rate on this corridor is above acceptable threshold." }
],
"reason_codes": ["HIGH_FX_VOLATILITY", "HIGH_FAILURE_RATE"],
"request_id": "req_p9xk2m4r7nq1",
"evaluated_at": "2026-07-15T11:31:22Z",
"decision_model": "v1.2",
"probe_count": 7,
"corridor_supported": true,
"signals": {
  "balance_sufficient":      true,
  "operation_valid":         true,
  "network_healthy":         true,
  "historical_failure_rate": 0.28,
  "cop_overridden":          false,
  "fx_volatility":           "high",
  "fraud_flags":             0
},
"environment": "fca-sandbox-validated"
200Verification result returned successfully
400Invalid request body: corridor field missing or malformed
401Missing or invalid API key
409Idempotency key reused with a different request body
429Rate limit exceeded — slow down and retry with backoff
500Unexpected server error — safe to retry with exponential backoff
503Decision service temporarily unavailable — retry later
GET /api/health API Health Check

Returns the current health status of the PAYKX Rail API, including version and mode. No authentication required.

"status": "healthy",
"version": "1.2",
"message": "PAYKX Rail Verification API is running successfully",
"mode": "shadow",
"environment": "shadow-demo"
200API is healthy and running
GET /api/v1/cip/status Corridor Intelligence Platform status

Returns the system-level maturity and confidence status of the Corridor Intelligence Platform (CIP). No authentication required. Use this endpoint to determine unambiguously whether CIP probe telemetry is influencing live scoring decisions.

ℹ Currently confidence: "experimental" — telemetry only.
The CIP is in Phase 0. Probe data is collected and stored but decision_influence: false means no CIP signal has any effect on POST /api/v1/verify responses. This status will advance to "provisional" once the quality gate defined in ADR-015 is passed.
FieldTypeDescription
phasenumberCIP implementation phase. 0 = telemetry collection only; 1 = sandbox coverage; 2 = production signals; 3 = full network.
statestring"telemetry_only" — data collected, no scoring influence. "scoring_passive" — signals computed, blend_weight=0. "scoring_active" — blend_weight>0, influencing decisions.
decision_influencebooleanfalse means no CIP observation has any effect on any verify decision. Only becomes true after the ADR-015 governance process is completed.
supported_for_decisioningbooleanfalse means this telemetry layer is not cleared for production payment decisions. Explicit complement to decision_influence — removes all ambiguity that telemetry exists but is not yet trusted for scoring.
confidencestring"experimental" — corpus <14 days, unvalidated. "provisional" — quality gate passed, blend_weight 0.05–0.10. "validated" — full production coverage, blend_weight ≥0.15.
last_updatedstring (ISO 8601)The date the CIP entered its current phase and confidence state. This is not the date of the last API request, last governance document edit, or last deployment — it changes only when a formal phase or confidence transition is committed through the ADR-015 governance process.
observations_collectednumberTotal synthetic probe observations stored in cip.observations. 0 during Phase 0 before the probe pipeline is live. Use this to track corpus growth over time.
blend_weightnumberProportion of the composite score drawn from CIP signals (0–1). Governed by ADR-015. Requires two-engineer PR approval and named decision-maker sign-off to increase.
escalation_enabledbooleanfalse — anomaly alerts never auto-degrade a corridor regardless of severity or duration. Phase 0 constraint; enabled in Phase 1 after sustained-alert validation.
active_adaptersstring[]Adapter IDs currently providing telemetry data. Grows as additional data sources are confirmed permitted and integrated.
governed_bystringADR reference that controls changes to this status. All state transitions require the process defined there.
{
  "phase": 0,
  "state": "telemetry_only",
  "decision_influence": false,
  "supported_for_decisioning": false,
  "confidence": "experimental",
  "last_updated": "2026-08-01T00:00:00.000Z",
  "observations_collected": 0,
  "blend_weight": 0,
  "escalation_enabled": false,
  "active_adapters": ["synthetic-baseline", "central-bank-ng"],
  "governed_by": "ADR-015"
}
200CIP status returned — always succeeds, no auth required
POST /api/v1/demo-verify Public demo, no auth required

Runs the same seven-signal scoring as /api/v1/verify for the GB-NG corridor, with no API key required. Rate limited to 15 requests per 15 minutes per IP.

FieldTypeRequiredDescription
corridorstringoptionalDefaults to GB-NG
amountnumberoptionalTransfer amount (defaults to 1000)
currencystringoptionalDefaults to GBP
POST /api/v1/demo-compare Public profile comparison, no auth required

Evaluates the same payment against up to three named risk profiles (conservative, balanced, optimise_speed) and returns a divergence summary. No API key required. Rate limited to 15 requests per 15 minutes per IP. For authenticated production use, see POST /api/v1/decision/compare.

FieldTypeRequiredDescription
paymentobjectYESPayment details: corridor, amount, currency. Defaults to GB-NG / 1000 / GBP.
profilesstring[]optionalWhich profiles to compare: conservative, balanced, optimise_speed. Defaults to all three.
curl -X POST https://api.paykx.co.uk/api/v1/demo-compare \
  -H "Content-Type: application/json" \
  -d '{
    "payment": { "corridor": "GB-NG", "amount": 85000, "currency": "GBP" },
    "profiles": ["conservative", "balanced", "optimise_speed"]
  }'
"corridor": "GB-NG",
"amount": 85000,
"profiles_compared": ["conservative", "balanced", "optimise_speed"],
"results": {
  "conservative": { "decision": "DEGRADED", "score": 0.726, "go_threshold": 0.85 },
  "balanced":     { "decision": "DEGRADED", "score": 0.726, "go_threshold": 0.80 },
  "optimise_speed": { "decision": "GO",       "score": 0.726, "go_threshold": 0.70 }
},
"divergence": {
  "profiles_agree": false,
  "summary": "Profiles diverge: conservative→DEGRADED, balanced→DEGRADED, optimise_speed→GO."
},
"evaluated_at": "2026-07-29T12:00:00Z",
"environment": "sandbox"
200Profile comparison result returned
400Invalid request body
429Rate limit exceeded
POST /api/v1/request-access Request an API key

Submit a request for a production API key. This records your request and returns a confirmation; once reviewed and approved, the PAYKX team issues a live key. Rate limited to 5 requests per 15 minutes per IP and protected by reCAPTCHA. The easiest way to submit is the access request form, which handles the CAPTCHA for you.

FieldTypeRequiredDescription
namestringYESYour name
emailstringYESWork email
reasonstringYESWhy you need access
recaptcha_tokenstringYESreCAPTCHA token (supplied automatically by the form)
200Access request submitted
400Missing fields or failed CAPTCHA
429Too many requests, try again later

Corridor Scope

PAYKX only returns a confident decision for corridors it has deep, validated data for. Everything else is deliberately returned as DEGRADED so you are never given false confidence.

CorridorRouteStatusBehaviour
GB-NGUnited Kingdom → NigeriaFully supportedFull seven-signal scoring, real GO / DEGRADED / NO-GO
NG-GBNigeria → United KingdomFully supportedFull seven-signal scoring, real GO / DEGRADED / NO-GO
Any othere.g. US-GB, GB-US, US-NGLimitedReturns corridor_supported: false and a forced DEGRADED decision
Always check the corridor_supported flag on the response. When it is false, treat the result as “not enough data” rather than a genuine risk signal.

Decisions

Every verification returns one of three decisions. For supported corridors the decision follows the composite score band below, so it's fully deterministic and explainable. Unsupported corridors are always returned as DEGRADED (see note).

DecisionScore bandMeaningWhat the operator should do
GO≥ 0.80Low riskProceed with the payment
DEGRADED0.48 – 0.79Moderate riskReview manually or route with caution
NO-GO< 0.48High riskBlock or investigate before sending
Only GB-NG and NG-GB are fully validated with deep data. Any other corridor returns corridor_supported: false and an honest DEGRADED decision rather than a falsely confident result.

Score vs Confidence — why both matter

The response returns two separate numeric fields that are easy to conflate but operationally distinct:

FieldWhat it measuresRange
scoreThe composite risk score — the weighted sum of all seven signal values. Drives the decision band directly.0.00 – 1.00
confidenceHow certain the engine is about its own assessment given the signal quality and corridor data depth. High confidence means the decision is well-supported; low confidence means it is based on thinner data.0.00 – 1.00

Two DEGRADED decisions can look identical in the decision band but be operationally very different:

"decision": "DEGRADED",
"score":    0.68,
"confidence": 0.97,
"recommended_partner": "Partner B",
"reasons": [{ "code": "HIGH_FX_VOLATILITY", "message": "FX volatility elevated." }]

High confidence DEGRADED — strong signal, known cause, route or review with certainty.

"decision": "DEGRADED",
"score":    0.68,
"confidence": 0.51,
"recommended_partner": null,
"reasons": [{ "code": "CORRIDOR_UNSUPPORTED", "message": "Limited data." }]

Low confidence DEGRADED — thin data, treat as “not enough signal” and apply more caution.

Code Examples

Call the verify endpoint from any stack. Replace YOUR_API_KEY with your issued key.

curl https://api.paykx.co.uk/api/v1/verify \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"corridor": "GB-NG", "amount": 1500, "currency": "GBP"}'
import requests

resp = requests.post(
    "https://api.paykx.co.uk/api/v1/verify",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"corridor": "GB-NG", "amount": 1500, "currency": "GBP"},
)
data = resp.json()
print(data["decision"], data["score"])
const res = await fetch("https://api.paykx.co.uk/api/v1/verify", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ corridor: "GB-NG", amount: 1500, currency: "GBP" })
});
const data = await res.json();
console.log(data.decision, data.score);

SDKs

The official @paykx/sdk package is in private beta. Copy the wrapper below into your project now — it has the same interface the published package will use, so your code will not need to change.

COMING SOON — Private Beta npm
npm install @paykx/sdk
import { verify } from '@paykx/sdk';

const result = await verify({
  apiKey: process.env.PAYKX_API_KEY,
  corridor: 'GB-NG',
  amount: 1500,
  currency: 'GBP',
});
// result.decision | result.confidence | result.reason_codes

Request early access: taseenrayed@paykx.co.uk

Use today — copy this wrapper

Same interface, no dependency, works in any Node.js or browser environment:

// paykx.js — drop-in client, same interface as @paykx/sdk
const BASE = "https://api.paykx.co.uk";

export async function verify({ apiKey, corridor, amount, currency = "GBP", idempotencyKey } = {}) {
  const res = await fetch(`${BASE}/api/v1/verify`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ corridor, amount, currency, idempotency_key: idempotencyKey }),
  });
  if (res.status === 429) {
    const retryAfter = res.headers.get("Retry-After");
    throw Object.assign(new Error("Rate limited"), { status: 429, retryAfter });
  }
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw Object.assign(new Error(err.message ?? `PAYKX error ${res.status}`), { status: res.status });
  }
  return res.json(); // { decision, score, confidence, reason_codes, signals, probe_details, … }
}

// usage
const { decision, confidence, reason_codes } = await verify({
  apiKey: process.env.PAYKX_API_KEY,
  corridor: "GB-NG",
  amount: 1500,
});
console.log(decision, confidence, reason_codes);
# paykx.py — drop-in client, same interface as paykx-sdk (PyPI, coming soon)
import os, requests

BASE = "https://api.paykx.co.uk"

def verify(*, api_key=None, corridor, amount, currency="GBP", idempotency_key=None):
    api_key = api_key or os.environ["PAYKX_API_KEY"]
    resp = requests.post(
        f"{BASE}/api/v1/verify",
        headers={"Authorization": f"Bearer {api_key}"},
        json={"corridor": corridor, "amount": amount, "currency": currency,
              **({"idempotency_key": idempotency_key} if idempotency_key else {})},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()  # dict with decision, score, confidence, reason_codes, …

# usage
result = verify(corridor="GB-NG", amount=1500)
print(result["decision"], result["confidence"], result["reason_codes"])
// paykx.cjs — CommonJS version
const BASE = "https://api.paykx.co.uk";

async function verify({ apiKey, corridor, amount, currency = "GBP", idempotencyKey } = {}) {
  const res = await fetch(`${BASE}/api/v1/verify`, {
    method: "POST",
    headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
    body: JSON.stringify({ corridor, amount, currency, idempotency_key: idempotencyKey }),
  });
  if (!res.ok) throw new Error(`PAYKX ${res.status}`);
  return res.json();
}

module.exports = { verify };
package paykx

import (
        "bytes"
        "encoding/json"
        "fmt"
        "net/http"
)

const base = "https://api.paykx.co.uk"

type VerifyRequest struct {
        Corridor       string  `json:"corridor"`
        Amount         float64 `json:"amount,omitempty"`
        Currency       string  `json:"currency,omitempty"`
        IdempotencyKey string  `json:"idempotency_key,omitempty"`
}

type VerifyResponse struct {
        Decision    string  `json:"decision"`
        Score       float64 `json:"score"`
        Confidence  float64 `json:"confidence"`
        ReasonCodes []string `json:"reason_codes"`
        RequestID   string  `json:"request_id"`
        EvaluatedAt string  `json:"evaluated_at"`
}

func Verify(apiKey string, req VerifyRequest) (*VerifyResponse, error) {
        body, _ := json.Marshal(req)
        r, err := http.NewRequest("POST", base+"/api/v1/verify", bytes.NewReader(body))
        if err != nil {
                return nil, err
        }
        r.Header.Set("Authorization", "Bearer "+apiKey)
        r.Header.Set("Content-Type", "application/json")
        resp, err := http.DefaultClient.Do(r)
        if err != nil {
                return nil, err
        }
        defer resp.Body.Close()
        if resp.StatusCode != 200 {
                return nil, fmt.Errorf("paykx: %s", resp.Status)
        }
        var result VerifyResponse
        json.NewDecoder(resp.Body).Decode(&result)
        return &result, nil
}

// usage
// result, err := paykx.Verify(os.Getenv("PAYKX_API_KEY"), paykx.VerifyRequest{
//     Corridor: "GB-NG", Amount: 1500, Currency: "GBP",
// })
# Verify corridor — replace YOUR_KEY with your API key
curl https://api.paykx.co.uk/api/v1/verify \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"corridor":"GB-NG","amount":1500,"currency":"GBP"}'

# Demo endpoint — no key required
curl -X POST https://api.paykx.co.uk/api/v1/demo-verify \
  -H "Content-Type: application/json" \
  -d '{"corridor":"GB-NG","amount":1500,"currency":"GBP"}'

# Profile comparison — no key required
# Returns the same payment scored against conservative / balanced / speed-optimised profiles
curl -X POST https://api.paykx.co.uk/api/v1/demo-compare \
  -H "Content-Type: application/json" \
  -d '{"payment":{"corridor":"GB-NG","amount":85000,"currency":"GBP"},"profiles":["conservative","balanced","optimise_speed"]}'

# Health check
curl https://api.paykx.co.uk/api/health

# With idempotency (safe retries)
curl https://api.paykx.co.uk/api/v1/verify \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"corridor":"GB-NG","amount":1500,"currency":"GBP","idempotency_key":"uuid-goes-here"}'

Error Codes

PAYKX uses standard HTTP status codes. Every error response includes a JSON body with a message field.

CodeMeaningWhen it happens
200OKDecision returned successfully
400Bad RequestRequest body is missing required fields or is malformed
401UnauthorizedMissing or invalid API key
409ConflictAn idempotency key was reused with a different request body
422UnprocessableRequest parsed but semantically invalid (e.g. unsupported corridor)
429Too Many RequestsRate limit exceeded, slow down and retry later
500Server ErrorUnexpected error — safe to retry with exponential backoff
503Service UnavailableDecision service temporarily unavailable — retry later

JSON response bodies

Every non-200 response returns the same JSON envelope. Use message for display and code (when present) for programmatic logic.

400 Bad Request
{ "message": "corridor is required" }
401 Unauthorized
{ "message": "Invalid API key." }
409 Conflict
{ "message": "Idempotency key reused with a different request body." }
429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Retry-After: 37

{ "message": "Rate limit exceeded. Try again in 37 seconds." }
500 Server Error
{ "message": "Internal Server Error" }
503 Service Unavailable
{ "message": "Service temporarily unavailable." }

Idempotency

Pass an idempotency_key (for example a UUID) in the request body to make retries safe. The first request with a given key is processed and cached; reusing the same key with the same body returns the identical cached result with idempotency_hit: true. Reusing it with a different body returns 409 Conflict.

Error Handling Guide

Practical guidance for each error state — what caused it, how to detect it in code, and the correct recovery action for your integration.

400 Invalid payment intent

Cause: The request body is missing a required field, has an invalid value, or the JSON is malformed. Common triggers: missing corridor or amount, a corridor string in the wrong format (e.g. "UK-NG" instead of "GB-NG"), or a negative amount.

HTTP/1.1 400 Bad Request

{
  "message": "corridor is required",
  "code": "INVALID_REQUEST"
}

Recommended action:

  • Read the message field — it identifies the exact field or constraint that failed.
  • Do not retry automatically. Fix the request payload before sending again.
  • Log the full request body alongside the error so your team can diagnose integration issues quickly.
  • Validate corridor against the supported corridor list before sending.
401 Invalid API key

Cause: The Authorization header is absent, the token is malformed, or the key has been revoked or has not yet been approved.

HTTP/1.1 401 Unauthorized

{
  "message": "Invalid API key."
}

Recommended action:

  • Confirm your key is being sent as Authorization: Bearer <key> — not in the query string or body.
  • Check for whitespace or line breaks in the stored key value (common in copy-paste from email).
  • Do not retry with the same key — repeated 401s against a production key may trigger abuse alerts.
  • If the key was just issued, allow up to 60 seconds for propagation before retrying.
  • To rotate a compromised key, contact access@paykx.co.uk immediately.
429 Rate limit exceeded

Cause: Your API key (or source IP for public endpoints) has exceeded the per-minute or per-hour request allowance. See the Rate Limits table for the exact thresholds.

HTTP/1.1 429 Too Many Requests
Retry-After: 37

{
  "message": "Rate limit exceeded. Try again in 37 seconds."
}

Recommended action:

  • Read the Retry-After response header — it tells you the exact number of seconds to wait.
  • Implement exponential backoff with jitter if you do not read Retry-After: start at 1 s, double on each retry up to 60 s, add ±10% random jitter.
  • If you regularly hit rate limits, batch payment intents server-side before calling PAYKX, or request a higher tier — contact access@paykx.co.uk.
  • Do not fan out multiple identical requests to work around rate limits — use idempotency_key for safe retries instead.
503 Signal unavailable

Cause: One or more upstream data signals (FX feed, network health probe, fraud flag provider) are temporarily unreachable. PAYKX cannot produce a reliable decision and will not guess. This is a transient condition — typically resolves within 2 minutes.

HTTP/1.1 503 Service Unavailable
Retry-After: 120

{
  "message": "Signal provider temporarily unavailable. Retry after 2 minutes.",
  "unavailable_signals": ["fx_volatility", "network_healthy"]
}

Recommended action:

  • This is safe to retry. Wait at least the Retry-After seconds, then resubmit with the same idempotency_key.
  • In your internal logic, treat a 503 as "DEGRADED — hold payment" rather than as a permanent failure.
  • Subscribe to the status page for incident alerts so your on-call team is notified automatically.
  • If you receive 503 for more than 5 minutes continuously, escalate to access@paykx.co.uk.
  • Do not fall back to allowing payments unconditionally during a PAYKX outage — your downstream compliance policy should define what to do when intelligence is unavailable.
General retry rule. Only 429 and 5xx responses should be retried. Never retry 400 or 401 automatically — they indicate a client-side problem that will not resolve on its own.

Rate Limits

Public endpoints are rate limited per IP. Authenticated endpoints are gated per API key. Exceeding any limit returns 429 Too Many Requests with a Retry-After header telling you when to retry.

EndpointAuthLimitWindowScope
/api/v1/verifyRequired100 requests1 minutePer API key
/api/v1/demo-verifyNone15 requests15 minutesPer IP
/api/v1/demo-compareNone15 requests15 minutesPer IP
/api/v1/request-accessNone5 requests15 minutesPer IP
/api/healthNoneUnlimited

Retry-After header

When a 429 is returned, the response includes a Retry-After header with the number of seconds until your limit resets. Implement exponential backoff starting from that value:

HTTP/1.1 429 Too Many Requests
Retry-After: 37
Content-Type: application/json

{ "message": "Rate limit exceeded. Try again in 37 seconds." }

Burst behaviour

Requests are counted on a rolling window, not a fixed clock boundary. A burst of requests within the same second counts against the per-minute limit. If you need higher throughput, contact taseenrayed@paykx.co.uk to discuss a higher-rate tier.

The corridor cooldown system is separate from rate limiting. After a NO-GO decision, the same corridor enters a 90-second cooldown window during which subsequent calls return the cached decision rather than recomputing — protecting both you and the API from redundant expensive probes.

Retry Guidance

Not every error is worth retrying. Use the table below to decide whether to retry, fix credentials, or escalate — and how quickly.

StatusShould you retry?Strategy
400NoFix the request — required field missing or corridor invalid
401NoFix credentials — API key is missing or has been revoked
403NoAccess denied — contact PAYKX to check your key permissions
409NoIdempotency conflict — reuse key with same body to get the cached result
429YesRetry after the Retry-After header value (seconds)
500YesExponential backoff: 1 s → 2 s → 4 s → 8 s, then escalate
503YesRetry after a short delay — service is recovering

Exponential backoff pattern

async function verifyWithRetry(payload, apiKey, maxAttempts = 4) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const res = await fetch("https://api.paykx.co.uk/api/v1/verify", {
      method: "POST",
      headers: { "Authorization": "Bearer " + apiKey, "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });
    if (res.ok) return res.json();
    if (res.status === 429) {
      const wait = parseInt(res.headers.get("Retry-After") ?? "5", 10) * 1000;
      await new Promise(r => setTimeout(r, wait));
    } else if (res.status >= 500) {
      await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
    } else {
      throw Object.assign(new Error("PAYKX error " + res.status), { status: res.status });
    }
  }
  throw new Error("Max retries exceeded");
}

Request Traceability

Every PAYKX response includes fields and headers that let you correlate a decision back to the exact request that produced it — in your logs, in your UI, and in support escalations. Traceability is part of the Execution Assurance contract: if you acted on a PAYKX decision, you can always prove what the engine said and when.

Response fields

FieldTypeExamplePurpose
request_idstring"paykx-req-a3f9c2"Unique identifier for this verification. Log it alongside every payment operation — it's the primary key for support and audit queries.
evaluated_atISO 8601"2026-07-19T11:42:00.000Z"Exact timestamp the scoring engine produced the decision. Use this for audit trails and time-bounded replays.
api_versionstring"1.2"The scoring model version that produced this decision. If you later compare two decisions made under different model versions, this tells you which rules applied.
decision_modelstring"v1.2"Alias for the decision logic version — included for human-readable audit logs alongside the numeric api_version.
correlation_idstring (optional)"my-payment-ref-123"Echoed back if you pass it on the request. Use it to tie a PAYKX decision to your own internal payment reference — no UUID required on your side.

Response headers

HeaderValuePurpose
X-Request-IDSame as request_idAvailable in the HTTP response headers so you can correlate without parsing the body — useful for logging middleware.

Passing a correlation ID

Include correlation_id in your request body to bind the PAYKX decision to your own transaction reference. It is echoed back on the response and stored with the verification record.

{
  "corridor": "GB-NG",
  "amount": 2500,
  "currency": "GBP",
  "correlation_id": "your-internal-payment-ref-abc123"
}

// Response includes:
{
  "request_id": "paykx-req-a3f9c2",
  "correlation_id": "your-internal-payment-ref-abc123",
  "evaluated_at": "2026-07-19T11:42:00.000Z",
  "api_version": "1.2",
  "decision": "GO",
  ...
}

Retrieving verification history

Every verification you run is stored and retrievable. Use your API key to query your own record set — cross-key access is not permitted.

GET /api/verifications
Authorization: Bearer pk_test_xxxxx

// Returns an array of your verification records including:
// request_id, corridor, decision, score, confidence, evaluated_at, correlation_id
Audit trail tip: Log request_id, correlation_id, decision, score, reason_codes, and evaluated_at to your own ledger at the point of each PAYKX call. If a payment is later disputed, you have a timestamped, versioned record of the pre-execution assurance check that was performed.

Decision Model

PAYKX evaluates seven independent signals across five risk dimensions. Every signal is weighted, the weights produce a composite score, and the score determines the decision band. There is no black-box model — every decision is auditable.

Fraud
fraud_flags 30%
AML and fraud indicator scan
Network
network_healthy 25%
Live rail and settlement network probe
Liquidity
balance_sufficient 15%
Partner liquidity and balance check
FX / Corridor
fx_volatility 10%
Foreign exchange spread and volatility
Operations
operation_valid 10%
Transaction type and operation validity
History
historical_failure_rate 5%
Partner / corridor failure rate lookback
Compliance
cop_overridden 5%
Confirmation of Payee and CoP override flag
Composite Score
0.00 – 1.00
Weighted sum of all signal values
≥ 0.80
GO
Proceed
0.48–0.79
DEGRADED
Review
< 0.48
NO-GO
Block
When 3 or more signals are flagged simultaneously, an additional penalty reduces the composite score by 25% — preventing a borderline decision from being masked by one strong signal carrying all the weight.

Seven signals — what each one measures

PAYKX does not expose proprietary scoring weights or raw data sources, but the category of inputs feeding each signal is documented here so you can reason about what a flag means and what to do about it.

Fraud fraud_flags Weight: 30%

AML and fraud indicator scan

Evaluates transaction-level AML indicators, amount-pattern anomalies, counterparty risk typologies, and known fraud profile matches for the corridor. The signal is binary: zero flags (clear) or one or more flags detected.

When flagged: The reason code HIGH_FRAUD_SIGNAL is emitted. Block the transaction and escalate — do not retry the same payload.

Network network_healthy Weight: 25%

Live rail and settlement network probe

A real-time probe of the settlement rail health for the requested corridor — network latency, congestion indicators, and partner settlement endpoint availability. Unhealthy state means a payment that might technically send could silently fail to settle.

When flagged: NETWORK_DEGRADED. Retry after a delay (see Retry Guidance) or reroute to an alternate rail.

Liquidity balance_sufficient Weight: 15%

Partner liquidity and balance check

Checks whether the selected corridor has sufficient liquidity headroom for the requested amount — based on partner reserve levels and recent settlement volumes. A transfer that enters a low-liquidity corridor may be delayed, reversed, or held pending rebalancing.

When flagged: INSUFFICIENT_BALANCE. Route to a different partner or retry later when reserves recover.

FX / Corridor fx_volatility Weight: 10%

Foreign exchange spread and rate volatility

Measures the current bid-ask spread and rate-of-change for the currency pair. Elevated volatility increases the gap between the rate quoted at initiation and the rate at settlement — widening slippage risk and increasing the probability of a failed payout if the rate moves outside corridor bounds.

When flagged: HIGH_FX_VOLATILITY. Proceed with caution or wait for a stable window; show the user an indicative rate with a tight expiry.

Operations operation_valid Weight: 10%

Transaction type and operation validity

Validates that the transaction type, operation category, and currency combination is supported and correctly structured for the target corridor. Some operation types are restricted for specific corridors — this signal surfaces that mismatch before any funds move.

When flagged: OPERATION_INVALID. Check the operation type field and resubmit — this is a data error, not a risk signal.

History historical_failure_rate Weight: 5%

Partner and corridor failure rate lookback

A rolling lookback of settlement outcomes for this partner and corridor combination. A rising or sustained failure rate is a leading indicator of coming execution problems — systems are often degrading before they visibly fail. This signal catches the trend early.

When flagged: HIGH_FAILURE_RATE. Consider an alternate route or a later time window.

Compliance cop_overridden Weight: 5%

Confirmation of Payee and CoP override detection

Detects whether Confirmation of Payee (CoP) was bypassed in the payment initiation chain upstream of this call. CoP bypass increases the probability of account name mismatch, authorised push payment (APP) fraud, and regulatory non-compliance. This signal fires as a compliance flag, not a network signal.

When flagged: COP_OVERRIDE_ACTIVE. Require manual CoP confirmation before releasing the transfer.

Execution Assurance context: Each signal is asking a different question about whether this payment will actually complete. Fraud, network, liquidity, FX, operations, history, and compliance are the seven ways a cross-border transfer can fail silently. PAYKX surfaces all seven before you execute.

Execution Uncertainty Score

The Execution Uncertainty Score (EUS) is a derived operator metric surfaced in Replay Analysis reports. It provides a single headline number summarising the concentration of DEGRADED and NO-GO decisions across an analysed transaction portfolio — giving operators an immediate orientation before reading the full report.

How it is calculated

EUS is computed directly from the replay results — it is not a separate model. The formula is:

EUS = round( min(100,  no_go_pct × 3.5  +  degraded_pct × 1.0) )

Where no_go_pct and degraded_pct are the percentage of transactions in each decision band from the replay. Both inputs come from the same PAYKX decisions already present in the report — EUS adds no new scoring logic.

Why NO-GO is weighted higher than DEGRADED

NO-GO decisions represent execution conditions severe enough for PAYKX to block a payment entirely. DEGRADED decisions indicate conditions that may warrant review but do not necessarily prevent execution. The 3.5× weight on NO-GO reflects that asymmetry — a portfolio with 10% NO-GO carries meaningfully more operational risk than one with 10% DEGRADED, and the score should reflect that difference.

Score levels and thresholds

BandScore rangeInterpretation
Low0 – 20Relatively stable execution conditions. Most transactions are receiving GO decisions.
Moderate21 – 35Noticeable proportion of flagged decisions. Review DEGRADED transactions before release.
High36 – 60Significant execution uncertainty. A material share of the portfolio received DEGRADED or NO-GO decisions.
Critical61 – 100Severe execution uncertainty. Immediate operational review of corridor conditions is recommended.

These thresholds are PAYKX guidance derived from the scoring formula. They will be calibrated against real partner replay data as design partners share historical transaction outcomes. Thresholds may shift as calibration data is incorporated.

What EUS is not

EUS is a summary statistic, not a predictive model output. It does not claim to predict future failure rates, guarantee operational outcomes, or replace analysis of the full report. Use it as an orientation metric — the opening number in a Shadow Diagnostic conversation — before reviewing decision distribution, corridor health, and top risk signals in detail.

Accessing EUS via API

EUS is returned automatically in every POST /api/v1/batch-assess response under execution_uncertainty_score. A standalone methodology reference is also available:

GET /api/v1/methodology

Returns the formula, band definitions, weighting rationale, and calibration status as structured JSON — suitable for embedding in operator dashboards or internal documentation.

Reason Codes

Every response includes a reasons array of objects (each with a code and a message) and a flat reason_codes array for backward-compatible consumers. Use these codes to drive downstream logic — retry routing, manual review queues, or user messaging. These codes are part of the API contract and will not be removed without a deprecation notice.

Reason CodeSignalWhat it meansSuggested action
HEALTHY_PARTNERAll signals are clear; partner operating normallyProceed — no elevated risk detected
HIGH_FRAUD_SIGNALfraud_flagsAML or fraud indicators detected on this transactionBlock and escalate for manual review
NETWORK_DEGRADEDnetwork_healthySettlement rail or network health is below thresholdRetry after a delay or reroute to alternate rail
INSUFFICIENT_BALANCEbalance_sufficientPartner or corridor liquidity is too lowRoute to a different partner or retry later
HIGH_FX_VOLATILITYfx_volatilityFX spread or rate volatility is elevatedProceed with caution or wait for stable window
OPERATION_INVALIDoperation_validTransaction type not valid for this corridorCheck operation type and resubmit
HIGH_FAILURE_RATEhistorical_failure_rateRecent failure rate on this corridor is elevatedConsider an alternate route or timing
COP_OVERRIDE_ACTIVEcop_overriddenConfirmation of Payee was bypassedRequire manual CoP confirmation before release
CORRIDOR_UNSUPPORTEDCorridor is not yet in the validated set (GB-NG, NG-GB)Treat as “no data” — do not use as a risk signal
POLICY_CAP_APPLIEDTransfer amount exceeds an autonomous execution thresholdRoute to manual settlement approval

Transactions above £50,000 are subject to a policy cap that floors the verdict at DEGRADED. This reflects conservative execution policy for high-value transfers pending corridor-specific calibration.

Using reason codes in your application

Read reason_codes (flat array) or reasons (array of objects with code and message) to drive consistent user experiences. Build your routing logic around the code strings — they are stable API contract values and will not change without a deprecation notice.

const data = await res.json();

switch (data.decision) {
  case "GO":
    // Safe to execute — all signals clear
    await executePayment(payload);
    break;

  case "DEGRADED":
    if (data.reason_codes.includes("POLICY_CAP_APPLIED")) {
      // High-value transfer needs manual approval
      await queueForManualReview(payload, data.request_id);
    } else if (data.reason_codes.includes("NETWORK_DEGRADED")) {
      // Rail issue — retry in 30–60s
      scheduleRetry(payload, 45_000);
    } else if (data.reason_codes.includes("HIGH_FX_VOLATILITY")) {
      // Show user a warning and ask to confirm at new rate
      showFxWarning(data.reasons.find(r => r.code === "HIGH_FX_VOLATILITY").message);
    } else {
      await routeToPartnerB(payload);
    }
    break;

  case "NO-GO":
    if (data.reason_codes.includes("HIGH_FRAUD_SIGNAL")) {
      // Hard block — do not allow retry
      blockAndEscalate(payload, data.request_id);
    } else {
      // All other NO-GO — inform user and stop
      showUserError("This transfer cannot proceed right now.");
    }
    break;
}
data = resp.json()
decision = data["decision"]
codes = data["reason_codes"]

if decision == "GO":
    execute_payment(payload)

elif decision == "DEGRADED":
    if "POLICY_CAP_APPLIED" in codes:
        queue_for_manual_review(payload, data["request_id"])
    elif "NETWORK_DEGRADED" in codes:
        schedule_retry(payload, delay_seconds=45)
    elif "HIGH_FX_VOLATILITY" in codes:
        show_fx_warning(next(r["message"] for r in data["reasons"]
                            if r["code"] == "HIGH_FX_VOLATILITY"))
    else:
        route_to_partner_b(payload)

elif decision == "NO-GO":
    if "HIGH_FRAUD_SIGNAL" in codes:
        block_and_escalate(payload, data["request_id"])
    else:
        show_user_error("This transfer cannot proceed right now.")
Execution Assurance pattern: Never branch only on decision. Reading reason_codes tells you why the decision was made — and therefore which action is correct. A DEGRADED because of HIGH_FX_VOLATILITY needs a different response than one caused by NETWORK_DEGRADED. The reason catalogue is the machine-readable explanation layer.

Architecture

PAYKX sits in front of your existing payment flow as an independent decision step. It never touches funds. It only returns a decision you act on.

Your System or AI Agent payment request POST /api/v1/verify PAYKX RAIL Decision API · v1.2 · Shadow Mode 7 SIGNAL PROBES fraud_flags 30% network 25% balance 15% fx_vol 10% op_valid 10% history 5% cop 5% Score ≥ 0.80 → GO  ·  0.48–0.79 → DEGRADED  ·  < 0.48 → NO-GO Corridors: GB-NG · NG-GB Never touches funds · Fails closed <200ms GO Execute the payment DEGRADED Review or reroute NO-GO Block and investigate Deterministic · Rule-based · Score-weighted across 7 signals

Request flow

A single POST /api/v1/verify call walks through the following steps inside PAYKX Rail before a decision is returned:

1
Auth & schema validation

Bearer token extracted, hashed with SHA-256, and matched against approved key hashes. Simultaneously, the request body is validated against the typed Zod schema. Both must pass — a missing field or invalid key returns immediately without touching the scoring engine.

2
Idempotency check

If an Idempotency-Key header is present, PAYKX checks whether that key has been seen before. If yes and the fingerprint matches, the cached decision is returned immediately — the scoring engine is not run again. A fingerprint mismatch returns 409 Conflict.

3
Corridor normalisation and cooldown check

The requested corridor is normalised to a canonical form (e.g. GB-NG) and checked against the supported-corridor allowlist. If this corridor recently returned NO-GO, the 90-second cooldown window applies and the cached decision is returned without a recompute.

4
Seven-signal probe run

All seven signals are evaluated in parallel where dependencies permit. Each probe writes a numeric value (0.0–1.0) and a binary pass/fail into the probe details object.

5
Composite score and policy gate

Weighted signal values are summed into a composite score. If 3 or more signals are flagged, a 25% penalty is applied. The policy gate then checks the transfer amount against thresholds — amounts above £50,000 floor at DEGRADED; above £500,000 force NO-GO.

6
Decision band assignment and reason code derivation

Score is mapped to GO (≥0.80), DEGRADED (0.48–0.79), or NO-GO (<0.48). Flagged signals are translated into reason codes, a recommended_action is derived for DEGRADED outcomes, and a recommended_partner is selected if applicable.

7
Persistence and response

The verification record is written to the database with a unique request_id. The response is serialised and returned, with X-Request-ID set in the response headers. Total wall time: typically 60–150 ms.

Decision pipeline

Request body
  ↓ Zod schema validation
  ↓ Auth: SHA-256(token) vs approved hash set
  ↓ Idempotency: key seen? → return cached result
  ↓ Corridor: normalise → cooldown check
  ↓ Signal probes (7 parallel evaluations)
      fraud_flags  network_healthy  balance_sufficient
      fx_volatility  operation_valid  historical_failure_rate  cop_overridden
  ↓ Weighted sum → composite score [0.00 – 1.00]
  ↓ Multi-flag penalty (3+ flagged → −25%)
  ↓ Policy gate (amount thresholds)
  ↓ Decision band: ≥0.80 GO | 0.48–0.79 DEGRADED | <0.48 NO-GO
  ↓ Reason codes derived from flagged signals
  ↓ recommended_action (DEGRADED only)
  ↓ Write verification record → return response

Latency

PercentileResponse timeNotes
Median (p50)~80 msFull seven-signal evaluation with all signals returning cached or fast values
p95~150 msIncludes live network probe with moderate latency
p99<250 msWorst-case with live partner probe and elevated network latency
Idempotent hit<20 msCached result returned — scoring engine not invoked
Cooldown hit<10 msCorridor in NO-GO cooldown — cached decision returned from DB

The response_ms field on every response gives you the actual server-side processing time for that specific call. Use it to monitor your own p95 and p99 over time.

Audit trail

Every PAYKX response is an immutable, timestamped record. The following fields together form a complete audit entry for any pre-execution assurance check:

FieldWhat it records
request_idUnique identifier for this specific verification call
evaluated_atISO 8601 timestamp — exact moment the scoring engine produced the decision
api_versionScoring model version — tells you which rules were in effect
decisionThe engine's verdict: GO, DEGRADED, or NO-GO
scoreThe composite score that produced the decision (0.00–1.00)
confidenceHow well-supported the decision is given signal quality
reason_codesWhich signals were flagged — the explainable basis for the decision
probe_detailsPer-signal raw values — full transparency, signal by signal
correlation_idYour reference (if passed) — ties the PAYKX record to your payment ledger
modeshadow or live — which environment produced the decision
Immutable by design. Verification records are write-once. A decision stored in the audit log cannot be modified — not by a key rotation, not by a later call. If a payment is disputed, PAYKX gives you an independently held record of the pre-execution decision that was in place at the moment the payment was initiated.

Enterprise Integration Guide

A production integration follows five deterministic steps. Each step maps to a PAYKX API call or an action in your own system.

1
Create an API key

Request a production key via the access form. Sandbox keys are issued instantly for integration testing. Production keys are reviewed manually and issued within 1 business day.

POST /api/v1/request-access
{ "name": "Acme Payments", "email": "eng@acme.com", "reason": "..." }
→ Your key is sent by email. Store it in your secrets manager — never in code.
2
Submit a payment intent

Before initiating any transfer, call /api/v1/verify with the corridor, amount, and currency. Pass an Idempotency-Key so safe retries never re-run the scoring engine.

POST /api/v1/verify
Authorization: Bearer <your-key>
Idempotency-Key: <your-payment-uuid>
{ "corridor": "GB-NG", "amount": 5000, "currency": "GBP" }
3
Receive the PAYKX decision

The response arrives in under 200 ms and contains the decision, composite score, confidence, signal breakdown, and reason codes. Map it to your payment flow logic.

{ "decision": "GO", "score": 0.87, "confidence": 0.94,
  "request_id": "req_...", "reason_codes": [] }

{ "decision": "DEGRADED", "score": 0.61, "confidence": 0.72,
  "reason_codes": ["HIGH_FX_VOLATILITY"] }

{ "decision": "NO-GO", "score": 0.31, "confidence": 0.88,
  "reason_codes": ["FRAUD_FLAG_ACTIVE", "NETWORK_DEGRADED"] }
4
Execute or hold the payment

Your system acts on the decision. If a compliance officer needs to override a DEGRADED or NO-GO result, record the override through the API — this creates a permanent, auditable link between the original decision and the human action.

// GO: proceed immediately
if (decision === 'GO') initiateTransfer(payment);

// DEGRADED: queue for review, or override if approved
if (decision === 'DEGRADED') holdForReview(payment);

// Record a compliance override if approved manually
POST /api/v1/override
{ "request_id": "PAYKX-2026-622", "action": "EXECUTE",
  "reason_code": "COMPLIANCE_APPROVED", "justification": "..." }

// NO-GO: block and do not proceed
if (decision === 'NO-GO') preventExecution(payment);
5
Store the audit record

Retrieve the full audit trail — original decision, all 7 signal readings, and any override — with a single call. Store or surface this to your compliance team as evidence of pre-execution due diligence.

GET /api/v1/audit/PAYKX-2026-{id}
Authorization: Bearer <your-key>
→ Returns decision + all signals + override (if any) in one response.
→ Immutable. Write-once. Cannot be modified after creation.
⚠ Only PAYKX-{year}-{id} IDs are auditable. The req_… format returned by /api/v1/demo-verify and /api/v1/demo-compare is ephemeral — those calls are never persisted and will return 404 here.
Webhooks optional but recommended. Register a webhook endpoint (POST /api/v1/webhooks) to receive real-time push events for payment.decision.created and payment.override.created — so your system reacts without polling.

Decision Lifecycle

Every payment intent flows through a fixed lifecycle. PAYKX produces the decision — your system acts on it — and the audit record closes the loop.

Payment Intent Created corridor + amount + currency POST /api/v1/verify PAYKX Evaluation 7 signals scored in parallel fraud · network · balance · fx · history · op · cop <200 ms · deterministic · fails closed GO Score ≥ 0.80 Execute the payment WAIT Score 0.48–0.79 Hold + Monitor NO-GO Score < 0.48 Prevent Execution Compliance Override POST /api/v1/override Audit Stored decision + signals + override (if any) GET /api/v1/audit/PAYKX-{year}-{id} Immutable write-once record · independently held by PAYKX · retrievable by your compliance team

Reading the diagram

GO — Execute

Score ≥ 0.80. All critical signals clear. Proceed immediately. No hold required.

WAIT — Hold + Monitor

Score 0.48–0.79. One or more signals elevated. Queue for review or route via a lower-risk rail. A compliance officer may issue an override to proceed.

NO-GO — Prevent

Score < 0.48. Multiple signals flagged or policy cap breached. Do not execute. Investigate the flagged signals before retrying.

The override path. When a compliance officer approves a DEGRADED result manually, POST /api/v1/override stores the justification and links it permanently to the original decision. The audit record returned by GET /api/v1/audit/… then contains both the original PAYKX decision and the human override — a complete, tamper-evident chain of custody.

Webhook Retry Policy

PAYKX guarantees at-least-once delivery for every webhook event. If your endpoint does not return a 2xx response, the platform retries automatically on a fixed backoff schedule.

Retry schedule

AttemptDelay after previous failureCumulative wait
1Immediate
21 minute1 min
35 minutes6 min
415 minutes21 min
530 minutes51 min

After 5 failed attempts the event is dropped. Events are not replayed beyond the retry window.

Event envelope fields

Every delivery includes metadata fields to support idempotent processing:

{
  "event":      "payment.override.created",
  "event_id":   "evt_3f8a2c1d9e7b4051",
  "created_at": "2026-07-20T14:23:01.000Z",
  "attempt":    1,
  "data": { ... }
}
  • event_id — stable across all retry attempts for the same event. Use this to deduplicate on your side.
  • attempt — integer starting at 1. Increments with each retry so you can log delivery history.
  • X-PAYKX-Attempt — also sent as an HTTP header on every delivery request.
  • X-PAYKX-Signature — HMAC-SHA256 of secret + body. Recompute this on every attempt to verify authenticity before processing.

Responding correctly

Your endpoint must return HTTP 2xx within 5 seconds. Return any other status code — or let the connection time out — and PAYKX will schedule a retry.

Idempotency recommendation. Because retries re-send the same event_id, your handler should check whether an event has already been processed before taking action — for example, by storing processed event IDs in a database.

Response headers on all enterprise endpoints

Every enterprise API endpoint also returns two environment headers for integration diagnostics:

X-PAYKX-Version:     v1
X-PAYKX-Environment: sandbox

API Key Permissions

Today every API key grants full access to all authenticated endpoints. The permissions model below is designed for enterprise buyers who need fine-grained role separation — for example, allowing a treasury team to read decisions but not submit overrides.

Planned permission scopes

ScopeGrants access toTypical role
decision:read POST /api/v1/verify, GET /api/v1/decision/:id/explanation Treasury analyst, risk monitor
audit:read GET /api/v1/audit/:id, GET /api/v1/audit/export/:id Compliance officer, auditor
override:create POST /api/v1/override Treasury manager, compliance lead
webhook:manage POST /api/v1/webhooks, GET /api/v1/webhooks, DELETE /api/v1/webhooks/:id Platform engineer, integration owner

Future API key schema

When permissions are introduced, API key metadata returned from the access-request flow will include a permissions array:

{
  "api_key":    "pk_live_...",
  "issued_at":  "2026-07-20T00:00:00Z",
  "permissions": [
    "decision:read",
    "audit:read",
    "override:create",
    "webhook:manage"
  ]
}

Requests made with a key that lacks the required scope will receive a 403 Forbidden response with a required_scope field identifying which permission is missing.

Current state. In the sandbox today, all authenticated keys have full access. If your compliance or security team needs a specific permission model for your evaluation, contact access@paykx.co.uk to discuss production onboarding requirements.

PAYKX vs Payment Orchestration

The single most common question from bank and fintech buyers. PAYKX and payment orchestration layers solve adjacent problems — they are designed to work together, not replace each other.

Payment Orchestration
"Which rail should we use?"

Orchestration layers (e.g. Spreedly, Paydock, Volt) route a confirmed payment intent to the cheapest, fastest, or most available rail — SWIFT, SEPA, CHAPS, Faster Payments, local schemes. They assume the payment should go ahead and optimise execution.

  • Rail selection & fallback routing
  • Fee optimisation across providers
  • Retry on rail failure
  • Provider SLA monitoring
  • Conversion rate uplift
PAYKX Rail
"Should this payment execute now?"

PAYKX is a pre-execution intelligence layer. Before any rail is selected or any funds move, PAYKX evaluates corridor health, FX volatility, fraud signals, and network conditions to return a GO / DEGRADED / NO-GO decision with a confidence score.

  • Corridor risk scoring (7 signals)
  • GO / DEGRADED / NO-GO decision
  • FX volatility & liquidity gate
  • Fraud & AML signal aggregation
  • Compliance-grade audit trail

How they fit together in a payment flow

// Step 1 — Pre-execution gate (PAYKX)
POST /api/v1/verify    { decision: "GO", score: 0.87 }
// Step 2 — Rail selection (Orchestration, only if GO)
POST /orchestrator/route   { rail: "SWIFT GPI", fee: "£2.40" }
// Step 3 — Execution
POST /orchestrator/execute { status: "submitted" }
// Step 4 — Audit closure (PAYKX)
GET   /api/v1/audit/:id     { decision: "GO", override: null, ... }

Capability comparison

Capability Payment Orchestration PAYKX
Rail selection & routing
Provider failover
Corridor risk scoring
FX volatility gate
Fraud / AML signal aggregation
GO / NO-GO pre-execution gate
Compliance override & audit chain
Works with any orchestration layer
Positioning in one sentence. Your orchestration layer decides how to move money. PAYKX decides whether money should move at all. Both questions must be answered — and PAYKX is designed to sit upstream of any orchestration or execution layer you already use.

PAYKX and Fraud, AML & Compliance Layers

PAYKX does not evaluate whether a customer is legitimate. It evaluates whether the payment route is ready to execute.

Fraud detection, AML, and KYC systems analyse customer behaviour, identity, and transaction risk. They answer:

"Should this transaction from this customer be approved?"

PAYKX analyses payment execution conditions before funds are committed. It answers:

"Is this corridor and payment partner reliable enough to execute through right now?"

These layers are complementary.

PAYKX does not replace:

  • KYC
  • AML screening
  • fraud detection
  • transaction monitoring

PAYKX operates upstream as an execution assurance layer.

Security & Compliance

A complete reference for the security controls, data handling practices, and compliance assurances that enterprise and regulated buyers need to evaluate PAYKX for production use.

🔒 Authentication

All authenticated API endpoints require a Bearer token in the Authorization header. Tokens are long random strings (minimum 128 bits of entropy) issued through the access request flow.

Authorization: Bearer pk_live_<your-key>
  • Keys are hashed (SHA-256) before storage — the plaintext key is never persisted.
  • The plaintext key is returned once only at issuance. Store it in a secrets manager (e.g. AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager).
  • Keys have no expiry by default in sandbox. Production keys will support configurable TTL and rotation policies.
  • Admin operations (key issuance, access approval) require a separate X-Admin-Key header with a distinct secret — no API key can perform admin actions.

🔐 Encryption

IN TRANSIT

All API traffic is TLS 1.2+ encrypted end-to-end. HTTP connections are not accepted in production — all requests are redirected to HTTPS. Certificate management is handled by the platform (auto-renewed).

AT REST

The PostgreSQL database is encrypted at rest using AES-256. API key secrets are SHA-256 hashed — PAYKX cannot recover a key once issued. No payment instrument data (card numbers, IBANs) is stored at any point.

📄 Audit Retention

Every verification decision is stored as a write-once immutable record. Records are never modified or deleted in response to API calls.

Record typeRetained forAccessible via
Verification decisions7 years (planned)GET /api/v1/audit/:id
Compliance overrides7 years (planned)GET /api/v1/audit/:idoverride
Compliance export reportsGenerated on demandGET /api/v1/audit/export/:id
Access request submissionsDuration of relationshipAdmin panel only
Webhook delivery logs30 days (planned)Admin panel (roadmap)

Retention periods are set to align with FCA and EBA record-keeping requirements for payment service providers. Contact us if your jurisdiction requires a different retention window.

🔑 API Key Management

  • Never embed keys in frontend code. API keys must only be used from server-side code where they cannot be extracted by browser inspection or public repos.
  • Use environment variables or a secrets manager. Do not hardcode keys in source files. Treat them like database passwords.
  • Rotate keys if compromised. Email access@paykx.co.uk to revoke a key immediately. Revocation takes effect within 60 seconds.
  • Use one key per environment. Maintain separate keys for development, staging, and production. Never use a production key in test environments.
  • Scope keys by role (roadmap). See the API Key Permissions section for the planned granular permission model.

🔗 Webhook Security

Every webhook delivery is signed with HMAC-SHA256. Your endpoint should verify the signature before processing the payload — this prevents spoofed events from triggering actions in your system.

// Node.js — verify incoming webhook signature
import { createHash } from "crypto";

function verifyWebhook(secret: string, rawBody: string, signature: string): boolean {
  const expected = "sha256=" + createHash("sha256").update(secret + rawBody).digest("hex");
  return expected === signature;
}

// In your handler:
const sig = req.headers["x-paykx-signature"];
const body = req.body; // raw string — do not parse before verifying
if (!verifyWebhook(process.env.PAYKX_WEBHOOK_SECRET, body, sig)) {
  return res.status(401).send("Invalid signature");
}
  • The X-PAYKX-Signature header is present on every delivery attempt, including retries.
  • Use a constant-time comparison (not ===) to prevent timing attacks — most crypto libraries provide this.
  • Store the webhook secret in your secrets manager alongside your API key.
  • Acknowledge with 200 OK immediately — do heavy processing asynchronously to avoid timeouts triggering retries.
  • See the Webhook Retry Policy for the full retry schedule and deduplication guidance.

👤 Data Handling

WHAT WE STORE
  • Corridor identifier (e.g. GB-NG)
  • Transfer amount & currency
  • Decision outcome & score
  • Signal risk values (no raw signal data)
  • Override records with actor identity
  • API key hash (not plaintext)
WHAT WE NEVER STORE
  • Sender or recipient personal details
  • IBANs, account numbers, sort codes
  • Card numbers or payment instruments
  • IP addresses in decision records
  • Plaintext API keys
  • Any biometric or KYC data
GDPR & data residency. The sandbox runs on EU infrastructure. PAYKX does not process personal data as part of the decision engine — corridor codes and amounts are not personal data under GDPR. Production deployments can be configured for specific data residency requirements on request.

Use Cases

Where a deterministic pre-transaction decision adds the most value.

💸

Remittance

Screen cross-border consumer transfers for corridor risk before release, cutting failed and reversed payments.

🏦

Treasury

Add a corridor-health gate to bulk settlement runs so large transfers escalate for review automatically.

🤖

Agentic Payments

Give autonomous agents a safe, idempotent GO/NO-GO check before they move money on a user's behalf.

Status

Live health of the PAYKX API, checked from your browser right now.

Status
Checking…
Version
Mode
Response time

Corridors fully validated: GB-NG, NG-GB.

How PAYKX helps payment teams scale

This section is for payment operations, treasury, and compliance leads — not engineers. It explains what changes when PAYKX is in the stack.

Before PAYKX

Payment teams managing cross-border execution typically rely on:

  • Provider dashboards — checked manually, updated on delay, one per partner
  • Failure queues — discovered after settlement, not before execution
  • Spreadsheets — corridor performance tracked offline, not in the payment path
  • Escalations — when a payment fails, the first signal is a customer complaint

The result: execution decisions are made without real-time signal data. Risk is discovered retrospectively.

After PAYKX

One API call before each payment returns:

  • Execution confidence — a score reflecting current corridor and partner health, not historical averages
  • Reason codes — machine-readable flags with plain-English business explanations, readable by compliance and treasury teams
  • Audit evidence — every decision is logged with probe-level detail, exportable for regulatory review
  • Operational recommendations — the API tells the operator what to do, not just what the signals say: proceed, reroute, request KYC, or hold

What this replaces

Without PAYKX With PAYKX
Check provider dashboard before sending POST /api/v1/verify — live seven-signal response in <300ms
Discover failure after settlement NO-GO decision before funds are committed
Manual rerouting investigation suggested_path and recommended_action: REROUTE returned inline
Offline spreadsheet corridor analysis POST /api/v1/replay/analyse — historical transaction dataset scored against current model
Audit trail assembled after the fact Every decision stored with probe-level evidence via GET /api/v1/decisions/:id/audit

Audience reference

  • Payments operations — use /verify to gate execution; use recommended_action to automate rerouting decisions
  • Treasury — use operational_implications to understand settlement and liquidity exposure before funds move
  • Compliance — use /audit and /explanation to produce decision evidence for regulatory review
  • Engineering — integrate via REST; see Quickstart and API Reference

Design Partner Data Flow

Bring PAYKX a payment execution question. We replay your historical evidence to show what PAYKX would have flagged and compare those signals with observed outcomes — each stage is discrete, reversible, and does not affect live payments.

1
Your payment execution question
Start with a question your team already has: where replayed signals aligned with later manual intervention, which later failures had relevant signals, or what evidence appeared before a delay. PAYKX frames the replay around that question.
Question: “For GB→NG payments later needing manual review, where did replayed signals align with the observed outcome?”
2
Historical evidence replay
Share 100–1,000 anonymised past transactions — corridor, amount, currency, timestamp, and outcome where available. PAYKX replays that evidence without connecting to your systems, then shows what it would have flagged. Once the dataset is confirmed usable and capacity is assigned, we agree the pilot delivery timeline.
POST /api/v1/replay/analyse → report_id
3
Decision intelligence report
A structured report shows decision distribution (GO / DEGRADED / NO-GO), top risk signals, risk patterns, affected corridors, and recommended operational actions. Every transaction includes a per-signal explanation so you can click into any individual case.
GET /api/v1/replay/report/{id} → executive summary · top risks · recommended actions
4
Shadow evaluation
PAYKX runs live against your real payment traffic — in shadow mode. Every new transaction is scored and the decision is logged, but no payment is blocked. You see exactly how the model would have routed each payment without any operational risk.
POST /api/v1/verify — execution_mode: "shadow" — no payments blocked
5
Production readiness
Once shadow alignment looks right, PAYKX moves to active gating — payments are held, rerouted, or released based on the decision. Overrides remain available at every stage: a human operator can review and release any flagged payment with a written justification, providing a full audit trail.
POST /api/v1/override — EXECUTE | HOLD — full justification logged

What question can a replay answer?

Start with a payment execution question, such as “where did manual intervention start?”, “which payments later failed?”, or “what risks were visible before a delay?”. Then provide the historical evidence needed to test it:

  • Corridor — source and destination country (e.g. GB-NG)
  • Amount and currency — transaction value
  • Outcome (optional but useful) — whether the payment succeeded or failed, so alignment can be measured

Customer names, account numbers, customer IDs, and other personal data are not required and must be removed. After your request, PAYKX provides a CSV template and secure upload instructions — do not send raw payment files by email.

How long does each stage take?

Stage Typical timeline What you see
Replay analysis Timeline agreed after dataset acceptance and capacity confirmation Decision intelligence report
Shadow evaluation 2–4 weeks Live alignment data; no operational change
Production readiness Operator decision Active gating; overrides available

Each stage has a clear exit condition. You can stop at any point — nothing is irreversible until you choose to move to active gating.

Start here

Request a limited shadow replay → — tell us your intended corridor and sample size. We'll send a CSV template and secure upload instructions within one business day; the delivery timeline is agreed only after PAYKX confirms the dataset is usable and assigns capacity.

Changelog

Notable changes to the PAYKX API and developer docs.

July 2026 · v1.3.0
  • Added ISO 20022 payment message parsing — decisions now reference ISO message type in audit record
  • Added decision confidence endpoint — /api/v1/decisions/search with score band filtering
  • Added webhook signing and replay tools — HMAC-SHA256 signatures, /api/v1/webhooks/replay/:id
  • Health endpoint extended with signals, execution_mode, capabilities, and limitations blocks
  • Navigation unified across all pages; Design Partner programme page added
June 2026 · v1.2.0
  • Added audit retrieval — full probe-level evidence per decision via /api/v1/decisions/:id/audit
  • Added sandbox scenarios — 12 pre-built corridor test cases with expected outcomes
  • Added model versioning — model_version field on every response; /api/v1/model/changelog endpoint
  • Security hardening: CAPTCHA fail-closed, API keys stored as SHA-256 hashes, 128-bit entropy
  • Developer hub: Go + cURL SDK examples, OpenAPI YAML download, Status page
June 2026 · v1.0.0
  • Seven-signal scoring engine with deterministic GO / DEGRADED / NO-GO decision bands
  • Corridor cooldowns and honest DEGRADED handling for unsupported corridors
  • Idempotency key support — safe retries with fingerprint binding
  • Public demo endpoint with no authentication required