Admin settlements

Configuration and the manual resolution tray for the LiquidaciĂłn de Cisterneros (driver settlement) module. Paths are relative to the /api/v1 prefix; these are back-office endpoints requiring a super-admin token.

Requires a super-admin bearer token (see Admin auth). Super-admin only.

See Common errors.

Endpoints

Method URI Access Description
GET /admin/settlements/config Super admin Read the settlement config
PATCH /admin/settlements/config Super admin Update the settlement config
GET /admin/settlements/receipts Super admin List receipts of any status
GET /admin/settlements/receipts/{id} Super admin Full detail of one receipt
GET /admin/settlements/tray Super admin List FAILED receipts awaiting manual resolution
POST /admin/settlements/receipts/{id}/retry Super admin Re-disperse a failed receipt (new payout order)
POST /admin/settlements/receipts/{id}/mark-external Super admin Mark a failed receipt paid outside AgĂĽita
POST /admin/settlements/receipts/{id}/annul Super admin Annul a failed receipt and release the reservation

The config object

{
  "cutoff_enabled": false,
  "cutoff_time": "02:00",
  "payout_enabled": false,
  "payout_time": "10:00",
  "min_balance_usd": 0.5,
  "min_balance_cents": 50,
  "active_days": [false, true, false, true, false, true, false],
  "iva_prc": 16,
  "timezone": "-04:00"
}
Field Type Notes
cutoff_enabled boolean Whether the receipt-generation (corte) process is on.
cutoff_time string H:i Wall-clock time (in timezone) the corte runs.
payout_enabled boolean Whether the dispersion (pago) process is on.
payout_time string H:i Wall-clock time (in timezone) the pago runs.
min_balance_usd number Minimum settleable balance, in USD.
min_balance_cents integer Same minimum, in USD cents.
active_days array[7] boolean Days both processes run, indexed by weekday 0=Sun … 6=Sat.
iva_prc number IVA percentage applied to AgĂĽita's commission.
timezone string UTC offset (±HH:MM) or IANA identifier for the wall-clock times.

GET /admin/settlements/config

Response 200 — { "config": { … } } (see the config object).


PATCH /admin/settlements/config

Partial update; every field is optional. Only the sent fields change; unsent fields keep their stored value.

Request body

Field Type Required Rules
cutoff_enabled boolean no —
cutoff_time string no H:i (24h)
payout_enabled boolean no —
payout_time string no H:i (24h); must be strictly after the effective cutoff_time
min_balance number no USD; ≥ 0.5
active_days array no exactly 7 booleans
active_days.* boolean no —
iva_prc number no 0–100
timezone string no a ±HH:MM UTC offset or a valid IANA identifier

The payout_time > cutoff_time invariant compares the effective values: a field not sent uses its current stored value.

Response 200

{
  "config": { "…": "see the config object" },
  "audit_entry": {
    "key": "settlement_config",
    "old_values": { "cutoff_enabled": false },
    "new_values": { "cutoff_enabled": true },
    "created_at": "2026-07-29T19:16:35.000000Z"
  }
}

audit_entry records only the fields that changed (previous and new values). A no-op update (all values equal the stored config) returns "audit_entry": null and writes no audit row.

Errors

Status Body When
422 { "error": "ValidationError", "details": { "fieldErrors": { "payout_time": ["…"] } } } payout_time is not strictly after the effective cutoff_time, or any field fails its rules.

GET /admin/settlements/receipts

Page-based list of settlement receipts, any status, newest first.

Query params

Param Type Required Rules
status string no one of GENERATED, IN_PAYMENT, PAID, FAILED, PAID_EXTERNALLY, VOIDED
driver_id integer no —
fiscal_period string no —
date_from date no filters on issued_at
date_to date no filters on issued_at; must be ≥ date_from
q string no ≤ 80; matches receipt_number or beneficiary_name
limit integer no 1–100 (default 25)
page integer no ≥ 1

Response 200

