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

# Découverte des factures

> Demandez à un facturier ce qu'un compte doit, et lisez le résultat

## Vue d'ensemble

La découverte est la moitié lecture du Paiement de Factures : vous demandez à un facturier ce qu'un compte doit actuellement, et vous recevez en retour une liste de factures payables. Rien n'est débité, et rien n'est engagé.

Cela se passe en deux temps. `POST /v3/bills/discover` accepte la requête et vous donne un `transactionId`. Les factures elles-mêmes arrivent sur cette transaction un instant plus tard, quand son `status` devient `READY`.

<Warning>
  Le `200` de `discover` est un **accusé de réception**. Il ne transporte aucune facture et ne dit rien de ce que le compte doit. Lisez la transaction avant d'annoncer quoi que ce soit à votre client.
</Warning>

## Construire la requête

Trois champs, tous obligatoires.

```json theme={null}
{
  "partner": "ADE",
  "account": { "reference": "0123456789012345678901234" },
  "ref": "disc-inv-2026-0042"
}
```

| Champ     | Règle                                                                                         |
| --------- | --------------------------------------------------------------------------------------------- |
| `partner` | L'un de `ADE`, `SONELGAZ`, `SEAAL`, `AADL`, `Algérie Télécom`                                 |
| `account` | Exactement un identifiant — voir l'[Étape 1](/fr/bill-payment-guides/1-partners-and-accounts) |
| `ref`     | Votre propre référence, 100 caractères au maximum, unique par facturier                       |

## Choisir un `ref`

`ref` est ce qui rend une découverte sûre à réessayer. Si la réponse est perdue, vous consultez le `ref` au lieu d'envoyer une seconde découverte — un `ref` que vous ne pouvez pas reconstruire à partir de vos propres données est donc une transaction que vous ne pouvez pas récupérer.

**Une recette qui fonctionne :** un préfixe fixe, votre propre identifiant de commande ou de facture, et rien d'autre.

```javascript theme={null}
// Deterministic: the same order always produces the same ref.
const discoveryRef = `disc-${order.id}`;
const paymentRef = `pay-${order.id}`;
```

| À faire                                                      | À ne pas faire                                                    |
| ------------------------------------------------------------ | ----------------------------------------------------------------- |
| `disc-inv-2026-0042` — dérivé de votre numéro de facture     | `1693476000000` — un horodatage que vous ne pouvez pas reproduire |
| `disc-order-88213` — dérivé de votre identifiant de commande | `abc123` — une chaîne aléatoire que vous n'avez pas stockée       |
| Gardez les refs de découverte et de paiement distincts       | Réutiliser le `ref` de découverte sur le paiement                 |

<Note>
  Un `ref` est unique par facturier, pas globalement. `disc-inv-2026-0042` pour `ADE` et la même chaîne pour `SONELGAZ` sont deux références différentes. Passer `partner` lors d'une consultation lève toute ambiguïté.
</Note>

## Envoyer la découverte

<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 BASE = "https://billapi.oneclickdz.com";
  const KEY = process.env.ONECLICKDZ_API_KEY;

  async function startDiscovery(partner, account, ref) {
    const response = await fetch(`${BASE}/v3/bills/discover`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Access-Token": KEY,
      },
      body: JSON.stringify({ partner, account, ref }),
    });

    const body = await response.json();

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

    // PENDING — the bills are not here yet.
    return body.data.transactionId;
  }

  const transactionId = await startDiscovery(
    "ADE",
    { reference: "0123456789012345678901234" },
    "disc-inv-2026-0042",
  );
  ```

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

  BASE = 'https://billapi.oneclickdz.com'
  KEY = os.getenv('ONECLICKDZ_API_KEY')


  def start_discovery(partner, account, ref):
      response = requests.post(
          f'{BASE}/v3/bills/discover',
          headers={
              'Content-Type': 'application/json',
              'X-Access-Token': KEY
          },
          json={'partner': partner, 'account': account, 'ref': ref}
      )

      body = response.json()

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

      # PENDING — the bills are not here yet.
      return body['data']['transactionId']


  transaction_id = start_discovery(
      'ADE',
      {'reference': '0123456789012345678901234'},
      'disc-inv-2026-0042'
  )
  ```

  ```php PHP theme={null}
  <?php
  const BASE = 'https://billapi.oneclickdz.com';

  function startDiscovery(string $partner, array $account, string $ref): string
  {
      $ch = curl_init(BASE . '/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([
          'partner' => $partner,
          'account' => $account,
          'ref'     => $ref
      ]));

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

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

      // PENDING — the bills are not here yet.
      return $body['data']['transactionId'];
  }

  $transactionId = startDiscovery(
      'ADE',
      ['reference' => '0123456789012345678901234'],
      'disc-inv-2026-0042'
  );
  ?>
  ```
</CodeGroup>

```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"
}
```

## Interroger jusqu'à `READY`

