KulPayAPI Live

API reference

Event pipeline

Operator view of the outbox relay, consumers, dead-letter queue and projection replays.

GET /v1/admin/events/dead-letters

List dead-lettered events

Events a consumer gave up on after its maximum attempts, with the last error. Requires admin:read.

Parameters

NameInTypeDescription
consumerquerystring
include_replayedqueryboolean

Responses

  • 200 Dead letters.

Request

curl -X GET "$KULPAY_URL/v1/admin/events/dead-letters" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "attempts": 1,
      "consumer": "test.flaky",
      "created_at": "2026-10-10T22:15:09.502740134Z",
      "error": "downstream unavailable",
      "event_id": "3KWPmSNckmxcSVVyjYjwiyknc9S",
      "event_type": "transaction.stage.notify.skipped",
      "id": "3KWPmXrzwUTODrmptisEQ6dOEIE",
      "tenant_id": "3KWPmY5oeJFG8Ct0XNDvwiGGOhG"
    }
  ]
}

POST /v1/admin/events/dead-letters/{id}/replay

Replay a dead-lettered event

Runs the event through its consumer again, once, recorded under the caller. A second failure dead-letters it again. Requires admin:write.

Parameters

NameInTypeDescription
id requiredpathstring

Responses

Request

curl -X POST "$KULPAY_URL/v1/admin/events/dead-letters/{id}/replay" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "replayed": true
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "No such dead letter (not_found).",
  "error": {
    "code": "not_found",
    "message": "No such dead letter (not_found)."
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}
409 Error · invalid_state application/problem+json
{
  "code": "invalid_state",
  "detail": "Already replayed (invalid_state).",
  "error": {
    "code": "invalid_state",
    "message": "Already replayed (invalid_state)."
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}
502 Error · internal_error application/problem+json
{
  "code": "internal_error",
  "detail": "The consumer failed again (replay_failed).",
  "error": {
    "code": "internal_error",
    "message": "The consumer failed again (replay_failed)."
  },
  "status": 502,
  "title": "Bad Gateway",
  "type": "about:blank"
}

GET /v1/admin/events/pipeline

Outbox, consumers and dead-letter summary

Requires admin:read.

Responses

  • 200 Pipeline status.

Request

curl -X GET "$KULPAY_URL/v1/admin/events/pipeline" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "bus": "db",
  "consumers": [
    {
      "consumer": "projection.event_stats",
      "cursor_at": "2026-10-10T22:22:46.913204Z",
      "cursor_event_id": "3KWQht6ShKw2GvsxLg91zBsjsyX",
      "started_at": "2026-10-08T10:38:02.854371Z"
    }
  ],
  "metrics": {
    "dead_lettered": 0,
    "duplicates": 0,
    "lag_alert": false,
    "oldest_unpublished_age": 0,
    "processed": 147,
    "publish_failures": 0,
    "publish_lag_p99": 596568000,
    "published": 147,
    "retries": 0,
    "stale_gaps": 0,
    "unpublished": 0
  },
  "oldest_unpublished_age_seconds": 0,
  "open_dead_letters": 0,
  "unpublished": 0
}

GET /v1/admin/events/replays

List projection replays

Requires admin:read.

Parameters

NameInTypeDescription
projectionquerystring

Responses

  • 200 Replays, newest first.

Request

curl -X GET "$KULPAY_URL/v1/admin/events/replays" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": []
}

POST /v1/admin/events/replays

Rebuild a projection from the event store

Rebuilds whole closed UTC days into a shadow table and swaps it in only if its row count and checksum match an independent count of the event store. Never sends webhooks or calls providers. Requires admin:write.

Request body application/json required

FieldTypeDescription
from requiredstring (date)
projectionstring
to requiredstring (date) Inclusive; must be before today (UTC).

Responses

  • 201 The completed replay. string
  • 400 Bad range, today included, or unknown projection (invalid_request).
  • 409 The rebuild did not match the event store; nothing was swapped (invalid_state).

Request

