> ## 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.

# Discover Bills

> Start a bill discovery for a partner account

## Overview

Asks a biller what a given account currently owes. The response is a `transactionId` you then poll; when the transaction reaches `READY`, its `bills[]` array holds everything that is payable.

<Warning>
  **`200` is an acknowledgement, not an outcome.** It means the request was accepted and a transaction was created. The bills arrive later, in the transaction. Never treat this response as "the account owes nothing".
</Warning>

Discovery does not move money and does not commit you to anything. It is safe to run before you show a customer what they owe.

## Request Body

<ParamField body="partner" type="string" required>
  The biller to query. Exactly one of `ADE`, `SONELGAZ`, `SEAAL`, `AADL`, `Algérie Télécom` — accents included.

  Check [List Partners](/en/api-reference/bill-payment/list-partners) first; an `UNAVAILABLE` partner answers `503 PARTNER_UNAVAILABLE`.
</ParamField>

<ParamField body="account" type="object" required>
  The account to look up. It must carry **exactly one** identifier — see [Account identifiers](#account-identifiers) below. Zero identifiers, or two, is rejected.
</ParamField>

<ParamField body="ref" type="string" required>
  Your own reference for this discovery. Maximum 100 characters, and unique among your live transactions for that partner.

  Reusing a `ref` answers `403 DUPLICATED_REF`. It is also how you recover a lost response — see [Get Transaction by Reference](/en/api-reference/bill-payment/check-by-ref).
</ParamField>

## Account identifiers

Send the field that belongs to the partner you are querying. This is also the field the API returns in `account` on every transaction for that partner.

| Partner           | Field            | Notes                                                                      |
| ----------------- | ---------------- | -------------------------------------------------------------------------- |
| `ADE`             | `reference`      | Client reference, up to 50 characters                                      |
| `SEAAL`           | `reference`      | Client reference, up to 50 characters                                      |
| `SONELGAZ`        | `contractNumber` | Contract number, up to 50 characters                                       |
| `AADL`            | `aadlNumber`     | Housing file number, up to 50 characters                                   |
| `Algérie Télécom` | `phoneNumber`    | Algerian landline, `0` or `+213` followed by a digit 2–4 and 7 more digits |

Two richer forms are also accepted where the biller needs more than a single number to identify a bill:

<AccordionGroup>
  <Accordion title="sonelgaz — invoice form">
    All three fields are required together.

    ```json theme={null}
    {
      "partner": "SONELGAZ",
      "account": {
        "sonelgaz": {
          "invoice_number": "9876543210",
          "amount_without_stamp": "15000",
          "ebb_key": "ABC123"
        }
      },
      "ref": "disc-inv-2026-0042"
    }
    ```

    `invoice_number` up to 20 characters, `amount_without_stamp` up to 20, `ebb_key` up to 30.
  </Accordion>

  <Accordion title="ade — invoice form">
    All four fields are required together.

    ```json theme={null}
    {
      "partner": "ADE",
      "account": {
        "ade": {
          "sub_id": "000123456789",
          "period": "07/2026",
          "amount": "12000",
          "pay_key": "1234567"
        }
      },
      "ref": "disc-inv-2026-0043"
    }
    ```

    `sub_id` exactly 12 characters, `period` in `MM/YYYY` format, `amount` up to 20 characters, `pay_key` exactly 7 characters.
  </Accordion>

  <Accordion title="electronic_payment_key — 25-character key">
    Accepted for ADE and SEAAL as an alternative to `reference`. It must be **exactly 25 characters**.

    ```json theme={null}
    {
      "partner": "ADE",
      "account": { "electronic_payment_key": "0123456789012345678901234" },
      "ref": "disc-inv-2026-0044"
    }
    ```
  </Accordion>
</AccordionGroup>

<Warning>
  **Exactly one identifier.** `reference`, `contractNumber`, `aadlNumber` and `electronic_payment_key` count as the same slot, as do `phoneNumber` and `phone_number`; `sonelgaz` and `ade` are each their own slot. Sending none, or sending two, is rejected with `400`.
</Warning>

## Response

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

<ResponseField name="data" type="object" required>
  <Expandable title="properties">
    <ResponseField name="transactionId" type="string" required>
      The transaction to poll. A 24-character lowercase hexadecimal string.
    </ResponseField>

    <ResponseField name="ref" type="string" required>
      The `ref` you sent, echoed back.
    </ResponseField>

    <ResponseField name="status" type="string" required>
      Always `PENDING` 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/discover \
    -X POST \
    -H "Content-Type: application/json" \
    -H "X-Access-Token: YOUR_API_KEY" \
    -d '{
      "partner": "ADE",
      "account": { "reference": "0123456789012345678901234" },
      "ref": "disc-inv-2026-0042"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://billapi.oneclickdz.com/v3/bills/discover",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Access-Token": process.env.ONECLICKDZ_API_KEY,
      },
      body: JSON.stringify({
        partner: "ADE",
        account: { reference: "0123456789012345678901234" },
        ref: "disc-inv-2026-0042",
      }),
    },
  );

  const body = await response.json();

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

  // Accepted — the bills are not here yet. Poll this id until READY.
  console.log(body.data.transactionId);
  ```

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

  response = requests.post(
      'https://billapi.oneclickdz.com/v3/bills/discover',
      headers={
          'Content-Type': 'application/json',
          'X-Access-Token': os.getenv('ONECLICKDZ_API_KEY')
      },
      json={
          'partner': 'ADE',
          'account': {'reference': '0123456789012345678901234'},
          'ref': 'disc-inv-2026-0042'
      }
  )

  body = response.json()

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

  # Accepted — the bills are not here yet. Poll this id until READY.
  print(body['data']['transactionId'])
  ```

  ```php PHP theme={null}
  <?php
  $payload = [
      'partner' => 'ADE',
      'account' => ['reference' => '0123456789012345678901234'],
      'ref'     => 'disc-inv-2026-0042'
  ];

  $ch = curl_init('https://billapi.oneclickdz.com/v3/bills/discover');
  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 — the bills are not here yet. Poll this id until READY.
  echo $body['data']['transactionId'];
  ?>
  ```