{
  "items": [
    {
      "id": 12,
      "receipt_number": "2026-0000042",
      "driver_id": 5,
      "driver_name": "Cisternero Test",
      "fiscal_period": "2026-07",
      "status": "PAID",
      "payout_method": "PAGO_MOVIL",
      "net_usd": 85.0,
      "payable_usd": 85.0,
      "total_paid_bs_cents": 340000,
      "bank_reference": "FAKE-REF-13",
      "failure_reason": null,
      "issued_at": "2026-07-31T10:00:00+00:00",
      "paid_at": "2026-07-31T14:05:00+00:00",
      "voided_at": null,
      "created_at": "2026-07-31T10:00:00+00:00"
    }
  ],
  "meta": { "total": 1, "per_page": 25, "current_page": 1, "last_page": 1 }
}

GET /admin/settlements/receipts/{id}

Full detail of one receipt: the breakdown totals (USD via _usd, Bs raw via _bs_cents), the frozen beneficiary/payout snapshot, its lines, its payout orders (each with its attempts), and its manual resolution-tray audit trail.

Response 200

{
  "receipt": {
    "id": 12,
    "receipt_number": "2026-0000042",
    "driver_id": 5,
    "city_id": 1,
    "fiscal_period": "2026-07",
    "status": "PAID",
    "failure_reason": null,
    "bank_reference": "FAKE-REF-13",
    "cutoff_from": null,
    "cutoff_to": "2026-07-31T10:00:00+00:00",
    "issued_at": "2026-07-31T10:00:00+00:00",
    "subtotal_usd": 100.0,
    "discount_usd": 0,
    "orders_total_usd": 100.0,
    "commission_usd": -15.0,
    "net_usd": 85.0,
    "payable_usd": 85.0,
    "net_bs_cents": 340000,
    "total_paid_bs_cents": 340000,
    "remuneration_base_usd": 15.0,
    "remuneration_usd": 15.0,
    "iva_prc": "16.00",
    "iva_usd": 2.4,
    "remuneration_total_usd": 17.4,
    "beneficiary_name": "Cisternero Test",
    "beneficiary_id_number": "12345678",
    "payout_method": "PAGO_MOVIL",
    "payout_account": "+584241234567",
    "beneficiary_bank_code": null,
    "beneficiary_account_type": null,
    "paid_at": "2026-07-31T14:05:00+00:00",
    "voided_at": null,
    "created_at": "2026-07-31T10:00:00+00:00",
    "lines": [
      {
        "id": 1, "line_number": 1, "type": "ORDER", "order_id": 40,
        "order_date": "2026-07-30", "description": "…",
        "subtotal_usd": 50.0, "discount_usd": 0, "order_total_usd": 50.0,
        "commission_usd": -7.5, "net_usd": 42.5,
        "exchange_rate_amount": "40.000", "total_bs_cents": 170000
      }
    ],
    "payout_orders": [
      {
        "id": 13, "idempotency_key": "settlement_payout:12", "method": "PAGO_MOVIL",
        "provider": "sypago", "amount_bs_cents": 340000, "status": "CONFIRMED",
        "reference": "FAKE-REF-13", "transaction_id": null, "attempt_count": 1,
        "last_error": null, "sent_at": "…", "confirmed_at": "…", "failed_at": null,
        "attempts": [
          { "attempt_no": 1, "provider_code": "OK", "result": "confirmed", "created_at": "…" }
        ]
      }
    ],
    "receipt_changes": [
      {
        "action": "retry", "admin_id": 3, "admin_name": "Ada Admin",
        "from_status": "FAILED", "to_status": "PAID", "reason": null, "created_at": "…"
      }
    ]
  }
}

lines[].type is ORDER for a delivered order or ADJUSTMENT for anything else in the window: manual wallet adjustments and promotion movements (PROMO_BONUS, PROMO_REIMBURSEMENT). ADJUSTMENT lines carry order_id: null and subtotal_usd, order_total_usd and commission_usd at 0, so they never enter the receipt's commission or IVA base.

Errors

Status Body When
404 { "error": "ReceiptNotFound" } No receipt with that id.

Manual resolution tray

Receipts that dispersion (pago) or reconciliation left in FAILED land here for a super-admin to resolve. Each action is audited (status transition + reason + admin).

failure_kind classifies why a receipt is FAILED:

