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

# IDR Offramp

> Sell stablecoin for Indonesian Rupiah, paid out directly to an Indonesian bank account.

IDR offramp lets your users convert their stablecoin balance into Indonesian Rupiah, settled to a bank account in Indonesia. Every offramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.**

<Info>
  Both USDT and USDC are supported for IDR offramp.
</Info>

<Steps>
  <Step title="Create User">
    ```http theme={null}
    POST /api/v2/user
    ```

    ```json theme={null}
    {
      "email": "user@example.com",
      "phone": "+6281234567890"
    }
    ```

    Response:

    ```json theme={null}
    {
      "success": true,
      "data": {
        "user_id": "550e8400-e29b-41d4-a716-446655440000"
      }
    }
    ```

    Save `user_id` — every subsequent call needs it as the `X-User-Id` header.
  </Step>

  <Step title="KYC">
    IDR supports two KYC methods:

    <Tabs>
      <Tab title="KYC Sharing">
        ```http theme={null}
        POST /api/v2/user/kyc
        ```

        Headers: `X-User-Id: <user_id>`

        ```json theme={null}
        {
          "legal_name": "Budi Santoso",
          "poi": "<proof-of-identity-document-id>",
          "poa": "<proof-of-address-document-id>",
          "dob": "1990-01-31",
          "email": "user@example.com",
          "phone": "+6281234567890",
          "country_iso3": "IDN"
        }
        ```

        Response:

        ```json theme={null}
        { "success": true, "data": {} }
        ```

        Saber may request supporting KYC documents at any time — these must be shared within 72 hours of the request.
      </Tab>

      <Tab title="KYC Widget">
        Redirect the user to a signed URL:

        ```javascript theme={null}
        const sigString = clientId + timestamp + "sdk" + userId
        const secret = HmacSHA256(sigString, clientSecret).toString().toUpperCase()
        // redirect to: https://app.saber.money/kyc?client_id=...&user_id=...&timestamp=...&secret=...&redirect_url=<url-encoded-url>
        ```

        The URL is valid for **10 minutes** from generation. Append the optional `redirect_url` as the last query parameter (URL-encoded) to send the user back to your app when they exit the flow. See [KYC Hosted Widget](/user/kyc/widget) for the full parameter reference.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Bank Account Linkage">
    First, fetch the list of supported banks and their codes. This lookup only works for `IDN` — it returns an error for any other country:

    ```http theme={null}
    GET /api/v2/user/bank_account/bank-codes/IDN
    ```

    ```json theme={null}
    {
      "success": true,
      "data": {
        "Bank Central Asia": "BCA",
        "Bank Mandiri": "MANDIRI",
        "Bank Negara Indonesia": "BNI",
        "Bank Rakyat Indonesia": "BRI"
      }
    }
    ```

    `data` is a flat map of bank name to `bank_code` — pass the code as-is into `bank_code` below.

    ```http theme={null}
    POST /api/v2/user/bank_account
    ```

    Headers: `X-User-Id: <user_id>`

    ```json theme={null}
    {
      "account_holder_name": "Budi Santoso",
      "account_number": "1234567890",
      "bank_code": "BCA",
      "bank_bic": "CENAIDJA",
      "country": "IDN"
    }
    ```

    Mandatory: `account_holder_name`, `account_number`, `bank_code`, `country`. `bank_bic` is optional but recommended for maximum delivery rates and compatibility across multiple downstream partners.

    Response:

    ```json theme={null}
    {
      "success": true,
      "data": {
        "bank_id": "550e8400-e29b-41d4-a716-446655440000",
        "account_number": "1234567890",
        "account_holder_name": "Budi Santoso",
        "name": null,
        "bic": null,
        "validation_status": "PENDING",
        "country": "IDN",
        "is_nre": false,
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-01T00:00:00Z"
      }
    }
    ```

    <Note>
      This is the shared bank account response shape across corridors — fields not applicable to bank-code routing (e.g. `bic`) are returned as `null`.
    </Note>

    Save `bank_id` for the sell step.
  </Step>

  <Step title="Transaction">
    Two variants, both requiring a quote first — a **Live Quote** (informational, executes at market rate regardless) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the sell request):

    <Tabs>
      <Tab title="Pool Sell (recommended)">
        Get an informational quote:

        ```http theme={null}
        GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=IDR&from_amount=100
        ```

        Then create the sell:

        ```http theme={null}
        POST /api/v2/wallet/transaction/pool/sell
        ```

        ```json theme={null}
        {
          "source_id": "<bank_id>",
          "fiat_symbol": "IDR",
          "crypto_symbol": "USDT",
          "crypto_amount": 25,
          "payment_method": "bank_transfer"
        }
        ```
      </Tab>

      <Tab title="External Sell">
        Generate a locked quote (expires in \~30-60s):

        ```http theme={null}
        GET /api/v2/wallet/w/quote?from_currency=USDT&to_currency=IDR&network=BSC&from_amount=10
        ```

        Save `id` from the response as `quote_id`, then create the sell:

        ```http theme={null}
        POST /api/v2/wallet/transaction/crypto/sell
        ```

        ```json theme={null}
        {
          "payment_method": "bank_transfer",
          "network": "BSC",
          "destination_instrument_id": "<bank_id>",
          "quote_id": "<quote_id>",
          "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8",
          "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8"
        }
        ```

        Then fund `deposit_crypto_address` on-chain within the settlement expiry, and confirm it:

        ```http theme={null}
        POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding
        ```

        ```json theme={null}
        { "transaction_hash": "0x1234567890abcdef" }
        ```

        <Warning>
          This returns **202 Accepted**, not 200 — Saber verifies the deposit asynchronously.
        </Warning>
      </Tab>
    </Tabs>

    **Payment rail:** `payment_method: bank_transfer` resolves automatically to the available local rail based on the transaction amount and the beneficiary bank.

    <Warning>
      Due to Indonesian banking cut-offs, any fiat withdrawal request submitted after **22:59 (UTC+7)** may be processed at **07:00 (UTC+7)** the following banking day.
    </Warning>
  </Step>
</Steps>

## Checking status

```http theme={null}
GET /api/v2/wallet/transaction?transaction_type={POOL_SELL|EXTERNAL_SELL}&transaction_id={id}
```

| Transaction Type | Possible `status` values                                                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Pool Sell        | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` |
| External Sell    | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED`                                   |

Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/offramp/operations/lifecycle).

## User limits and EDD

Each user has a lifetime transaction limit (currently \$200,000 USD equivalent — this figure is suggestive and subject to change; confirm current limits with Saber). Once a user's limit is breached, no further transactions can be created until Enhanced Due Diligence (EDD) is completed to raise it.

EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days.

## Related pages

<CardGroup cols={2}>
  <Card title="Offramp Operations" icon="arrow-right-arrow-left" href="/offramp/available-flows" cta="View guide" arrow="true" />

  <Card title="User KYC" icon="id-card" href="/user/kyc/overview" cta="View guide" arrow="true" />

  <Card title="Bank Account" icon="building-columns" href="/user/bank-account/overview" cta="View guide" arrow="true" />
</CardGroup>


## Related topics

- [Overview](/user/kyc/overview.md)
- [Available Flows](/offramp/available-flows.md)
- [Welcome to Saber Money](/getting-started/welcome.md)
