---
title: "Approve or decline an agent top-up request (human, dashboard session)"
method: POST
path: "/api/billing/topup-requests/{id}/resolve"
tags: ["Infrastructure"]
---

# Approve or decline an agent top-up request (human, dashboard session)

`POST /api/billing/topup-requests/{id}/resolve`

## Request

- Base URL: `https://www.apier.no`
- URL: `https://www.apier.no/api/billing/topup-requests/{id}/resolve`
- Auth: API key in cookie `sb-access-token`

The HUMAN approval leg of agent-initiated top-up requests. Session-cookie-authed (browser dashboard surface — NOT Bearer/agent-facing) and intentionally outside the /api/v1 namespace next to /api/billing/checkout, so the API-key auth model, response provenance, and scope rules do not apply. Called only by the site's own /dashboard/billing page. `decision: "approve"` atomically claims the still-pending, unexpired request (a single compare-and-swap — a second approve, an approve after the 24-hour expiry, and concurrent resolutions all fail with 409), re-checks the requested amount against the LIVE adjustable ceiling (a ceiling lowered after the request was created blocks approval), verifies the requesting key is still active, and then drives the EXISTING credit-checkout path with the request's key and amount — returning the Stripe Checkout `url` for the browser to follow. The human completes (or abandons) payment on Stripe's hosted page exactly as on the direct top-up path; credits accrue only via the deduplicated webhook. `decision: "decline"` resolves the request without touching any Stripe code path. Cross-tenant resolution is impossible: every read and the claim itself are pinned to the session consumer, and an unknown id, another consumer's request, and an already-resolved request are indistinguishable (uniform 409).

## Path parameters

- `id` string, uuid, required

## Request body

- object
  - `decision` 'approve' | 'decline', required: approve → claim the request and start the Stripe checkout for its amount; decline → resolve it with no payment and no Stripe interaction.

## Response `200`

Resolved. On approve, `url` is the Stripe-hosted Checkout page for the requested amount — the browser follows it and the human completes payment there (the session reference is stored on the request as `checkout_session_ref`). On decline, no `url` is returned and no payment surface was ever touched.

- object
  - `id` string, uuid, required
  - `status` 'approved' | 'declined', required
  - `url` string, uri: Present only on approve — the Stripe Checkout URL to redirect the human to.

## Other responses

- `400`: VALIDATION_FAILED — non-UUID id, malformed JSON, or a decision other than approve/decline.
- `401`: No authenticated dashboard session.
- `409`: TOPUP_REQUEST_NOT_PENDING (already resolved, expired, or not found on this account — uniform by design), TOPUP_CEILING_EXCEEDED (the amount now exceeds a lowered ceiling; the request STAYS pending and carries `requested_ore` + `ceiling_ore`), or TOPUP_KEY_REVOKED (the requesting key is no longer active).
- `503`: BILLING_NOT_LIVE (beta — billing dark), BILLING_NOT_CONFIGURED (no Stripe in this environment), or TOPUP_CEILING_UNAVAILABLE (ceiling unreadable; nothing was approved). All checked BEFORE the one-shot approve claim, so none of them burns the request.

---

[API](https://skmtc.dev/apier/apis/apier-no-api.md) · [All operations](https://skmtc.dev/apier/apis/apier-no-api/llms.txt) · [OpenAPI document](https://skmtc.dev/apier/apis/apier-no-api/revisions/9ec19bc87311?raw)