failure_kind Meaning
business The provider rejected the payment (money confirmed not sent). Safe to retry.
deadline Reconciliation timed out (failure_reason = "reconcile_deadline_exceeded"); the money status is unknown. Retry is blocked — use mark-external or annul.

GET /admin/settlements/tray

Page-based list of FAILED receipts, newest first.

Query params

Param Type Required Rules
failure_kind string no business or deadline
limit integer no 1–100 (default 25)
page integer no ≥ 1

Response 200

{
  "items": [
    {
      "id": 12,
      "receipt_number": "2026-0000042",
      "driver_id": 5,
      "driver_name": "Cisternero Test",
      "payout_method": "PAGO_MOVIL",
      "net_usd": 85.0,
      "payable_usd": 85.0,
      "total_paid_bs_cents": 340000,
      "failure_reason": "Cuenta inválida",
      "failure_kind": "business",
      "provider_code": "RJCT",
      "attempts": 1,
      "failed_at": "2026-07-31T14:05:00+00:00",
      "created_at": "2026-07-31T10:00:00+00:00"
    }
  ],
  "meta": { "total": 1, "per_page": 25, "current_page": 1, "last_page": 1 }
}

Fields per item: id, receipt_number, driver_id, driver_name, payout_method (PAGO_MOVIL|BANK_TRANSFER|null), net_usd, payable_usd, total_paid_bs_cents (bolĂ­vares), failure_reason, failure_kind, provider_code (latest attempt, nullable), attempts (count on the latest order), failed_at (nullable), created_at.


POST /admin/settlements/receipts/{id}/retry

Re-disperse a FAILED receipt. Mints a new payout order (fresh idempotency key); the old order is never re-opened. No request body.

Response 200

{
  "receipt": { "id": 12, "receipt_number": "2026-0000042", "status": "PAID", "bank_reference": "FAKE-REF-13", "failure_reason": null, "paid_at": "…", "voided_at": null },
  "order": { "id": 13, "status": "CONFIRMED", "idempotency_key": "settlement_payout:12:r1" },
  "audit_entry": { "action": "retry", "from_status": "FAILED", "to_status": "PAID", "reason": null, "created_at": "…" }
}

receipt.status / order.status reflect the dispersion outcome: a synchronous confirm → PAID / CONFIRMED; an acknowledgement → IN_PAYMENT / SENT.

Errors

Status Body When
404 { "error": "ReceiptNotFound" } No receipt with that id.
409 { "error": "ReceiptNotFailed" } The receipt is not in FAILED.
409 { "error": "RetryUnsafeDeadline" } The receipt failed by reconciliation deadline (failure_kind = "deadline"); use mark-external or annul.

POST /admin/settlements/receipts/{id}/mark-external

Record that a FAILED receipt was paid to the driver outside AgĂĽita. Settles the wallet and sets status = PAID_EXTERNALLY with the external reference in bank_reference.

Request body

Field Type Required Rules
reason string yes ≤ 500
reference string yes ≤ 191 (external payment reference)

Response 200 — { "receipt": { …, "status": "PAID_EXTERNALLY", "bank_reference": "MANUAL-REF-123" }, "audit_entry": { "action": "mark_external", … } }

Errors

Status Body When
404 { "error": "ReceiptNotFound" } No receipt with that id.
409 { "error": "ReceiptNotFailed" } The receipt is not in FAILED.
422 { "error": "ValidationError", "details": { "fieldErrors": { … } } } reason or reference missing/invalid.

POST /admin/settlements/receipts/{id}/annul

Annul a FAILED receipt: status = VOIDED and the reserved payout is released back to the driver's withdrawable balance (no money is sent).

Request body

Field Type Required Rules
reason string yes ≤ 500

Response 200 — { "receipt": { …, "status": "VOIDED", "voided_at": "…" }, "audit_entry": { "action": "annul", … } }

Errors

Status Body When
404 { "error": "ReceiptNotFound" } No receipt with that id.
409 { "error": "ReceiptNotFailed" } The receipt is not in FAILED.
422 { "error": "ValidationError", "details": { "fieldErrors": { "reason": ["…"] } } } reason missing/invalid.