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

# Pay a Bill

> Pay one of the bills found by a discovery

## Overview

Pays a single bill from a `READY` discovery. You choose one `billId` out of that transaction's `bills[]`; the same transaction then carries the payment through to a final state.

<Warning>
  **`200` is an acknowledgement, not an outcome.** It means the payment was accepted and is now in flight. Whether the money actually moved is only knowable from the transaction's `status` — poll until it reaches `SUCCESS`, `FAILED` or `REFUNDED`.
</Warning>

This is the call that moves money. Everything you need on your own side — the order record, the amount, your customer's authorisation — should already be persisted before you send it.

## Request Body

<ParamField body="transactionId" type="string" required>
  The discovery to pay against. Must be a 24-character lowercase hexadecimal string, and the transaction must currently be `READY`.
</ParamField>

<ParamField body="billId" type="string" required>
  The `billId` of one entry in that transaction's `bills[]`. Maximum 100 characters.

  Copy it from the response — do not construct it.
</ParamField>

<ParamField body="ref" type="string" required>
  A **new** reference for this payment. Maximum 100 characters, and unique among your live transactions for that partner.

  It must be different from the `ref` you used for the discovery; reusing that value answers `403 DUPLICATED_REF`.
</ParamField>

<Note>
  The transaction keeps the `ref` it was created with. That original discovery `ref` is what this endpoint echoes back, what [Get Transaction by Reference](/en/api-reference/bill-payment/check-by-ref) looks up, and what appears on the transaction from now on.
</Note>

## Response

<ResponseField name="success" type="boolean" required>
  `true` when the payment was accepted.
</ResponseField>

<ResponseField name="data" type="object" required>
  <Expandable title="properties">
    <ResponseField name="transactionId" type="string" required>
      The same transaction, now carrying the payment.
    </ResponseField>

    <ResponseField name="ref" type="string" required>
      The transaction's discovery `ref`.
    </ResponseField>

    <ResponseField name="status" type="string" required>
      Always `PROCESSING` at this point.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="meta" type="object" required>
  <Expandable title="properties">
    <ResponseField name="timestamp" type="string" required>
      Response time, ISO 8601 UTC.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="requestId" type="string" required>
  Correlation identifier, also sent as the `X-Request-Id` response header.
