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

# Send an approved mailpiece

> Purchase an individually approved letter or postcard

## Overview

Buys the mailpiece a person approved: the quote and its approval, at the approved price. Paid from credits, or by x402. Returns `202` with the order. Read the order for its status and the signed receipt, signed on acceptance, cancellation or failure, not delivery.

The `Idempotency-Key` header is required and never expires. Save the key before you call. The same key with the same body replays the original order with the `Idempotent-Replay: true` header and sends nothing. After a timeout or a lost response, [Recover](https://docs.oneshotagent.com/api-reference/physical-mail/recover.md) finds the order by that key instead of sending again.

> [!WARNING]
>
> A piece that reached printing cannot be recalled. Approve from the approver's handler, never automatically after a preview.

## Authentication

`X-Agent-ID` plus a signed `x-agent-proof` with `scope: "submit"`, or an access token. See [proof authentication](https://docs.oneshotagent.com/sdk/overview.md#read-authentication). An access-token session pays from credits only and cannot carry a payment header. 60 requests a minute per agent.

## Headers

**Idempotency-Key** `string` *required*

1 to 255 characters of `[A-Za-z0-9_.:-]`. The same key with a different body returns `422 idempotency_key_reuse`.

**X-Max-Cost-USDC** `string`

Refuses the send with `400 max_cost_exceeded` when the quote's `total_usdc` is above it.

**X-Approval-Id** `string`

The `approval_id` from a `403 approval_required` response, once approved. Repeating the call with it releases the send the [action policy](https://docs.oneshotagent.com/api-reference/approvals/overview) held. Not the quote's `approval_id`.

## Request Body

**quote\_id** `string` *required*

Quote ID from the preview

**approval\_id** `string` *required*

Approval ID from [Approve](https://docs.oneshotagent.com/api-reference/physical-mail/approve.md)

### Audit fields (optional)

**memo** `string`

Reason for this send, stored on the receipt for audit. Max 1000 characters: a longer memo is rejected with `400 invalid_input` before anything is held or charged, not truncated. See [Audit Trail](https://docs.oneshotagent.com/sdk/audit-context.md).

**decisionContext** `object`

Decision metadata for supervisor and auditor agents, stored with the receipt. Open schema; known fields `goal`, `goalId`, `alternatives` (string\[]) and `confidence` (0-1). Extra keys are stored verbatim.

## Response

`202` with the order. Its fields are listed on [Get an order](https://docs.oneshotagent.com/api-reference/physical-mail/order.md#response). A replay returns `200` with the same order.

```bash cURL theme={null}
curl -X POST https://win.oneshotagent.com/v1/tools/physical-mail/send \
  -H "Content-Type: application/json" \
  -H "X-Agent-ID: 0xYourWalletAddress" \
  -H "x-agent-proof: <base64 signed proof, scope=submit>" \
  -H "Idempotency-Key: thank-you-2026-q3-jane-doe" \
  -d '{
    "quote_id": "00000000-0000-4000-8000-000000000000",
    "approval_id": "00000000-0000-4000-8000-000000000000",
    "memo": "Q3 customer thank-you letter"
  }'
```

```typescript SDK theme={null}
import { OneShot } from "@oneshot-agent/sdk";

const agent = new OneShot({ privateKey: process.env.AGENT_PRIVATE_KEY });

const order = await agent.physicalMail.send({
  quote_id: approval.quote_id,
  approval_id: approval.approval_id,
  idempotencyKey: savedKey, // saved before this call
  maxCost: Number(quote.total_usdc),
  memo: "Q3 customer thank-you letter",
});
```

```python Python theme={null}
order = client.physical_mail.send(
    quote_id=approval["quote_id"],
    approval_id=approval["approval_id"],
    idempotency_key=saved_key,
    max_cost=float(quote["total_usdc"]),
    memo="Q3 customer thank-you letter",
)
```

```json Accepted (202) theme={null}
{
  "order_id": "00000000-0000-4000-8000-000000000000",
  "quote_id": "00000000-0000-4000-8000-000000000000",
  "order_status": "pending",
  "payment_status": "pending",
  "fulfillment_status": "pending",
  "receipt_id": "rcpt_...",
  "signed_receipt": null,
  "total_usdc": "<the quote's total_usdc>",
  "idempotency_key": "thank-you-2026-q3-jane-doe",
  "events": [],
  "cancellation_requested": false,
  "cancellation_error": null,
  "cancel_before": null,
  "refunded_at": null,
  "delivery_proves_readership": false
}
```

```json Insufficient Credits (402) theme={null}
{
  "error": "insufficient_credits",
  "required_usdc": "<the quote's total_usdc>",
  "credits_balance": "<your credits balance>",
  "shortfall_usdc": "<required_usdc minus credits_balance>",
  "quote_id": "00000000-0000-4000-8000-000000000000"
}
```

```json Held by Action Policy (403) theme={null}
{
  "error": "approval_required",
  "status": "pending_approval",
  "message": "This physical_mail call needs a human approval (rule <rule id>). It was not run or charged. Retry the same call with X-Approval-Id once approved.",
  "approval_id": "<policy approval id>",
  "approval": { "...": "the pending approval" }
}
```

```json Approval Required or Expired (409) theme={null}
{
  "error": "approval_required_or_expired"
}
```

## Errors

| Code | When |
| - | - |
| `400 invalid_input` | Missing `Idempotency-Key`, a bad body, or a `memo` over 1000 characters |
| `400 max_cost_exceeded` | Above `X-Max-Cost-USDC` |
| `402 payment_required` | Pay by x402; a wallet session without enough credits |
| `402 insufficient_credits` | An access-token session without enough credits |
| `403 approval_required` | Held by a `require_approval` policy rule; nothing sent or charged. Repeat the call, same `Idempotency-Key`, with `X-Approval-Id` once approved |
| `403 action_denied_by_policy` | Refused by a `deny` policy rule; not resumable |
| `409 approval_required_or_expired` | No current approval for this quote |
| `409 quote_already_ordered` | The quote already has an order |
| `422 idempotency_key_reuse` | Same key, different body |
| `503 moderation_unavailable` | Unsent, nothing charged; retry after `Retry-After` |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.