Lisez la transaction jusqu'à ce que son `status` quitte `PENDING`. Une découverte aboutit normalement en quelques secondes.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://billapi.oneclickdz.com/v3/bills/transactions/68b2f4c1a7d3e9f204c81a55 \
    -H "X-Access-Token: YOUR_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

  async function waitForBills(transactionId, { timeoutMs = 90_000 } = {}) {
    const deadline = Date.now() + timeoutMs;
    let interval = 2000;

    while (Date.now() < deadline) {
      const response = await fetch(
        `${BASE}/v3/bills/transactions/${transactionId}`,
        { headers: { "X-Access-Token": KEY } },
      );

      const body = await response.json();
      if (!body.success) throw new Error(body.error.code);

      const transaction = body.data;

      if (transaction.status === "READY") return transaction.bills;
      if (transaction.status === "FAILED") {
        throw new Error(transaction.error?.code ?? "FAILED");
      }

      await sleep(interval);
      interval = Math.min(interval * 1.5, 10_000);
    }

    throw new Error("Discovery did not finish in time");
  }

  const bills = await waitForBills(transactionId);
  ```

  ```python Python theme={null}
  import time


  def wait_for_bills(transaction_id, timeout_s=90):
      deadline = time.monotonic() + timeout_s
      interval = 2.0

      while time.monotonic() < deadline:
          response = requests.get(
              f'{BASE}/v3/bills/transactions/{transaction_id}',
              headers={'X-Access-Token': KEY}
          )

          body = response.json()
          if not body['success']:
              raise RuntimeError(body['error']['code'])

          transaction = body['data']

          if transaction['status'] == 'READY':
              return transaction.get('bills', [])
          if transaction['status'] == 'FAILED':
              raise RuntimeError(transaction.get('error', {}).get('code', 'FAILED'))

          time.sleep(interval)
          interval = min(interval * 1.5, 10.0)

      raise TimeoutError('Discovery did not finish in time')


  bills = wait_for_bills(transaction_id)
  ```

  ```php PHP theme={null}
  <?php
  function waitForBills(string $transactionId, int $timeoutSeconds = 90): array
  {
      $deadline = time() + $timeoutSeconds;
      $interval = 2;

      while (time() < $deadline) {
          $ch = curl_init(BASE . "/v3/bills/transactions/$transactionId");
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
          curl_setopt($ch, CURLOPT_HTTPHEADER, [
              'X-Access-Token: ' . getenv('ONECLICKDZ_API_KEY')
          ]);

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

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

          $transaction = $body['data'];

          if ($transaction['status'] === 'READY') {
              return $transaction['bills'] ?? [];
          }
          if ($transaction['status'] === 'FAILED') {
              throw new Exception($transaction['error']['code'] ?? 'FAILED');
          }

          sleep($interval);
          $interval = min((int) ceil($interval * 1.5), 10);
      }

      throw new Exception('Discovery did not finish in time');
  }

  $bills = waitForBills($transactionId);
  ?>
  ```
</CodeGroup>

## Lire `bills[]`

Une transaction `READY` porte les factures payables à cet instant.

```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"
}
```

| Champ    | Signification                                                     |
| -------- | ----------------------------------------------------------------- |
| `billId` | Ce que vous envoyez à `pay`. Copiez-le — ne le construisez jamais |
| `amount` | Ce qui est dû au facturier, en DZD                                |
| `fee`    | Les frais de service pour payer cette facture, en DZD             |
| `label`  | Une description lisible, quand le facturier en a fourni une       |
| `period` | La période de facturation, quand le facturier en a fourni une     |

Montrez `amount + fee` au client. Cette somme est ce qui sera débité, et elle est renvoyée comme `total` sur la transaction une fois qu'une facture est sélectionnée.

<Note>
  `bills[]` n'est présent **que** tant que `status` vaut `READY`. Une fois qu'un paiement démarre, la transaction porte `selectedBill` à la place. Lisez les factures tant que vous les avez.
</Note>

## Quand `bills[]` est vide

Un tableau vide est un résultat normal et réussi — pas une erreur.

```json theme={null}
{
  "status": "READY",
  "bills": []
}
```

Cela signifie l'une de deux choses, et l'API ne fait pas la distinction entre elles :

* le compte ne doit rien, ou
* tout ce que le compte doit est sous le **plancher de découverte de 200 DZD**.

Les factures inférieures à 200 DZD sont filtrées pendant la découverte et n'apparaissent jamais.

<Warning>
  Formulez cela avec soin. « Aucune facture n'est payable pour le moment » est exact. « Vous ne devez rien » ne l'est pas — une facture de 150 DZD existe mais ne peut pas être payée via cette API.
</Warning>

```javascript theme={null}
if (bills.length === 0) {
  return {
    message: "No bills are payable for this account right now.",
    // Not: "This account has no outstanding balance."
  };
}
```

## Récupérer une réponse perdue

Si la requête de découverte échoue d'une manière que vous ne pouvez pas expliquer — un timeout, un crash, un redéploiement — demandez ce qu'est devenu le `ref`. N'envoyez jamais une seconde découverte.

```javascript theme={null}
async function discoverSafely(partner, account, ref) {
  try {
    return await startDiscovery(partner, account, ref);
  } catch (error) {
    const existing = await findByRef(ref, partner);
    if (existing) return existing.transactionId;
    throw error;
  }
}