</CodeGroup>

### Success Response

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

Poll [Get Transaction by ID](/en/api-reference/bill-payment/check-by-id) until `status` is `READY`, then read `bills[]`:

```json theme={null}
{
  "success": true,
  "data": {
    "transactionId": "68b2f4c1a7d3e9f204c81a55",
    "ref": "disc-inv-2026-0042",
    "type": "discovery",
    "status": "READY",
    "partner": "ADE",
    "account": { "reference": "0123456789012345678901234" },
    "bills": [
      {
        "billId": "sbx_bill_68b2f4c1a7d3e9f204c81a55_0",
        "amount": 443.39,
        "fee": 30.00,
        "label": "Facture ADE"
      }
    ],
    "currency": "DZD",
    "createdAt": "2026-08-31T10:15:32.194Z",
    "updatedAt": "2026-08-31T10:15:33.008Z",
    "completedAt": "2026-08-31T10:15:33.008Z"
  },
  "meta": {
    "timestamp": "2026-08-31T10:15:34.120Z"
  },
  "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
}
```

## Error Responses

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

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "ERR_VALIDATION",
        "message": "ref is required",
        "details": ["ref is required"]
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    `details` lists every field that failed, not just the first. Common causes: a missing `ref`, a `partner` that is not one of the five values, an `account` with no identifier or with two, an `electronic_payment_key` that is not exactly 25 characters, a `phoneNumber` that is not a valid Algerian landline.

    **What to do:** fix the request. Retrying it unchanged returns the same error.
  </Accordion>

  <Accordion title="400 — Invalid account">
    **The identifier is structurally acceptable but not usable for this partner.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INVALID_ACCOUNT",
        "message": "The provided account identifier is invalid."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** ask the customer to check the number on their bill. This is the error to surface to them; `ERR_VALIDATION` is one for your logs.
  </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"
    }
    ```

    A missing header returns `MISSING_ACCESS_TOKEN` instead, with the same HTTP status.

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

  <Accordion title="403 — Duplicate reference">
    **You have already used this `ref` 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"
    }
    ```

    **What to do:** do **not** retry with a new `ref` blindly — you would start a second discovery for the same account. Look the existing one up with [Get Transaction by Reference](/en/api-reference/bill-payment/check-by-ref) and carry on from its state.
  </Accordion>

  <Accordion title="409 — Bill already paid">
    **This account was already paid recently, so a new discovery is refused.**

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

    **What to do:** this is a guard against double payment, not a failure. Find the successful transaction in [List Transactions](/en/api-reference/bill-payment/list-transactions) and show the customer that receipt.
  </Accordion>

  <Accordion title="409 — Payment in progress">
    **Another payment for this account has not finished yet.**

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

    **What to do:** wait for that payment to reach a final state, then start again. Do not run both in parallel.
  </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:** refresh [List Partners](/en/api-reference/bill-payment/list-partners) and try again later. No transaction was created and nothing was charged. This response carries no `Retry-After`; back off on your side.
  </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 with the same `ref`.
  </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 with the same `ref`.
  </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:** check whether the discovery was created with [Get Transaction by Reference](/en/api-reference/bill-payment/check-by-ref) before retrying, and send the `requestId` to support if it persists.
  </Accordion>
