> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flashnet.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Approval Flows

> Resolve ZeroConf offers

When an eligible Bitcoin L1 deposit receives an instant-credit offer, the order enters `awaiting_approval` with a pending `zeroconfOffer`. Use the endpoints below to accept or decline. See [Order Lifecycle](/products/orchestration/order-lifecycle) for the full state machine and [ZeroConf](/products/orchestration/zeroconf) for how instant credit works.

<Note>
  Market-move repricing is handled automatically. If post-deposit execution would exceed the quote's `slippageBps`, the order refunds without partner action. There is no accept/decline step. See [Market moves and refunds](#market-moves-and-refunds) below.
</Note>

## POST /v1/orchestration/zeroconf/accept

Accept a pending ZeroConf offer and resume execution with instant credit.

Headers:

```bash theme={null}
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
```

Request:

```json theme={null}
{
  "orderId": "ord_..."
}
```

Rules:

* Order must be in `awaiting_approval` with a pending `zeroconfOffer`.
* The offer must not be expired.
* The API key must belong to the same partner as the order.
* Retrying the same request with the same `X-Idempotency-Key` is safe. A new accept request after the offer has already resolved returns an invalid state error.

Response:

```json theme={null}
{
  "orderId": "ord_...",
  "status": "processing"
}
```

## POST /v1/orchestration/zeroconf/decline

Decline a pending ZeroConf offer. The order waits for 1 on-chain confirmation before proceeding.

Headers:

```bash theme={null}
Authorization: Bearer fn_...
X-Idempotency-Key: <unique key>
```

Request:

```json theme={null}
{
  "orderId": "ord_...",
  "reason": "Optional partner reason"
}
```

Rules:

* Order must be in `awaiting_approval` with a pending `zeroconfOffer`.
* The API key must belong to the same partner as the order.

Response:

```json theme={null}
{
  "orderId": "ord_...",
  "status": "confirming"
}
```

After declining, the engine waits for 1 on-chain confirmation before proceeding.

## ZeroConf offer fields

When a Bitcoin L1 deposit receives a ZeroConf quote, the order includes a `zeroconfOffer` object:

```json theme={null}
{
  "zeroconfOffer": {
    "version": 1,
    "status": "pending",
    "quoteId": "zc_...",
    "sparkAddress": "spark1...",
    "txid": "<bitcoin txid>",
    "vout": 0,
    "depositSats": "250000",
    "instantSats": "245000",
    "holdbackSats": "0",
    "feeSats": "5000",
    "expiresAt": "2026-02-04T01:35:00.000Z",
    "offeredAt": "2026-02-04T01:30:00.000Z",
    "resolvedAt": null
  }
}
```

Fields:

* `status`: `pending`, `accepted`, `declined`, `expired`, or `confirmed` (confirmed means the Bitcoin tx reached 1-conf before the offer was resolved, bypassing the approval window)
* `depositSats`: total BTC deposited (sats)
* `instantSats`: amount credited instantly on acceptance (sats)
* `holdbackSats`: amount reserved for later confirmed credit (sats). Current generated offers use `"0"`.
* `feeSats`: difference between `depositSats` and `instantSats` from Spark's static-deposit quote
* `expiresAt`: offer expiry time (ISO 8601)
* `offeredAt`: when the offer was generated
* `resolvedAt`: when the offer was accepted, declined, or expired (`null` while pending)

`feeSats` is not the Flashnet orchestration platform fee. Platform pricing stays in the normal quote and order fields such as `feeBps`, `feeAmount`, and `totalFeeAmount`.

Read `feeSats` from each offer. Do not hard-code a fixed percentage or fixed sat amount.

The order status is `awaiting_approval` while `zeroconfOffer.status` is `pending`. Resolve with:

* `POST /v1/orchestration/zeroconf/accept` to accept and receive instant credit
* `POST /v1/orchestration/zeroconf/decline` to wait for 1 on-chain confirmation before proceeding

If the offer expires without a response, it is marked `expired` and the engine waits for 1 on-chain confirmation before proceeding.

## Market moves and refunds

If the pool moves against the quote between the deposit and execution, Orchestra refunds the order automatically. No partner action is required. The resulting order will have `status=refunding` and `errorCode=slippage_exceeded` before finalizing as `refunded`.

Late deposits (arriving after quote expiry) are always accepted and repriced at the live market rate at detection time, then executed against `slippageBps`. If the pool has moved too far, the normal `slippage_exceeded` refund applies.

Exact-out orders that can't be satisfied from the received amount refund with one of:

* `exact_out_insufficient_input`: the deposit was below `requiredAmountIn`
* `exact_out_input_above_max`: the deposit exceeded `maxAcceptedAmountIn`
* `exact_out_target_not_met`: the pool couldn't produce `targetAmountOut`

In all cases the webhook event is `order.refunding`. Partners should treat these as normal refund flows.