async function findByRef(ref, partner) {
  const url = new URL(`${BASE}/v3/bills/transactions/by-ref`);
  url.searchParams.set("ref", ref);
  url.searchParams.set("partner", partner);

  const response = await fetch(url, { headers: { "X-Access-Token": KEY } });
  const body = await response.json();

  if (body.success) return body.data;
  if (body.error.code === "NOT_FOUND") return null; // Never landed — safe to resend.
  throw new Error(body.error.code);
}
```

La même consultation répond à `403 DUPLICATED_REF`. Cette erreur signifie que la découverte existe déjà ; ce n'est jamais une raison de réessayer avec un `ref` différent, ce qui lancerait une seconde découverte pour le même compte.

## Les erreurs que vous rencontrerez

| Erreur                | HTTP | Ce que cela signifie                                 | Que faire                                                 |
| --------------------- | ---- | ---------------------------------------------------- | --------------------------------------------------------- |
| `ERR_VALIDATION`      | 400  | Le corps ne correspond pas au schéma                 | Corrigez la requête ; ne réessayez jamais sans changement |
| `INVALID_ACCOUNT`     | 400  | L'identifiant n'est pas utilisable pour ce facturier | Demandez au client de vérifier sa facture                 |
| `DUPLICATED_REF`      | 403  | Ce `ref` existe déjà pour ce facturier               | Consultez-le ; ne le renvoyez pas                         |
| `BILL_ALREADY_PAID`   | 409  | Ce compte a déjà été payé récemment                  | Retrouvez la transaction payée et montrez son reçu        |
| `PAYMENT_IN_PROGRESS` | 409  | Un autre paiement pour ce compte est en cours        | Attendez qu'il se termine                                 |
| `PARTNER_UNAVAILABLE` | 503  | Le facturier est injoignable ou désactivé            | Réessayez plus tard ; rien n'a été créé                   |
| `AUTH_UNAVAILABLE`    | 503  | Nous n'avons pas pu vérifier votre clé à temps       | Respectez `Retry-After` et réessayez la même requête      |
| `SERVICE_UNAVAILABLE` | 503  | Maintenance planifiée                                | Respectez `Retry-After` et réessayez                      |

Une découverte `FAILED` porte sa raison dans `error.code` : `INVALID_ACCOUNT`, `PARTNER_UNAVAILABLE`, `BILL_ALREADY_PAID` ou `PAYMENT_DECLINED`.

[Chaque code, avec des exemples de corps →](/fr/api-reference/error-handling)

## Bonnes pratiques

<CardGroup cols={2}>
  <Card title="Dérivez le ref" icon="fingerprint">
    Construisez-le à partir de votre propre identifiant de commande afin de pouvoir toujours le reconstruire après un échec.
  </Card>

  <Card title="Interrogez, ne renvoyez jamais" icon="arrows-rotate">
    Une découverte lente n'est pas une découverte perdue. Lisez la transaction au lieu d'envoyer une autre requête.
  </Card>

  <Card title="Ne mettez rien en cache au sujet des factures" icon="clock">
    Une découverte est un instantané. Si le client attend, relancez une découverte plutôt que de payer sur des chiffres périmés.
  </Card>

  <Card title="Dites payable, pas dû" icon="quote-left">
    Un `bills[]` vide signifie que rien n'est payable. Cela ne signifie pas que le compte ne doit rien.
  </Card>
</CardGroup>

## Étape suivante

<Card title="Étape 3 : Paiement des factures" icon="money-bill-transfer" href="/fr/bill-payment-guides/3-paying-bills">
  Choisissez une facture, confirmez le total, et soumettez le paiement en toute sécurité
</Card>

## Pages liées

<CardGroup cols={2}>
  <Card title="Découvrir les factures" icon="magnifying-glass-dollar" href="/fr/api-reference/bill-payment/discover-bills">
    La référence de l'endpoint
  </Card>

  <Card title="Obtenir une transaction par ID" icon="id-card" href="/fr/api-reference/bill-payment/check-by-id">
    L'objet transaction en entier
  </Card>

  <Card title="Obtenir une transaction par référence" icon="tag" href="/fr/api-reference/bill-payment/check-by-ref">
    Le chemin de récupération
  </Card>

  <Card title="Partenaires et comptes" icon="address-card" href="/fr/bill-payment-guides/1-partners-and-accounts">
    Les règles d'identifiant par facturier
  </Card>
</CardGroup>
