API overview
This reference covers the server-to-server integration API available to environment-bound API keys. Interactive dashboard and platform-operator operations are intentionally excluded.
| Base URL | https://pehmen-api.huper.technology |
| Version header | pehmen-version: 2026-08-01 on every response. Sending a different Pehmen-Version is a 400. |
| Reference | GET /openapi.json (OpenAPI 3.1) and GET /docs (rendered), generated at boot from the live route table. |
| Liveness | GET /healthz → {"ok":true,"service":"pehmen-api"} |
| Public keys | GET /.well-known/pehmen-webhook-keys.json, GET /.well-known/pehmen-vce-keys.json |
Headers
| Header | Meaning |
|---|---|
x-api-key: pehmen_test_… or Authorization: Bearer pehmen_test_… | Your key. |
x-pehmen-organization: <workspace id> | The workspace, as a 36-character UUID, shown on the overview and Quickstart screens. The API verifies that it matches the key's workspace. |
x-pehmen-environment: test | Defaults to test. A key may only name its own environment (403 environment_mismatch). |
idempotency-key: <1–255 chars> | Mandatory where the operation's OpenAPI parameters mark it required. |
content-type: application/json | On bodies. Body limit 1 MiB. |
Every response carries request-id (minted server-side, never adopted from the request). Quote it when you ask for help.
Idempotency
The key is scoped to workspace, environment and resource family. The request is fingerprinted (method, URL, body):
- Same key, same request: the stored response is replayed. Nothing re-runs.
- Same key, different request:
409 idempotency_conflict. - Same key while the first is still running:
409 idempotency_in_progress. - Missing or too long:
400 idempotency_key_required.
Use something you already have, like your order id, so a retry after a timeout can never create a second payment.
Money and ids
Amounts are integer minor units with an ISO 4217 currency (12800 + SGD is SGD 128.00). A value already rounded by JSON parsing is refused rather than paid.
Ids are prefix_ + 32 lowercase hex: org_, usr_, mct_, con_, ord_, pi_, pa_, ptx_, rd_, rk_, re_, slr_, bnf_, bch_, po_, cs_, ev_, evt_ (the id in Pehmen-Event-Id), apl_, pol_, whe_, whd_, whi_, rcf_, rcx_, key_, apr_, sim_, vce_, fev_, fsrc_, job_, att_, mcu_, nch_, dxi_. The workspace id used in the x-pehmen-organization header is the exception: a plain UUID.
Lists
GET lists take limit (up to 200) and after (a cursor). Responses are { data: [...], has_more, next_cursor }.
Errors
One envelope everywhere, including unmatched routes:
{ "error": { "code": "no_eligible_route", "message": "…", "request_id": "…", "details": [ … ] } }
Codes and their meanings are in Errors. A refused credential carries details: [{ reason_code, guidance }] with the same operator guidance the dashboard shows.
Cross-workspace access
An object that belongs to another workspace is a 404, indistinguishable from one that does not exist.
Rate limits
| Limiter | Numbers |
|---|---|
| Per API key | Bucket of 50, refilled at 10 per second. 429 rate_limited. |
| Mutations per source IP | 600 per minute, burst 900. 429 with retry-after. |
| Provider webhook inbox per IP | 3000 per minute, burst 4500. |