# Non-Fixed Rate On-Ramp

Use this flow when a partner converts fiat funds into crypto and delivers the crypto to an external wallet without locking the exchange rate in advance. The final rate and final crypto amount are set when Spike executes the trade.

In this mode, `GET /v1/quotes` is **optional and informational**. A quote may be shown to the end user as an estimate, but it does not reserve liquidity, does not lock the rate, and is not passed back to Spike when the exchange is created.

## When to use this flow

Choose non-fixed-rate on-ramp when:

- The partner wants the simplest on-ramp integration shape — no quote bookkeeping or rate-lock window to manage.
- The partner's product can present an estimate up front and the final amount after execution.
- The end user's destination is an external wallet on a Spike-supported network.

If the product needs to lock a rate at the moment the user confirms, use the fixed-rate on-ramp flow instead (documented separately).

## Scope

This page describes the partner-managed on-ramp pattern:

- The partner owns the end-user relationship, eligibility checks, fiat debit, and any customer-facing fee presentation.
- The partner validates the destination wallet before taking fiat from the user.
- The partner secures fiat first, then creates an exchange in Spike without `quoteId`.
- Spike executes the fiat-to-crypto trade, tracks the exchange state, requests withdrawal authorization, and submits the crypto withdrawal after approval.
- The partner reconciles its order using `externalReference.externalId`, `exchangeId`, final amounts, and withdrawal details.

Use the [API Reference](../../../api.html) for exact request and response schemas. This page explains the business flow and integration decisions.

For lifecycle, events, idempotency, addresses, and failure handling, see:

- [Exchange Lifecycle](../concepts/exchange-lifecycle.md)
- [Events and Webhooks](../concepts/events-and-webhooks.md)
- [Idempotency and Retries](../concepts/idempotency.md)
- [Addresses](../concepts/addresses.md)
- [Failure Handling](../operations/failure-handling.md)

## End-to-end sequence

```mermaid
sequenceDiagram
    autonumber
    participant U as End User
    participant P as Partner
    participant S as Spike
    participant W as External Wallet

    U->>P: Wants to buy crypto
    P->>S: GET /v1/conversion-routes
    S-->>P: Route enabled
    P->>S: GET /v1/quotes (optional, indicative)
    S-->>P: Estimate
    P->>S: POST /v1/addresses/validate
    S-->>P: valid: true
    P->>U: Show estimate, confirm fiat debit
    Note over P: Partner secures fiat
    P->>S: POST /v1/exchanges (no quoteId)
    S-->>P: 200 WAITING_DEPOSIT
    Note over S: Fiat deposit reference is processed asynchronously
    S-->>P: deposit.received
    Note over S: Trade executes at market
    S-->>P: exchange.completed
    S-->>P: exchange.authorization_required
    P->>P: Verify authorizationContext
    P->>S: POST /authorization/approve
    S-->>P: withdraw.initiated
    Note over S,W: On-chain confirmation
    S-->>P: withdraw.completed
    S-->>P: onramp.completed
    P->>U: Mark order delivered
```

## Happy path

### 1. Confirm the route is available

`GET /v1/conversion-routes?fromCurrency=<FIAT>&toCurrency=<CRYPTO>&type=ON_RAMP&enabledOnly=true` to check that the partner can execute the requested direction. For example, a USD to BTC on-ramp is queried as `fromCurrency=USD&toCurrency=BTC&type=ON_RAMP`.

### 2. Show an indicative estimate (optional)

Call `GET /v1/quotes` with `fromCurrency`, `toCurrency`, and either `fromAmount` or `toAmount`. Display the response as a non-binding estimate. Do not call `GET /v1/quotes/fixed` for this flow, and do not include `quoteId` when creating the exchange.

### 3. Validate the destination wallet

Before debiting fiat, call `POST /v1/addresses/validate` with the destination `currency`, `network`, and `address`. If the endpoint returns `200` with `valid: false`, treat that as a domain result and ask the user to correct the address. Do not debit fiat until the destination is acceptable for the selected asset and network. See [Addresses](../concepts/addresses.md).

### 4. Secure fiat on the partner side

The partner debits, reserves, or otherwise secures the user's fiat according to the partner's payment setup. The amount sent to Spike is the amount available for crypto purchase after any partner-side deductions that are outside Spike's exchange amount.

### 5. Create the exchange without a fixed quote

Call `POST /v1/exchanges` without `quoteId`. For a standard on-ramp to an external wallet, use `flow.source=ACCOUNT` and `flow.destination=CRYPTOWALLET`, include `destinationAddress`, include `externalReference`, and include `depositReference` to identify the partner-side fiat movement.

```json
{
  "currencyPair": {
    "fromCurrency": "USD",
    "toCurrency": "BTC"
  },
  "fromAmount": "500.00",
  "flow": {
    "source": "ACCOUNT",
    "destination": "CRYPTOWALLET"
  },
  "externalReference": {
    "externalId": "partner-order-123",
    "externalUserId": "partner-user-456"
  },
  "destinationAddress": {
    "address": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
    "network": "bitcoin"
  },
  "depositReference": "partner-fiat-movement-789"
}
```