curl -X POST "$KULPAY_URL/v1/admin/events/replays" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "from": "2026-10-01",
  "projection": "event_stats",
  "to": "2026-10-07"
}'
201 Response · string application/json
{
  "checksum": "string",
  "completed_at": "2026-10-10T09:30:00Z",
  "error": "string",
  "events_read": 1,
  "from": "2026-10-10T09:30:00Z",
  "id": "3KUXQ43bztiuf9tCMNgbcrYvne3",
  "projection": "event_stats",
  "rows_written": 1,
  "started_at": "2026-10-10T09:30:00Z",
  "started_by": "string",
  "status": "running",
  "to": "2026-10-10T09:30:00Z"
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "eventbus: a replay can only cover closed days (up to yesterday, UTC)",
  "error": {
    "code": "invalid_request",
    "message": "eventbus: a replay can only cover closed days (up to yesterday, UTC)"
  },
  "status": 400,
  "title": "Invalid replay",
  "type": "about:blank"
}
409 Error · invalid_state application/problem+json
{
  "code": "invalid_state",
  "detail": "The rebuild did not match the event store; nothing was swapped (invalid_state).",
  "error": {
    "code": "invalid_state",
    "message": "The rebuild did not match the event store; nothing was swapped (invalid_state)."
  },
  "status": 409,
  "title": "Conflict",
  "type": "about:blank"
}

GET /v1/admin/jobs

Singleton jobs and their leaders

Settlement sync, retention, the audit anchor and the audit verifier run on one node per region. This lists each job, the node leading it and this node's run statistics; every run is also recorded as job.run.started and job.run.completed or job.run.failed. Requires admin:read.

Responses

Request

curl -X GET "$KULPAY_URL/v1/admin/jobs" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "data": [
    {
      "every": "30s",
      "failures_on_this_node": 0,
      "job": "settlement_sync",
      "last_run_at": "2026-10-10T22:15:13.567193217Z",
      "last_success_at": "2026-10-10T22:15:13.567210404Z",
      "leader_alive": true,
      "leader_node": "node-a",
      "lease_until": "2026-10-10T22:15:33.548606907Z",
      "runs_on_this_node": 1,
      "this_node_leads": true
    }
  ],
  "node": "node-a",
  "region": "mz"
}
503 Error · jobs_unavailable application/problem+json
{
  "code": "jobs_unavailable",
  "detail": "No scheduler on this node (jobs_unavailable).",
  "error": {
    "code": "jobs_unavailable",
    "message": "No scheduler on this node (jobs_unavailable)."
  },
  "status": 503,
  "title": "Service Unavailable",
  "type": "about:blank"
}

POST /v1/admin/jobs/{name}/run

Run a job now

Makes the job due at once: the node leading it runs it on its next tick, with its lease and run record as usual (QA T-501). Requires admin:write.

Parameters

NameInTypeDescription
name requiredpathstring

Responses

  • 202 Scheduled.
  • 404 No job of that name. not_found

Request

curl -X POST "$KULPAY_URL/v1/admin/jobs/{name}/run" \
  -H "Authorization: Bearer $KULPAY_KEY"
202 Response application/json
{
  "job": "string",
  "scheduled": true
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "no job named nightly (GET /v1/admin/jobs lists them)",
  "error": {
    "code": "not_found",
    "message": "no job named nightly (GET /v1/admin/jobs lists them)"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

POST /v1/admin/test/clock/advance

Move a test deployment's time forward

In a deployment with test_apis on (never in live mode), moves the service's time forward by duration (a Go duration: 90m, 26h), so windows and deadlines pass without backdating rows (QA T-501). Elsewhere 404. Requires admin:write.

Request body application/json required

FieldTypeDescription
duration requiredstring

Responses

  • 200 The new time.
  • 404 test_apis is off. not_found

Request

curl -X POST "$KULPAY_URL/v1/admin/test/clock/advance" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "duration": "26h"
}'
200 Response application/json
{
  "now": "2026-10-10T09:30:00Z"
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "payments: this deployment's clock cannot be moved (test_apis is off)",
  "error": {
    "code": "not_found",
    "message": "payments: this deployment's clock cannot be moved (test_apis is off)"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}