</AccordionGroup>

## The 200 DZD discovery floor

Bills below **200 DZD** are filtered out during discovery and never appear in `bills[]`.

A `READY` transaction with an empty `bills[]` therefore means one of two things, and the API does not distinguish between them:

* the account owes nothing, or
* everything it owes is below the 200 DZD floor.

<Note>
  Word this carefully for your customers. "No bills are payable right now" is accurate; "you owe nothing" is not.
</Note>

## Preventing duplicate requests

`ref` makes a discovery safe to retry. If a network error hides the response, look the `ref` up instead of sending a second discovery.

```javascript theme={null}
async function discoverSafely(partner, account, ref) {
  try {
    const response = await fetch(
      "https://billapi.oneclickdz.com/v3/bills/discover",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Access-Token": process.env.ONECLICKDZ_API_KEY,
        },
        body: JSON.stringify({ partner, account, ref }),
      },
    );

    const body = await response.json();

    if (body.success) {
      return body.data.transactionId;
    }

    // Already started earlier — recover it instead of starting a second one.
    if (body.error.code === "DUPLICATED_REF") {
      return await findByRef(ref, partner);
    }

    throw new Error(`${body.error.code}: ${body.error.message}`);
  } catch (networkError) {
    // The request may still have been accepted. Check before retrying.
    const existing = await findByRef(ref, partner).catch(() => null);
    if (existing) return existing;
    throw networkError;
  }
}

async function findByRef(ref, partner) {
  const url = new URL(
    "https://billapi.oneclickdz.com/v3/bills/transactions/by-ref",
  );
  url.searchParams.set("ref", ref);
  url.searchParams.set("partner", partner);

  const response = await fetch(url, {
    headers: { "X-Access-Token": process.env.ONECLICKDZ_API_KEY },
  });

  const body = await response.json();
  if (!body.success) throw new Error(body.error.code);
  return body.data.transactionId;
}
```

A good `ref` is derived from something you already store — your own invoice or order identifier — so you can always reconstruct it. See [Discovering bills](/en/bill-payment-guides/2-discovering-bills) for a naming recipe.

## Status Lifecycle

<Steps>
  <Step title="PENDING">
    The response you just received. The discovery is queued and running.
  </Step>

  <Step title="READY">
    Discovery finished. `bills[]` is present — possibly empty. Choose a `billId` and call [Pay a Bill](/en/api-reference/bill-payment/pay-bill).
  </Step>

  <Step title="FAILED">
    The discovery could not complete. `error.code` explains why: `INVALID_ACCOUNT`, `PARTNER_UNAVAILABLE`, `BILL_ALREADY_PAID` or `PAYMENT_DECLINED`.
  </Step>
</Steps>

[Full status reference →](/en/api-reference/bill-payment/check-by-id#status-lifecycle)

## Best Practices

<CardGroup cols={2}>
  <Card title="Check the partner first" icon="building-columns">
    A cached partner map lets you hide an unavailable biller before the customer fills in an account number.
  </Card>

  <Card title="Derive the ref, do not invent it" icon="fingerprint">
    Build `ref` from your own order identifier so you can always look the transaction up again.
  </Card>

  <Card title="Never assume 200 means empty" icon="triangle-exclamation">
    The bills arrive in the transaction, not in this response. Poll before you tell a customer anything.
  </Card>

  <Card title="Read fee from the response" icon="calculator">
    Each bill carries its own `fee`. Do not recompute it in your own code.
  </Card>
</CardGroup>

## Related Endpoints

<CardGroup cols={3}>
  <Card title="List Partners" icon="building-columns" href="/en/api-reference/bill-payment/list-partners">
    Check availability first
  </Card>

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

  <Card title="Pay a Bill" icon="money-bill-transfer" href="/en/api-reference/bill-payment/pay-bill">
    Pay one of them
  </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="Discovering Bills" icon="magnifying-glass" href="/en/bill-payment-guides/2-discovering-bills">
    The full walkthrough
  </Card>

  <Card title="Partners and Accounts" icon="address-card" href="/en/bill-payment-guides/1-partners-and-accounts">
    Identifier rules per partner
  </Card>
</CardGroup>
