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

# Quickstart

> Go from a credential to one payment you watched reach a terminal state, with curl at each step.

Run this on staging before you write integration code. It ends with a payment you confirmed by reading the resource, not by trusting a response code.

You need an app ID and an app secret for one environment, and both stay on a server.

```bash theme={null}
export APP_ID="app_..."
export APP_SECRET="..."
```

Staging is `https://staging-api-v2.stableyard.fi` and production is `https://prod-api.stableyard.fi`. A credential authenticates against one of them and returns `401` against the other. See [Environments](/environments).

## 1. Read your configuration

This is the first call in every environment. It proves the credential and tells you what this app can actually do.

```bash theme={null}
curl https://staging-api-v2.stableyard.fi/v2/partners/config \
  -u "$APP_ID:$APP_SECRET"
```

```json theme={null}
{
  "apiVersion": "2026-09-09",
  "credentialType": "standard",
  "status": "active",
  "networks": [
    {
      "code": "arbitrum",
      "chainId": 42161,
      "paymentCollection": { "supported": true, "configured": true },
      "settlement": { "supported": true },
      "defaultAsset": { "symbol": "USDC", "decimals": 6 }
    }
  ],
  "capabilities": {
    "payments": { "available": true }
  }
}
```

A network can settle and still have no collection path, so take the chain and asset from this response rather than from any table. Details are on [Capabilities](/capabilities).

**Before moving on:** `status` is `active`, `capabilities.payments.available` is `true`, and you have written down one `chainId` whose `paymentCollection.supported` is `true`, with an asset symbol on it. Steps 2 and 3 both use that pair.

## 2. Create an account

Every movement is addressed to an account. Create one for the party the money is for.

```bash theme={null}
curl -X POST https://staging-api-v2.stableyard.fi/v2/accounts \
  -u "$APP_ID:$APP_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: account:user_123" \
  -d '{
    "externalUserId": "user_123",
    "subjectType": "individual",
    "wallets": [
      {
        "chainId": 42161,
        "address": "0x1111111111111111111111111111111111111111",
        "label": "Primary wallet"
      }
    ]
  }'
```

A `201` carries four resources created together:

```json theme={null}
{
  "account": { "id": "acct_123", "subjectType": "individual", "status": "active" },
  "connectedWallets": [{ "id": "wallet_123", "chainId": 42161 }],
  "settlementProfile": {
    "id": "settle_123",
    "settlementDestinationId": "destination_123",
    "chainId": 42161,
    "assetSymbol": "USDC",
    "status": "draft"
  }
}
```

Use the `chainId` from step 1 and an address from that chain's family. `subjectType` is required, never inferred, and cannot be changed later. Passing `wallets` is what gives the account somewhere for value to land.

**Before moving on:** store `account.id`, and confirm `settlementProfile.settlementDestinationId` is present. Without a settlement destination, step 3 is refused with `settlement_profile_required`.

## 3. Create a receive payment

A receive payment is one bounded obligation: one amount, one asset, one expiry, one reference of yours.

```bash theme={null}
curl -X POST https://staging-api-v2.stableyard.fi/v2/payments \
  -u "$APP_ID:$APP_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: deposit:order_1042" \
  -d '{
    "intent": "receive",
    "recipient": { "accountId": "acct_123" },
    "paymentAmount": {
      "amount": "25.00",
      "assetType": "crypto",
      "assetCode": "USDC",
      "chainId": 42161
    },
    "description": "Order 1042",
    "externalReference": "order_1042",
    "expiresInSeconds": 600
  }'
```

Use the chain and asset from step 1. `expiresInSeconds` accepts 60 to 86400 and defaults to 600.

```json theme={null}
{
  "payment": {
    "id": "payment_01JY8K4Q6P9A2M7T3W5X8Z1BCR",
    "intent": "receive",
    "status": "requires_payment_method",
    "operationalState": "normal",
    "externalReference": "order_1042",
    "expiresAt": "2026-08-28T10:10:00.000Z"
  },
  "checkout": {
    "paymentUrl": "https://pay.stableyard.fi/pay/7Yf3KMpQ2xWa",
    "clientSecret": "pay_client_secret_opaque_value_123",
    "expiresAt": "2026-08-28T10:10:00.000Z"
  },
  "nextAction": { "type": "payment_method" }
}
```

A `200` here means the obligation exists, not that money arrived.

<Warning>
  `checkout.clientSecret` is returned at creation and never again. Keep it out of URLs, logs and analytics. `checkout.paymentUrl` is a capability for this one payment: whoever holds it can view and fund it, and can reach nothing else.
</Warning>

**Before moving on:** persist `payment.id` against your own `order_1042`, with a unique constraint on that reference so one obligation can be fulfilled once.

## 4. Fund it

Open `checkout.paymentUrl` in a browser and pay it. The page works out the ways that payment can be funded; your code calls none of that.

Building your own checkout instead? Put `payment.id` in the path and send `checkout.clientSecret` only as a bearer header, never in a URL:

```http theme={null}
GET /v2/public/payments/payment_01JY8K4Q6P9A2M7T3W5X8Z1BCR
Authorization: Bearer pay_client_secret_opaque_value_123
```

That secret reaches this one payment and nothing else. See [Authentication](/authentication) and [Build your own checkout](/payments/depositing-funds#build-your-own-checkout).

Staging runs on test networks, so fund from a test wallet on the chain you chose in step 1.

**Before moving on:** re-read the payment once. It should have left `requires_payment_method`.

## 5. Read it to a terminal state

```bash theme={null}
curl https://staging-api-v2.stableyard.fi/v2/payments/payment_01JY8K4Q6P9A2M7T3W5X8Z1BCR \
  -u "$APP_ID:$APP_SECRET"
```

```json theme={null}
{
  "payment": {
    "id": "payment_01JY8K4Q6P9A2M7T3W5X8Z1BCR",
    "status": "succeeded",
    "operationalState": "normal",
    "operationalReasonCode": null,
    "paymentAmount": { "amount": "25.00", "assetCode": "USDC", "chainId": 42161 },
    "fees": {
      "platformFeeAmountAtomic": "125000",
      "partnerFeeAmountAtomic": "0",
      "merchantNetAmountAtomic": "24875000"
    },
    "acceptedAt": "2026-08-28T10:03:11.000Z",
    "succeededAt": "2026-08-28T10:04:02.000Z"
  }
}
```

Read two fields, not one.

| What you see | What to do |
| - | - |
| `requires_payment_method`, `requires_action`, `processing` | Keep reading |
| `accepted` | Funds were verified. Settlement has not finished |
| `succeeded` | Terminal. Value reached the destination |
| `failed`, `cancelled`, `expired` | Terminal. Nothing is held for you |
| `operationalState` other than `normal` | Read `operationalReasonCode`. A human may be needed, whatever `status` says |

**You are done when** `status` is terminal and `operationalState` is `normal`. Every state, including the ones that need a person, is on [Status codes](/status-codes).

Polling got you through this once. It is not the integration.

<Card title="Next: Wire webhooks" icon="bell" href="/webhooks">
  Get told when state changes, then read the payment.
</Card>


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