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

# Partners and Accounts

> Check biller availability and build a valid account identifier

## Overview

Before you can discover anything, two things have to be right: the biller has to be available, and the account identifier has to be the one that biller understands. This step covers both, and the difference between the two errors you will see when the second one is wrong.

<Note>
  Everything on this page uses `https://billapi.oneclickdz.com` and the `X-Access-Token` header. If you have not confirmed which environment your key belongs to, start with [Validate API Key](/en/api-reference/bill-payment/validate-key).
</Note>

## Checking availability

`GET /v3/partners` returns one entry per biller, each with a single `status` field.

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

  ```javascript Node.js theme={null}
  const response = await fetch("https://billapi.oneclickdz.com/v3/partners", {
    headers: { "X-Access-Token": process.env.ONECLICKDZ_API_KEY },
  });

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

  const available = Object.entries(body.data)
    .filter(([, partner]) => partner.status === "ACTIVE")
    .map(([name]) => name);

  console.log(available);
  ```

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

  response = requests.get(
      'https://billapi.oneclickdz.com/v3/partners',
      headers={'X-Access-Token': os.getenv('ONECLICKDZ_API_KEY')}
  )

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

  available = [
      name for name, partner in body['data'].items()
      if partner['status'] == 'ACTIVE'
  ]

  print(available)
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init('https://billapi.oneclickdz.com/v3/partners');
  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']);
  }

  $available = array_keys(array_filter(
      $body['data'],
      fn($partner) => $partner['status'] === 'ACTIVE'
  ));

  print_r($available);
  ?>
  ```
</CodeGroup>

```json theme={null}
{
  "success": true,
  "data": {
    "ADE": { "status": "ACTIVE" },
    "SONELGAZ": { "status": "ACTIVE" },
    "SEAAL": { "status": "UNAVAILABLE" },
    "AADL": { "status": "UNAVAILABLE" },
    "Algérie Télécom": { "status": "ACTIVE" }
  },
  "meta": { "timestamp": "2026-08-31T10:15:32.194Z" },
  "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
}
```

A biller marked `UNAVAILABLE` answers `503 PARTNER_UNAVAILABLE` on discovery and on payment. `SEAAL` and `AADL` are currently `UNAVAILABLE` in both sandbox and production — this is the operator's current setting, not a permanent limitation, so keep them in your code and let the map decide what to show.

<Warning>
  A `PRODUCTION` key sees live availability; a `SANDBOX` key sees a fixed map, because a sandbox request never reaches a biller. Do not use sandbox to test how your application reacts to a biller going down — use the dedicated [sandbox scenarios](/en/bill-payment-guides/6-sandbox-testing) for that.
</Warning>

## The identifier for each biller

Send exactly one identifier, and send the field that belongs to the biller you are querying. The API returns the same field back in `account` on every transaction for that biller.

| Partner           | Field            | Format                                         | Valid example                 |
| ----------------- | ---------------- | ---------------------------------------------- | ----------------------------- |
| `ADE`             | `reference`      | Up to 50 characters                            | `"0123456789012345678901234"` |
| `SEAAL`           | `reference`      | Up to 50 characters                            | `"0123456789012340000000000"` |
| `SONELGAZ`        | `contractNumber` | Up to 50 characters                            | `"9876543210"`                |
| `AADL`            | `aadlNumber`     | Up to 50 characters                            | `"1112223334"`                |
| `Algérie Télécom` | `phoneNumber`    | `0` or `+213`, then a digit 2–4, then 7 digits | `"023456789"`                 |

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

<Note>
  Send `Algérie Télécom` with its accents. The value is compared character for character, and it is also the key you read back from the partner map.
</Note>

### Landline numbers

`phoneNumber` is an Algerian **landline**, not a mobile number.

**Valid:**

* `"023456789"` — leading zero, then a digit in the range 2–4
* `"+213023456789"` is not valid; use `"+21323456789"` or the local `"023456789"` form

**Invalid:**

* `"0778037340"` — a mobile number, not a landline
* `"23456789"` — missing the leading zero
* `"023 45 67 89"` — contains spaces
* `23456789` — a number instead of a string

### The richer identifier forms

Two billers accept a fuller object when a single number is not enough to identify a specific bill. Every field inside these objects is required.

<AccordionGroup>
  <Accordion title="SONELGAZ — invoice form" icon="bolt">
    ```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" icon="file-lines">
    ```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="ADE and SEAAL — 25-character key" icon="key">
    `electronic_payment_key` is accepted as an alternative to `reference`, and must be **exactly 25 characters**.

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

## Exactly one identifier

The `account` object must carry **one** identifier and no more. The fields group into four slots:

| Slot                | Fields                                                                |
| ------------------- | --------------------------------------------------------------------- |
| Reference-style key | `reference`, `contractNumber`, `aadlNumber`, `electronic_payment_key` |
| Landline            | `phoneNumber`, `phone_number`                                         |
| SONELGAZ invoice    | `sonelgaz`                                                            |
| ADE invoice         | `ade`                                                                 |

Exactly one slot must be filled. Zero or two is rejected before anything else happens.

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

```json theme={null}
{
  "success": false,
  "error": {
    "code": "ERR_VALIDATION",
    "message": "account must contain exactly one identifier (electronic_payment_key, phone_number, sonelgaz, or ade)",
    "details": [
      "account must contain exactly one identifier (electronic_payment_key, phone_number, sonelgaz, or ade)"
    ]
  },
  "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
}
```

Validating this on your own side is one line, and it turns a round trip into an instant form error:

```javascript theme={null}
const SLOTS = [
  ["reference", "contractNumber", "aadlNumber", "electronic_payment_key"],
  ["phoneNumber", "phone_number"],
  ["sonelgaz"],
  ["ade"],
];