</ResponseField>

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl https://billapi.oneclickdz.com/v3/bills/pay \
    -X POST \
    -H "Content-Type: application/json" \
    -H "X-Access-Token: YOUR_API_KEY" \
    -d '{
      "transactionId": "68b2f4c1a7d3e9f204c81a55",
      "billId": "sbx_bill_68b2f4c1a7d3e9f204c81a55_0",
      "ref": "pay-inv-2026-0042"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://billapi.oneclickdz.com/v3/bills/pay", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Access-Token": process.env.ONECLICKDZ_API_KEY,
    },
    body: JSON.stringify({
      transactionId: "68b2f4c1a7d3e9f204c81a55",
      billId: "sbx_bill_68b2f4c1a7d3e9f204c81a55_0",
      ref: "pay-inv-2026-0042",
    }),
  });

  const body = await response.json();

  if (!body.success) {
    throw new Error(`${body.error.code}: ${body.error.message}`);
  }

  // Accepted — in flight. Poll until the status is final.
  console.log(body.data.status); // "PROCESSING"
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.post(
      'https://billapi.oneclickdz.com/v3/bills/pay',
      headers={
          'Content-Type': 'application/json',
          'X-Access-Token': os.getenv('ONECLICKDZ_API_KEY')
      },
      json={
          'transactionId': '68b2f4c1a7d3e9f204c81a55',
          'billId': 'sbx_bill_68b2f4c1a7d3e9f204c81a55_0',
          'ref': 'pay-inv-2026-0042'
      }
  )

  body = response.json()

  if not body['success']:
      raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")

  # Accepted — in flight. Poll until the status is final.
  print(body['data']['status'])  # "PROCESSING"
  ```

  ```php PHP theme={null}
  <?php
  $payload = [
      'transactionId' => '68b2f4c1a7d3e9f204c81a55',
      'billId'        => 'sbx_bill_68b2f4c1a7d3e9f204c81a55_0',
      'ref'           => 'pay-inv-2026-0042'
  ];

  $ch = curl_init('https://billapi.oneclickdz.com/v3/bills/pay');
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'Content-Type: application/json',
      'X-Access-Token: ' . getenv('ONECLICKDZ_API_KEY')
  ]);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));

  $body = json_decode(curl_exec($ch), true);
  curl_close($ch);

  if (!$body['success']) {
      throw new Exception($body['error']['code'] . ': ' . $body['error']['message']);
  }

  // Accepted — in flight. Poll until the status is final.
  echo $body['data']['status']; // "PROCESSING"
  ?>
  ```
</CodeGroup>

### Success Response

```json theme={null}
{
  "success": true,
  "data": {
    "transactionId": "68b2f4c1a7d3e9f204c81a55",
    "ref": "disc-inv-2026-0042",
    "status": "PROCESSING"
  },
  "meta": {
    "timestamp": "2026-08-31T10:15:38.410Z"
  },
  "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
}
```

Once the transaction reaches `SUCCESS`, [Get Transaction by ID](/en/api-reference/bill-payment/check-by-id) returns `operationId` and `receiptUrl` alongside `selectedBill` and `total`.

## Error Responses

<AccordionGroup>
  <Accordion title="400 — Validation error">
    **The body did not match the schema.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "ERR_VALIDATION",
        "message": "transactionId must be 24 characters long",
        "details": ["transactionId must be 24 characters long"]
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    Common causes: a `transactionId` that is not 24 hexadecimal characters, a missing `billId`, a missing `ref`, or a `ref` longer than 100 characters.

    **What to do:** fix the request. Never retry it unchanged.
  </Accordion>

  <Accordion title="401 — Missing or invalid access token">
    **The key was absent or rejected.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INVALID_ACCESS_TOKEN",
        "message": "The provided access token is invalid."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** verify the key with [Validate API Key](/en/api-reference/bill-payment/validate-key). Nothing was charged.
  </Accordion>

  <Accordion title="403 — Duplicate reference">
    **The `ref` is already in use for this partner.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "DUPLICATED_REF",
        "message": "A transaction with this ref already exists for this partner."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    The most common cause is reusing the discovery `ref` on the payment. Send a distinct value, for example `pay-` in front of your order identifier.

    **What to do:** before retrying, check the transaction's current state with [Get Transaction by ID](/en/api-reference/bill-payment/check-by-id). If it is already `PROCESSING`, your payment went through and there is nothing to resend.
  </Accordion>

  <Accordion title="404 — Not found or not payable">
    **The transaction does not exist for your account, or it is not in a payable state, or that `billId` is not one of its bills.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "NOT_FOUND",
        "message": "Transaction is not in a payable state, or the bill was not found."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    All three cases answer `404` — including a transaction that belongs to another partner, so the API never confirms that someone else's transaction exists.

    **What to do:** re-read the transaction. If its `status` is no longer `READY`, the payment has already been started; poll it instead of sending another one.
  </Accordion>

  <Accordion title="409 — Bill already paid">
    **This exact bill was already paid.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "BILL_ALREADY_PAID",
        "message": "This bill has already been paid."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** treat it as a successful outcome that you already have. Find the paid transaction in [List Transactions](/en/api-reference/bill-payment/list-transactions) and use its receipt. Do not charge your customer twice.
  </Accordion>

  <Accordion title="409 — Payment in progress">
    **Another payment for the same bill has not finished.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "PAYMENT_IN_PROGRESS",
        "message": "A payment for this bill is currently in progress."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** poll the transaction that is already running. Sending this again will not make it finish sooner.
  </Accordion>

  <Accordion title="503 — Partner unavailable">
    **The biller cannot be reached, or is currently switched off.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "PARTNER_UNAVAILABLE",
        "message": "Partner temporarily unavailable — please try again later."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** nothing was charged. The discovery is still `READY`, so you can pay the same `billId` again later — with the same `ref`, which was never consumed.
  </Accordion>

  <Accordion title="503 — Authentication unavailable">
    **We could not verify your key in time. Your key is not the problem.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "AUTH_UNAVAILABLE",
        "message": "Authentication is temporarily unavailable. Please retry shortly."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** wait for `Retry-After` (5 seconds) and retry the same request. The request never reached the payment path, so retrying it is safe.
  </Accordion>

  <Accordion title="503 — Service unavailable">
    **The Bill Payment API is in planned maintenance.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "SERVICE_UNAVAILABLE",
        "message": "The service is temporarily unavailable. Please retry shortly."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** honour `Retry-After` and retry.
  </Accordion>

  <Accordion title="500 — Internal error">
    **Something failed on our side.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INTERNAL_ERROR",
        "message": "An unexpected error occurred."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** **do not resend the payment.** Read the transaction first — if it is `PROCESSING`, the payment is running. Contact support with the `requestId` if the state is unclear.
  </Accordion>
</AccordionGroup>

## What you pay

Every bill in a discovery carries its own money fields.

| Field    | Meaning                                               |
| -------- | ----------------------------------------------------- |
| `amount` | What the biller is owed, in DZD                       |
| `fee`    | The OneClickDz service fee for paying it, in DZD      |
| `total`  | `amount + fee` — the figure debited from your balance |

The fee is a percentage of the amount, clamped between a minimum and a maximum, **set per biller**:

| Biller            | Percentage | Minimum | Maximum |
| ----------------- | ---------- | ------- | ------- |
| `ADE`             | 0.5%       | 30 DZD  | 60 DZD  |
| `SONELGAZ`        | 0.5%       | 30 DZD  | 60 DZD  |
| `Algérie Télécom` | 0.5%       | 10 DZD  | 50 DZD  |

Because 0.5% of a typical bill is well under the minimum, most payments are charged exactly the minimum. These rates are configuration and can be adjusted, so read `fee` from the response rather than recomputing it. `total` appears on the transaction once a bill has been selected.

For the 443.39 DZD ADE bill above: 0.5% is 2.22, which is below the 30 DZD minimum, so `fee` is 30.00 and `total` is 473.39.

## Before you call

<Steps>
  <Step title="Persist your own record first">
    Write your order — customer, `transactionId`, `billId`, `amount`, `fee`, `total` and the `ref` you are about to use — before the request leaves your process. If the response is lost, that record is how you find the payment again.
  </Step>

  <Step title="Confirm the amount with your customer">
    `amount` and `fee` come from the discovery. Show the `total` you are about to charge, not an estimate.
  </Step>

  <Step title="Send the payment">
    One call, with a `ref` that is new for this partner.
  </Step>

  <Step title="Poll until final">
    `SUCCESS`, `FAILED` or `REFUNDED`. Treat `UNKNOWN` as "keep polling", never as a failure.

    → [Status polling](/en/bill-payment-guides/4-status-polling)
  </Step>
</Steps>

## The guards that protect you

Three rules stop the same money moving twice. All three answer before anything is charged.

| Guard               | Response                  | Meaning                                                           |
| ------------------- | ------------------------- | ----------------------------------------------------------------- |
| Duplicate reference | `403 DUPLICATED_REF`      | This `ref` already belongs to a live transaction for this partner |
| Already paid        | `409 BILL_ALREADY_PAID`   | This bill has already been paid successfully                      |
| Payment in flight   | `409 PAYMENT_IN_PROGRESS` | Another payment for this bill has not finished                    |

<Warning>
  None of these three is a reason to retry with a different `ref`. Each one means the work either is already done or is already running — look it up rather than sending it again.
</Warning>

## Best Practices

<CardGroup cols={2}>
  <Card title="Write before you send" icon="database">
    Persist your order record, including the `ref`, before the request. A lost response is then recoverable.
  </Card>

  <Card title="One ref per call" icon="fingerprint">
    Use a distinct `ref` for the discovery and for the payment. `disc-` and `pay-` in front of your order identifier is enough.
  </Card>

  <Card title="Never resend on a timeout" icon="triangle-exclamation">
    Read the transaction first. A network timeout does not mean the payment did not happen.
  </Card>

  <Card title="Charge from total" icon="calculator">
    Debit your customer the `total` the API returned, never a figure you calculated yourself.
  </Card>
</CardGroup>

## Related Endpoints

<CardGroup cols={3}>
  <Card title="Discover Bills" icon="magnifying-glass-dollar" href="/en/api-reference/bill-payment/discover-bills">
    Find what is payable
  </Card>

  <Card title="Get Transaction by ID" icon="id-card" href="/en/api-reference/bill-payment/check-by-id">
    Poll the outcome
  </Card>

  <Card title="Download Receipt" icon="file-arrow-down" href="/en/api-reference/bill-payment/get-receipt">
    Proof of payment
  </Card>

  <Card title="Get Transaction by Reference" icon="tag" href="/en/api-reference/bill-payment/check-by-ref">
    Recover a lost response
  </Card>

  <Card title="Paying Bills" icon="money-bill-transfer" href="/en/bill-payment-guides/3-paying-bills">
    The full walkthrough
  </Card>

  <Card title="Status Polling" icon="arrows-rotate" href="/en/bill-payment-guides/4-status-polling">
    A production-grade poller
  </Card>
</CardGroup>