Use a UUID `Idempotency-Key` for retries. Use the same key and the same body when retrying the same partner order. Use `externalReference.externalId` as the business identifier for reconciliation. See [Idempotency](../concepts/idempotency.md).

The success response is `200 OK`. A newly created partner-managed on-ramp normally starts as `WAITING_DEPOSIT` while Spike accepts and processes the fiat `depositReference`. After that asynchronous deposit step completes, the exchange moves to `PROCESSING`. If an idempotent retry is made after the exchange has already advanced, the response may return the current progressed status instead of the initial creation status.

> `WAITING_DEPOSIT` does not currently expire. If the partner does not follow through, the exchange will sit indefinitely. Time out abandoned exchanges in your own bookkeeping and call `POST /v1/exchanges/cancel` to release the reservation. See [Failure handling](../operations/failure-handling.md) Section 2.

### 6. Track execution

Spike processes the partner-supplied fiat reference, then executes the trade at the available market rate. While the trade is in progress, the exchange is in `PROCESSING`. When the trade has filled and the crypto withdrawal requires partner approval, the exchange becomes `AWAITING_AUTHORIZATION` and includes `authorizationContext`. After approval the exchange returns to `PROCESSING` while the on-chain withdrawal is in flight.

Track this with webhooks (`exchange.completed`, `exchange.authorization_required`, `withdraw.*`) and use `GET /v1/exchanges/by-id` as the source of truth.

> `exchange.completed` is the trading-leg fill signal. It means the crypto was bought, **not** that the user has received it. See [Exchange Lifecycle](../concepts/exchange-lifecycle.md).

### 7. Approve or reject the withdrawal

On `exchange.authorization_required`, verify the final withdrawal parameters in `authorizationContext`: `amount`, `currency`, `destinationAddress`, and `bitfinexTrxId` (the provider order ID captured when the trade filled). Then either approve or reject:

- If acceptable, call `POST /v1/exchanges/{exchangeId}/authorization/approve` with `approvalToken`, `commitmentHash`, and `validUntil`. This releases the crypto withdrawal.
- If the partner's checks fail, call `POST /v1/exchanges/{exchangeId}/authorization/reject`. The trading leg is **already final** at this point — `exchange.completed` has fired and is not retracted. The partner-visible status moves to `FAILED` and `onramp.failed` fires with `failureReason: AUTHORIZATION_REJECTED`. The partner reverses fiat off-platform.

### 8. Confirm delivery and reconcile

The on-ramp is complete only when the crypto withdrawal completes. The canonical "done" signal is `onramp.completed` (the aggregate terminal event). Equivalently, poll until the exchange is `COMPLETED` and `cryptoWithdrawals[]` contains the final withdrawal result with `transactionHash` populated.

Reconcile against `externalReference.externalId`, `exchangeId`, final `amounts`, and withdrawal details. See [Reconciliation](../operations/reconciliation.md).

## Decision points

A robust on-ramp integration makes three decisions explicitly:

1. **Quote display vs. final amount.** Always present the indicative quote as non-binding. The final crypto amount is in `amounts.to` after `exchange.completed`.
2. **Approve or reject at authorization.** The `authorizationContext` is the partner's last chance to validate the withdrawal before crypto leaves Spike. Verify the destination address, amount, and currency match the original order.
3. **Delivery confirmation.** Do not mark the user-facing on-ramp as delivered until `withdraw.completed` arrives or the exchange status reaches `COMPLETED` with a populated `cryptoWithdrawals[]`. A trade-fill alone does not guarantee delivery.

## What can go wrong

The full negative-scenarios catalog is in [Failure handling](../operations/failure-handling.md). The most common on-ramp paths to be ready for:

- **Quote drift**: the indicative rate is not a price guarantee.
- **Invalid destination address**: address validation returned `valid: false`. Treat as a user input issue.
- **Trade execution failure**: the market order cannot be filled. The aggregate event is `onramp.failed` with `failureReason: EXCHANGE_ORDER_CANCELLED`. The partner reverses fiat on the partner side.
- **Approval material invalid or expired**: `400 commitment_mismatch` or `400 authorization_expired` on `/authorization/approve`. Retry with the unmodified material from the latest `authorizationContext`.
- **Partner rejects authorization**: trade is final (`exchange.completed` already fired); aggregate fires `onramp.failed` with `failureReason: AUTHORIZATION_REJECTED`. Partner reverses fiat off-platform.
- **Withdrawal failure after trade**: the withdrawal failed after a successful trade. Aggregate fires `onramp.failed` with `failureReason: WITHDRAWAL_FAILED`. Operational exception requiring Spike support.

## Reconciliation checklist

For every on-ramp order, the partner system should record:

- `externalReference.externalId` — the partner's business identifier.
- `exchangeId` — Spike's primary identifier.
- `amounts.from` and `amounts.to` — the final fiat-out and crypto-in amounts.
- `amounts.rate` — the executed rate.
- `cryptoWithdrawals[].transactionHash` — the on-chain delivery proof.
- `cryptoWithdrawals[].networkFee` and `cryptoWithdrawals[].netAmount` — the network fee deducted from the withdrawal amount and the net amount delivered.

See [Reconciliation](../operations/reconciliation.md) for the full pattern.
