# Get started

Prepare a business key and inspect account capabilities before creating a draft payment link.

> **Switched on resource by resource**
>
> Each `/v1` resource is switched on separately in production. A resource that is not switched on
> answers `503 temporarily_unavailable` — read it as "not available yet", not as an outage, and read
> `GET /v1/capabilities?product=<product>` for what your account can do. The examples below are not an onboarding
> guarantee. The [public MCP documentation](https://www.swaps.app/developers/skills/mcp-server) lists
> the published integration surfaces.

## 1. Obtain a business key

The account holder manages keys in Developers → API keys. Automatic issuance on signup is not promised. Supply an existing key through your client environment. **This guide needs a live key**: a `sk_test_` key runs only the operations [Test mode](/test-mode) lists — API-key, request-log and account reads on your test account, and webhook endpoints, webhook deliveries and the per-object event lists in the development project — and the payment-link calls below answer it `503 temporarily_unavailable`. Once enabled, read `GET /v1/capabilities?product=payouts` first, with a live key — `capabilities` is unavailable in test mode (see [Test mode](/test-mode)) — `product` is required, one of `payment_links`, `payouts`, `payroll`, `buy_sell`, `wallet_bank` — and proceed only when the account is authorized.

```
sk_live_51NxSupaLabs...9f2
```

Keys are shown once. Rotate a lost key from Developers → API keys — support cannot read it back to you.

## 2. Create a payment link

A link starts as a `draft`: nothing is charged and no rail goes live until you activate it. Every mutating request carries an `Idempotency-Key` so a retried call never double-creates the link — generate a fresh key per new link; reusing the same key within 24 hours replays the stored response instead of creating a second one — and `amount` is always a `Money` object — a minor-unit integer string plus `decimals`, never a float. An optional `Swaps-Version` header pins the call to a dated contract version; omit it and the key's own default applies, and every response echoes the version it was served under (see [Conventions](/conventions)).

Name where the money lands when you draft the link, or `activate` refuses it with `400 settlement_destination_required`. `settlement_destination` points to a bank account or crypto address you already saved in the address book. `/v1/address_book` takes a dashboard session or an agent key, never a business key, so save the destination once under Addresses in the dashboard. No dashboard screen shows its `adr_…` id today: read it from `GET /v1/address_book` with an agent key or a dashboard session (the `id` field, `adr_` plus a UUID), then reuse it from your key. Without one, use the crypto-only path below. The other option is a crypto-only link paid out to your Swaps Wallet: send `accepted_rail_kinds: ["crypto"]` instead, on a **USD** link only, where the crypto-only capability is enabled for your account. Don't send both. Payment links are unavailable in test mode, so the samples use a live key; a `sk_test_` key gets `503 temporarily_unavailable` here.

There is no published Swaps client library today — `packages/sdk-ts` (`@swaps/sdk`) is this repo's own internal, unpublished package, not something `npm install`able. Until one ships, cURL and plain `fetch` — a Node 18+ and browser built-in, no library to install — are what actually runs:

```shell title="cURL"
curl https://api.swaps.app/v1/payment_links \
  -X POST \
  -H "x-api-key: sk_live_51NxSupaLabs...9f2" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: supalabs-2026-014" \
  -H "Swaps-Version: 2026-09-04" \
  -d '{
    "title": "Design retainer — September",
    "amount": { "amount": "500", "currency": "USD", "decimals": 2 },
    "payer_email": "billing@supalabs.dev",
    "settlement_destination": { "address_book_id": "adr_3f1c2e4a-8b6d-4f2a-9c1e-5d7b3a9e0f21" }
  }'
```

```javascript title="fetch (Node 18+ / browser)"
const res = await fetch('https://api.swaps.app/v1/payment_links', {
  method: 'POST',
  headers: {
    'x-api-key': 'sk_live_51NxSupaLabs...9f2',
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(), // a NEW key per new link — reusing one replays the prior response
    'Swaps-Version': '2026-09-04', // optional — pins the call to a dated contract version
  },
  body: JSON.stringify({
    title: 'Design retainer — September',
    amount: { amount: '500', currency: 'USD', decimals: 2 },
    payer_email: 'billing@supalabs.dev',
    settlement_destination: { address_book_id: 'adr_3f1c2e4a-8b6d-4f2a-9c1e-5d7b3a9e0f21' },
  }),
});

if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`${error.type}/${error.code}: ${error.message}`);
}

const link = await res.json();
console.log(link.id, link.status); // pl_01J9ZK... draft
```

**201 Created — `payment_link` · `draft`**

```json
{
  "id": "pl_01J9ZK3Q8M2F5A7C9E1G3H5J7K",
  "object": "payment_link",
  "livemode": true,
  "status": "draft",
  "title": "Design retainer — September",
  "memo": null,
  "invoice_number": null,
  "amount": { "amount": "500", "currency": "USD", "decimals": 2 },
  "client_id": null,
  "expected_payer_type": "any",
  "allowed_rails": [],
  "payable_rails": [],
  "accepted_rail_kinds": [],
  "expires_at": null,
  "payer_email": "billing@supalabs.dev",
  "reminder_schedule": { "status": "off", "offsets_days": [], "sent": [] },
  "settlement_kind": null,
  "settlement_destination": { "address_book_id": "adr_3f1c2e4a-8b6d-4f2a-9c1e-5d7b3a9e0f21" },
  "items": [],
  "url": null,
  "created_at": "2026-09-04T14:02:11Z"
}
```

## 3. Open the link

A draft has no `url`. Activating is the account holder's own compliance attestation — over the dashboard, or a signed `POST …/activate` — never something an agent does on their behalf (see [Payment links](/products/payment-links)). REST only: `activate` has no MCP tool.

- `attestation_accepted` must be `true`; anything else is refused.
- `url`, `payable_rails` and `settlement_kind` are set at that moment, never before.
- `status` moves `draft → active` and the payer can open the link.

```shell title="cURL"
curl https://api.swaps.app/v1/payment_links/pl_01J9ZK3Q8M2F5A7C9E1G3H5J7K/activate \
  -X POST \
  -H "x-api-key: sk_live_51NxSupaLabs...9f2" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: supalabs-2026-014-activate" \
  -d '{ "attestation_accepted": true }'
```

A refused activation leaves the link a `draft`:

| Answer | Meaning |
|---|---|
| `400 settlement_destination_required` | The draft names neither a `settlement_destination` nor `accepted_rail_kinds: ["crypto"]`. `PATCH` a destination onto it and activate again. |
| `409 allowed_rails_invalid_for_currency` | The stored `allowed_rails` leaves no rail a payer could use. `error.details.offerable_rails` lists the ones that would work. |
| `422 crypto_only_currency_not_usd` | A crypto-only link is not in USD. The Swaps Wallet rail pays out USD stablecoins with no FX step. |

An account not yet eligible gets `403 permission_error` before the destination is checked; a crypto-only draft can also get `409 wallet_not_found` or `409 capability_unavailable`, and a second activate gets `409 conflict`. The guide's Minimal flow table gives each one's fix.

The payer opens `url` (`https://swaps.app/pay/plk_…` on production). Its last path segment is the public session token that `GET /v1/payment_sessions/{token}`, `POST …/view` and `POST …/payments` take, with no key. The [Payment links guide](/products/payment-links#minimal-flow) walks through the payer side and the other refusals.

## 4. Go live

Live access requires enablement, an authorized live key, [account verification](/products/customers) and the relevant capabilities. Changing the key prefix does not grant access. Confirm the supported operation and rail before making a live call.

> Do not treat a documented test-mode contract as proof of a working end-to-end sandbox. See [Test
> mode](/test-mode) for implementation limits.

## Ask your agent instead

After enablement, an agent with an authorized key can inspect capabilities and prepare permitted drafts. The account holder creates the key and confirms any activation themselves.

**Try the quickstart as a prompt**

Paste this into an agent that already holds a Swaps live business key — capabilities are unavailable in test mode.

> Using my existing business key, call GET /v1/capabilities?product=payment_links over REST — the MCP tool equivalent is list_capabilities (see the Agents & MCP page). If authorization or availability fails, stop and report the error. Otherwise summarize permitted actions; do not create or activate a payment link.

Next: the [Payment links guide](/products/payment-links) · the full [error type table](/conventions#errors).
