Mono Colombia

Errors and retries

HTTP status codes, the Mono error envelope, and how to retry failed requests safely.

Every Mono response carries an HTTP status code. Successful calls return 2xx; failures return 4xx (your request was wrong) or 5xx (something went wrong on Mono's side). This page documents the status codes Mono uses, the error envelope you can expect on every failure, and the retry policy that turns transient failures into eventual success without duplicating side effects.

Rate limits are not yet enforced uniformly across every Mono endpoint, but 429 will start appearing as we tighten them. The retry rules below describe the contract you should code against today so that your integration already behaves correctly when limits kick in.

HTTP status codes

StatusMeaningRetryable?
200 (OK)Request completed successfully.n/a
201 (Created)A new resource was created.n/a
204 (NoContent)Request succeeded; no body to return.n/a
400 (BadRequest)Malformed JSON, missing field, or non-compliant header. The request itself is wrong.No
401 (Unauthorized)Missing or invalid Authorization header.No (fix credential)
403 (Forbidden)Authenticated, but lacking scope for this resource.No (fix scope)
404 (NotFound)Resource does not exist.No
408 (RequestTimeout)Mono did not receive the full request in time.Yes, with backoff
409 (Conflict)Idempotency-key collision or state conflict — see Idempotency Keys.No (application logic)
422 (UnprocessableEntity)Request structure is valid, but the content is rejected (business validation).No
429 (TooManyRequests)Rate limit exceeded. Honor the Retry-After header.Yes, after Retry-After
500 (InternalServerError)Unexpected server-side failure.Yes, with backoff
502, 503, 504Upstream/gateway transient failures.Yes, with backoff

Error envelope

Every failure response carries a standard JSON envelope so clients can parse errors uniformly:

{
  "code": "400 Bad Request",
  "errors": [
    {
      "error_code": "item_not_found",
      "message": "Item doesn't exist",
      "path": "#/item/id",
      "url": "https://api.mono.co/docs#errors"
    }
  ],
  "id": "log_7MkWaFqvfosB8fzHhb1Eql",
  "message": "Malformed request"
}

Fields:

  • code — the HTTP status and reason phrase (e.g., "404 Not Found").
  • errors[] — one entry per validation failure. Useful when a single request has multiple invalid fields.
    • error_code — stable, machine-readable code for this specific failure.
    • message — short human description.
    • path — JSON pointer into the request payload identifying the offending field.
    • url — link to extended documentation for this error code, when available.
  • id — Mono-generated request identifier. Include this when contacting support — it lets us locate the exact request in our logs.
  • message — top-level human description suitable for surfacing to operators (not end users).

429 and other rate-limit responses use the same envelope with error_code: "rate_limited".

When to retry

Retry only on transient failures, never on application errors:

ResponseRetry?Why
429Yes, after Retry-After.Transient. Request was not processed; nothing changed on Mono's side.
408, 500504Yes, with exponential backoff.Transient. Request may or may not have been processed — idempotency key protects.
400, 422No.The request itself is malformed. Retrying will not change the answer.
401, 403No.Credentials or scope problem. Fix the auth, then send a fresh request.
404, 409No.Resource missing or state conflict. Application logic must react.

Four rules apply to every retry:

  1. Honor Retry-After. If the header is present (always on 429, sometimes on 503), sleep for at least that many seconds before retrying. Do not retry sooner.
  2. Use exponential backoff with jitter when Retry-After is absent. Start at 1 second, double on each subsequent failure, add 0–500 ms of random jitter, and cap the delay at 30 seconds. Jitter prevents the thundering-herd problem where many clients retry in lockstep.
  3. Cap the retry budget. Up to 5 retries per logical operation is a reasonable default. After that, surface the failure — retrying forever just shifts the problem.
  4. Reuse the idempotency key. Every retry must carry the same X-Idempotency-Key as the original attempt. Otherwise, a successful retry after an ambiguous 5xx could double-charge a customer.

A minimal retry loop in Python:

import random
import time

import httpx

RETRYABLE_STATUSES = {408, 429, 500, 502, 503, 504}

def request_with_backoff(client, method, url, **kwargs, max_retries=5):
    delay = 1.0
    for attempt in range(max_retries + 1):
        response = client.request(method, url, **kwargs)
        if response.status_code not in RETRYABLE_STATUSES:
            return response

        retry_after = response.headers.get("Retry-After")
        wait = float(retry_after) if retry_after else delay + random.uniform(0, 0.5)
        time.sleep(min(wait, 30.0))
        delay = min(delay * 2, 30.0)

    return response  # last response, still failing

Designing for resilience up front

Even before rate limits become strict, three habits keep bursty workloads out of trouble:

  • Spread bursty work. Batch jobs that fan out thousands of calls — payroll runs, end-of-day reconciliation, mass card issuance — should be paced from the client side rather than fired in parallel. A few requests per second sustained beats a thousand at once and a recovery window afterward.
  • Cache reads. Reference data (catalogs, fee schedules, capability lookups) rarely changes inside a single request cycle. Cache the response and only hit Mono when the cache misses.
  • Separate sync from async paths. A user-facing checkout call should not share its budget with a nightly export. Use separate API keys for separate workloads when possible so a runaway batch job cannot exhaust the limit a real customer needs.

Common mistakes

MistakeSymptomFix
Retrying immediately on 429.Repeated 429s and longer total recovery time.Sleep for at least Retry-After seconds before the next attempt.
Ignoring the Retry-After header.Retries land before the limit has reset.Treat Retry-After as authoritative when present.
Retrying without an idempotency key.Duplicate transfers once the retry eventually succeeds.Generate the key once, send it on every attempt.
Retrying 400/422 as if transient.Wasted attempts; the same error returns every time.Only retry on 408, 429, and 5xx.
No retry cap.Stuck workers, blocked queues, alert fatigue.Cap at a small number of retries (5 is a reasonable default) and surface up.
Sharing a key across services and traffic classes.One noisy batch job rate-limits user-facing checkout traffic.Issue separate API keys for separate workloads.
Dropping the id field when reporting an issue.Support cannot find the request in logs.Always include id from the error envelope when you escalate.

Next steps

  • Idempotency Keys — required on every retried write.
  • Authentication — credential handling for the 401 and 403 cases.
  • Webhooks — Mono's own retry policy for the events it sends to your system.

On this page