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

# Machine Payments

> Let an AI agent pay per call, with no API key and no signup

FastDrop exposes a [Machine Payments Protocol](https://paymentauth.org) endpoint at `POST /mpp/classify`. An agent that holds a payment credential but no FastDrop account can classify a video by paying for it directly — no signup, no key to store, no human in the loop.

This is a separate surface from the keyed developer API. If you have an `fd_live_...` key, use [`POST /v1/classify`](/api-reference/classify-video) instead; it is cheaper per credit and gives you the whole API rather than one endpoint.

<Note>
  One endpoint, but not one capability. `/mpp/classify` accepts the same
  `capabilities` array as the keyed API — `classify`, `thumbnails`,
  `diagnostics`, `duplicates`, `clips`, `transcribe` and the `full_pipeline`
  bundle — so a transcription-only call is just
  `{"capabilities": ["transcribe"]}`. Batches and search still require a key.
</Note>

## What you get for a payment

FastDrop sells **credits in packs**, not individual calls. One classification costs 2-6 credits depending on the processing tier, which works out to roughly $0.12-$0.36 — below Stripe's \$0.50 minimum for a shared payment token, so charging per call is not possible on the card rail at these prices.

A pack is **10 credits for \$0.60**. You spend what the call costs and keep the rest, held against the agent identity in your payment token. A returning agent with leftover balance is served with no payment step at all — the credential still identifies you, but no money moves.

## The exchange

```
POST /mpp/classify                              →  402 + WWW-Authenticate: Payment ...
POST /mpp/classify + Authorization: Payment ... →  202 + Payment-Receipt: ...
```

### 1. Ask, and get quoted

Send the classification request with no `Authorization` header:

```bash theme={null}
curl -i -X POST https://api.fastdrop.io/mpp/classify \
  -H "Content-Type: application/json" \
  -d '{
    "video_url": "https://example.com/clip.mp4",
    "capabilities": ["classify"],
    "processing_tier": "basic"
  }'
```

You get a `402 Payment Required` with an MPP challenge in `WWW-Authenticate` and a body that names the price:

```json theme={null}
{
  "error": {
    "code": "payment_required",
    "message": "This request costs 2 credits. Purchase 10 credits for $0.60 and retry with the payment credential.",
    "details": {
      "credits_required": 2,
      "credit_pack_size": 10,
      "amount_usd": "0.60"
    }
  }
}
```

The quote is returned before the request body is validated, so a discovery probe with an empty or unfamiliar body still learns the price. The pack price does not depend on what you asked for.

You can also read the price straight out of the OpenAPI document — the operation carries an `x-payment-info` extension — and skip this round trip entirely.

### 2. Pay, and get served

Retry with a Stripe shared payment token in an `Authorization: Payment` credential built from the challenge:

```bash theme={null}
curl -i -X POST https://api.fastdrop.io/mpp/classify \
  -H "Content-Type: application/json" \
  -H "Authorization: Payment <credential>" \
  -d '{
    "video_url": "https://example.com/clip.mp4",
    "capabilities": ["classify"],
    "processing_tier": "basic",
    "webhook_url": "https://your-agent.example.com/hooks/fastdrop"
  }'
```

A `202 Accepted` comes back with a `Payment-Receipt` header and:

```json theme={null}
{
  "job_id": "9f1c...",
  "status": "queued",
  "credits_charged": 2,
  "credits_remaining": 8,
  "paid_this_request": true,
  "job_token": "kQ8f...",
  "poll_url": "/mpp/classify/9f1c...?token=kQ8f..."
}
```

`paid_this_request` is `false` when the call was covered by credits left over from an earlier pack. The receipt is attached either way — a request served from balance was still paid for, just earlier.

## Collecting the result

<Note>
  **Keep the `job_token`.** It is the only credential that will get you this
  result — a machine-payments caller is never issued an API key, so the keyed
  `GET /api/v1/classify/{job_id}` is closed to you.
</Note>

Poll the URL from the response, or construct it yourself:

```bash theme={null}
curl "https://api.fastdrop.io/mpp/classify/9f1c...?token=kQ8f..."
```

The token also works as a bearer credential, if you would rather keep it out of a URL:

```bash theme={null}
curl https://api.fastdrop.io/mpp/classify/9f1c... \
  -H "Authorization: Bearer kQ8f..."
```

The token is scoped to that one job and does not expire. It will not open any other job, and no other job's token will open this one.

The response carries `status` (`queued`, `downloading`, `processing`, `completed`, `failed`), `progress`, and — once `status` is `completed` — the full `results` object. On `failed` you get an `error` with the reason, and the credits are returned to your balance automatically.

Alternatively, set `webhook_url` on the request and FastDrop posts the finished classification to it instead of making you poll. The payload and signature are identical to the keyed API's; see [Webhooks](/guides/webhooks).

## Identity and rate limits

Your Stripe token carries an agent profile, and FastDrop keys everything to it: your credit balance, your usage history, and a rate limit of 120 requests per minute. Two calls with tokens from the same agent platform share one balance. This happens server-side — there is nothing for you to register or manage.

## Errors

| Status | Code | Meaning |
| - | - | - |
| `402` | `payment_required` | No credential sent. The challenge is in `WWW-Authenticate`. |
| `402` | *(protocol codes)* | The credential was malformed, expired, or scoped to a different request. A fresh challenge is attached — pay again and retry. |
| `402` | `payment_failed` | Stripe declined the settlement. |
| `400` | `invalid_payment_token` | The token could not be retrieved, or carries no agent profile. |
| `400` | `insufficient_authorization` | The token authorizes less than the quoted amount. |
| `400` | `invalid_capabilities`, `invalid_request`, `invalid_url` | Same validation as the keyed endpoint. Checked after the credential but before any money moves. |
| `400` | `video_too_long` | Over the 60-minute limit. Only raised when you send `duration_seconds`; otherwise the job fails during processing and the credits are returned. |
| `401` | `missing_job_token`, `invalid_job_token` | Polling without the `job_token` from the 202, or with one minted for a different job. |
| `503` | `machine_payments_unavailable` | Machine payments are not configured on this deployment. |

Nothing in the `400` group costs you a payment: the body is validated, the token inspected, and the spend ceiling checked before settlement is attempted.

## Not the MCP server

`/mpp` and `/mcp` are one transposed letter apart and unrelated. [`/mcp`](/guides/mcp-setup) is the Model Context Protocol tool server, which authenticates with an API key. `/mpp` is machine payments. Paying for MCP tool calls the same way is prototyped but disabled — the JSON-RPC error code MPP uses for payment challenges collides with one the MCP specification assigns to something else, and that needs resolving upstream before it can be turned on.


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