function hasExactlyOneIdentifier(account) {
  const filled = SLOTS.filter((slot) =>
    slot.some((field) => {
      const value = account[field];
      return value !== undefined && value !== null && value !== "";
    }),
  );

  return filled.length === 1;
}
```

## `ERR_VALIDATION` or `INVALID_ACCOUNT`?

Both are `400`, and they mean very different things.

|                      | `ERR_VALIDATION`                                                                                     | `INVALID_ACCOUNT`                               |
| -------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| Cause                | The request does not match the schema                                                                | The identifier is well-formed but not usable    |
| Examples             | No `ref`; two identifiers; a 24-character `electronic_payment_key`; a mobile number in `phoneNumber` | An account number the biller does not recognise |
| Whose mistake        | Your integration's                                                                                   | Your customer's typing                          |
| Show to the customer | No — log it                                                                                          | Yes — "check the number on your bill"           |
| Retry unchanged      | Never                                                                                                | Only after the customer corrects the number     |

<Warning>
  Do not show `ERR_VALIDATION` messages to end customers. They name internal field names such as `electronic_payment_key`, which means nothing to someone holding a paper bill.
</Warning>

## Caching the partner map

Availability changes rarely, so refresh it on a timer instead of before every discovery — and keep serving the last good copy if a refresh fails. An empty biller list is worse for your customers than a slightly stale one.

```javascript theme={null}
let cache = { map: null, fetchedAt: 0 };
const TTL_MS = 5 * 60 * 1000;

async function getPartners() {
  if (cache.map && Date.now() - cache.fetchedAt < TTL_MS) return cache.map;

  try {
    const response = await fetch("https://billapi.oneclickdz.com/v3/partners", {
      headers: { "X-Access-Token": process.env.ONECLICKDZ_API_KEY },
    });

    const body = await response.json();
    if (body.success) cache = { map: body.data, fetchedAt: Date.now() };
  } catch {
    // Serve the previous map rather than blocking the customer.
  }

  return cache.map ?? {};
}
```

<Note>
  Caching does not remove the need to handle `503 PARTNER_UNAVAILABLE` on discovery. A biller can go down between your last refresh and the customer pressing the button.
</Note>

## Best practices

<CardGroup cols={2}>
  <Card title="Validate before you send" icon="circle-check">
    Check the slot rule and the landline format client-side. It saves a round trip and gives a better error message.
  </Card>

  <Card title="Cache the map for minutes, not hours" icon="database">
    Five minutes is plenty. Refresh in the background, never on the customer's critical path.
  </Card>

  <Card title="Keep unavailable billers in your code" icon="toggle-on">
    `SEAAL` and `AADL` will come back. Drive the UI from the map, not from a hard-coded list.
  </Card>

  <Card title="Separate the two 400s" icon="arrows-split">
    `INVALID_ACCOUNT` is a message for your customer. `ERR_VALIDATION` is a message for your logs.
  </Card>
</CardGroup>

## Next step

<Card title="Step 2: Discovering bills" icon="magnifying-glass-dollar" href="/en/bill-payment-guides/2-discovering-bills">
  Send a discovery, poll it to `READY`, and read what is payable
</Card>

## Related pages

<CardGroup cols={2}>
  <Card title="List Partners" icon="building-columns" href="/en/api-reference/bill-payment/list-partners">
    The endpoint reference
  </Card>

  <Card title="Discover Bills" icon="magnifying-glass-dollar" href="/en/api-reference/bill-payment/discover-bills">
    Where the account object is sent
  </Card>

  <Card title="Bill Payment Overview" icon="file-invoice-dollar" href="/en/bill-payment-guides/overview">
    The five-step map
  </Card>

  <Card title="Sandbox Testing" icon="flask" href="/en/bill-payment-guides/6-sandbox-testing">
    Identifiers that produce a chosen outcome
  </Card>
</CardGroup>
