Two cross-cutting operations that don’t belong to any business module, but that every integration uses:

OperationSDK method (Python)ScopeUsed for
GET/v1/gateway/transactions/{tx_id}/client.transactions.get(tx_id)none (any valid key)State and trace of a write you made through the gateway.
GET/v1/gateway/health/client.system.health()noneValidate the key: tenant and scopes. Health check for your monitoring.

In the other SDKs: client.transactions.get / client.system.health() (TypeScript), $client->transactions->get / $client->system->health() (PHP), client.Transactions.GetAsync / client.System.HealthAsync() (.NET) and client.Transactions.Get / client.System.Health (Go).

Transaction state #

Every write that goes through the gateway (submitting, deciding, disbursing, recording a collection, loading affiliates…) leaves a transaction record and returns its transaction_id in the response envelope. GET/v1/gateway/transactions/{tx_id}/ reads that record.

FieldSDK modelWhat it is
transaction_idyesThe id the write returned to you.
moduleyescredits, subsidies, affiliates, loans, wallet…
statusyesPROCESSING, COMPLETED or FAILED (see below).
referenceyesThe business reference that was associated with it: the application_id in credits and subsidies, the disbursement_id in monetary allowance, the receipt_id in loan portfolio.
historyyesThe milestones the call went through, in order (e.g. ["received", "submitted"]).
operationin rawThe internal operation (application, decision:<id>, disbursement:<id>…).
datain rawThe response that was stored (the same data as in the envelope).
errorin rawThe reason, if status is FAILED.
created_at, updated_atin rawISO 8601 timestamps.

The typed models (TransactionStatus) expose the first five; the rest is in raw (status.raw, $status->raw, status.Raw).

What each state means #

stateDiagram-v2
  [*] --> PROCESSING: the gateway accepts the write
  PROCESSING --> COMPLETED: the engine completed it
  PROCESSING --> FAILED: the engine rejected it or failed
  PROCESSING --> PROCESSING: the business operation is still in progress
  • PROCESSING: the write was accepted and the business operation is still open. It’s the normal state of a credit or subsidy submission (the application is now in the hands of the engine and the analyst) and of an affiliate load with rejected rows.
  • COMPLETED: the operation finished (a decision, a disbursement, an applied collection, a load with no rejections).
  • FAILED: the engine rejected the request or didn’t respond; raw.error carries the reason.

The record describes the call to the gateway, not the full life of the business object: the state of a credit application (approved, disbursed…) is queried in its module (credits.get_application) and arrives through webhooks. Use transactions.get for support, auditing and reconciliation of your own calls.

status = client.transactions.get("a1b2c3d4-0000-4a1b-9c2d-000000000001")
print(status.module, status.status, status.reference, status.history)
print(status.raw.get("operation"), status.raw.get("error"))
JSON
{
  "transaction_id": "a1b2c3d4-0000-4a1b-9c2d-000000000001",
  "module": "credits",
  "operation": "application",
  "status": "PROCESSING",
  "reference": "sbx-E-2cf1b69c533d",
  "history": ["received", "submitted"],
  "data": {"application_id": "sbx-E-2cf1b69c533d", "status": "RECEIVED"},
  "error": null,
  "created_at": "2026-11-02T14:03:11.204Z",
  "updated_at": "2026-11-02T14:03:11.612Z"
}

You only see your entity’s transactions: a transaction_id from another tenant (or one that doesn’t exist) responds 404.

Wait for a result without webhooks #

The recommended approach is to react to webhooks, but if a batch process needs to confirm its own writes you can poll with increasing backoff until the state is no longer PROCESSING. Keep in mind that a submission stays in PROCESSING on purpose: only wait this way for operations that finish (decisions, disbursements, collections, loads).

import time

def esperar(tx_id: str, intentos: int = 6) -> str:
    espera = 1.0
    for _ in range(intentos):
        st = client.transactions.get(tx_id)
        if st.status != "PROCESSING":
            return st.status
        time.sleep(espera)
        espera *= 2
    return "PROCESSING"

What if you never received the transaction_id?

If the write was cut off before the response arrived, you have no transaction_id to query. Resend the same request with the same Idempotency-Key: the gateway returns the original response (with its transaction_id and idempotent_replay: true) or, if it’s still in progress, a 409 you can retry. See Idempotency.

Validate the key and its scopes #

GET/v1/gateway/health/ doesn’t touch the engine: it confirms the key is valid and returns the tenant it belongs to and the list of scopes. Use it at two moments:

  1. When your service starts: check that the key has every scope your integration needs and fail fast (with a clear message) if any is missing, instead of finding out through a 403 on an affiliate’s first operation.
  2. As a monitoring health check: a 200 tells you the gateway is responding and that your key is still valid (it hasn’t been revoked or rotated).
from compense_gateway import AuthError

REQUERIDOS = {"credits:submit", "credits:read", "loans:read", "loans:collect"}

try:
    health = client.system.health()
except AuthError as e:
    raise SystemExit(f"Invalid or revoked key ({e.status}, trace_id={e.trace_id})")

faltan = REQUERIDOS - set(health.scopes)
if faltan:
    raise SystemExit(f"The key for {health.tenant} is missing scopes: {sorted(faltan)}")
print("OK", health.tenant, health.status)
ResponseMeaning
200Valid key: check tenant and scopes.
401The X-API-Key header is missing.
403Invalid key, revoked key, key from another environment (a sandbox key against production or vice versa) or an IP outside the key’s allowlist (title: IP not allowed).

For monitoring, one request per minute is enough. A 200 doesn’t guarantee that a module’s engine is up (this endpoint doesn’t touch it): for that, watch the 502/504 responses of your real operations and the rate limits on your writes.

trace_id and X-Request-ID #

Every gateway response carries the X-Request-ID header. On errors, the application/problem+json body carries the same value as trace_id: that’s what you should quote to support, because it lets them find the exact cause in the gateway’s logs (which are never leaked in the response).

  • The SDKs generate an X-Request-ID for each logical operation and keep it the same across all of their automatic retries; in exceptions they expose it as trace_id (Python), traceId (TypeScript, PHP), TraceId (.NET) and TraceID (Go).
  • With cURL or your own HTTP client you can send your own X-Request-ID (for example, the request id in your system) and the gateway returns it.
  • In a successful response, your correlation key is the envelope’s transaction_id.

Always log transaction_id, trace_id (if there was an error), your external_reference and the Idempotency-Key together: with those four pieces of data any support case gets resolved in minutes. More in Errors and Support.

What’s next #