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

# Send Mobile Top-Up

> Create and send a mobile top-up request

## Overview

Send instant mobile top-ups to Algerian operators (Mobilis, Djezzy, Ooredoo, Pixx) with real-time status tracking.

<Note>
  **Recommended:** Use unique `ref` parameter to prevent duplicate orders and
  enable easy tracking.
</Note>

## Request Body

<ParamField body="plan_code" type="string" required>
  Plan code from `/mobile/plans` endpoint (e.g., `PREPAID_DJEZZY`, `PIXX_500`)
</ParamField>

<ParamField body="MSSIDN" type="string" required>
  Phone number in format `0[567][0-9]{8}` (must be string, not number)
  **Examples:** `"0778037340"`, `"0665983439"`, `"0556121212"`
</ParamField>

<ParamField body="amount" type="integer">
  Top-up amount in DZD (required for dynamic plans, ignored for fixed plans)
  Must be between `min_amount` and `max_amount` from plan details
</ParamField>

<ParamField body="ref" type="string">
  Your unique order reference for tracking (auto-generated if not provided)
  **Important:** Prevents duplicate requests if same ref is submitted twice
</ParamField>

## Response

<ResponseField name="success" type="boolean" required>
  Indicates if the request was successfully submitted
</ResponseField>

<ResponseField name="data" type="object" required>
  <Expandable title="properties">
    <ResponseField name="topupId" type="string">
      Internal top-up identifier for status tracking
    </ResponseField>

    <ResponseField name="topupRef" type="string">
      Your reference (provided ref or auto-generated)
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="meta" type="object" required>
  <Expandable title="properties">
    <ResponseField name="timestamp" type="string">
      Request timestamp in ISO 8601 format
    </ResponseField>
  </Expandable>
</ResponseField>

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.oneclickdz.com/v3/mobile/send \
    -X POST \
    -H "Content-Type: application/json" \
    -H "X-Access-Token: YOUR_API_KEY" \
    -d '{
      "plan_code": "PREPAID_DJEZZY",
      "MSSIDN": "0778037340",
      "amount": 500,
      "ref": "order-12345"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.oneclickdz.com/v3/mobile/send", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Access-Token": "YOUR_API_KEY",
    },
    body: JSON.stringify({
      plan_code: "PREPAID_DJEZZY",
      MSSIDN: "0778037340",
      amount: 500,
      ref: "order-12345",
    }),
  });

  const data = await response.json();
  console.log(data.data.topupId);
  ```

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

  response = requests.post(
      'https://api.oneclickdz.com/v3/mobile/send',
      headers={
          'Content-Type': 'application/json',
          'X-Access-Token': 'YOUR_API_KEY'
      },
      json={
          'plan_code': 'PREPAID_DJEZZY',
          'MSSIDN': '0778037340',
          'amount': 500,
          'ref': 'order-12345'
      }
  )

  topup_id = response.json()['data']['topupId']
  ```

  ```php PHP theme={null}
  <?php
  $data = [
      'plan_code' => 'PREPAID_DJEZZY',
      'MSSIDN' => '0778037340',
      'amount' => 500,
      'ref' => 'order-12345'
  ];

  $ch = curl_init('https://api.oneclickdz.com/v3/mobile/send');
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'Content-Type: application/json',
      'X-Access-Token: YOUR_API_KEY'
  ]);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

  $response = json_decode(curl_exec($ch), true);
  $topupId = $response['data']['topupId'];
  ?>
  ```
</CodeGroup>

### Success Response

```json theme={null}
{
  "success": true,
  "data": {
    "topupId": "6901616fe9e88196b4eb64b0",
    "topupRef": "API-+213665983439-test-1761698159224-success"
  },
  "meta": {
    "timestamp": "2025-10-29T00:35:59.454Z"
  }
}
```

## Error Responses

<AccordionGroup>
  <Accordion title="400 - Validation Errors">
    **Invalid request body or parameters**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "ERR_VALIDATION",
        "message": "body/MSSIDN must match pattern \"^0[567][0-9]{8}$\"",
        "details": {
          "field": "MSSIDN",
          "value": "778037340"
        }
      },
      "requestId": "req_1730160959_xyz789"
    }
    ```

    **Common validation errors:**

    * Missing `plan_code` or `MSSIDN`
    * Invalid phone number format
    * `amount` required for dynamic plans
    * `amount` outside min/max range
    * MSSIDN doesn't match plan operator
  </Accordion>

  <Accordion title="403 - Insufficient Balance">
    **Not enough balance for the operation**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INSUFFICIENT_BALANCE",
        "message": "Your balance is insufficient for this operation",
        "details": {
          "required": 500,
          "available": 250
        }
      },
      "requestId": "req_1730160959_def456"
    }
    ```
  </Accordion>

  <Accordion title="403 - Duplicate Reference">
    **Reference already used**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "DUPLICATED-REF",
        "message": "This reference ID is already in use."
      },
      "requestId": "req_1730160959_ghi789"
    }
    ```

    **Solution:** Check the status of the existing top-up using the same reference
  </Accordion>

  <Accordion title="500 - Internal Error">
    **Server error (rare)**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INTERNAL_ERROR",
        "message": "Developer was notified and will check shortly"
      },
      "requestId": "req_1730160959_jkl012"
    }
    ```

    **Action:** Contact support with the `requestId`. Do NOT refund until investigated.
  </Accordion>
</AccordionGroup>

## Preventing Duplicate Requests

<Info>
  The `ref` field ensures the top-up is processed only once, even in case of network issues.
</Info>

```javascript theme={null}
async function sendTopUpSafe(planCode, phone, amount, orderId) {
  // Use orderId as the ref for idempotency
  const ref = `topup-${orderId}`;

  try {
    const response = await fetch("https://api.oneclickdz.com/v3/mobile/send", {
      method: "POST",
      headers: {
        "X-Access-Token": API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        plan_code: planCode,
        MSSIDN: phone,
        amount,
        ref,
      }),
    });

    const data = await response.json();

    if (data.success) {
      return { success: true, topupId: data.data.topupId };
    }

    // Handle duplicate ref - order already exists
    if (data.error.code === "DUPLICATED-REF") {
      const status = await checkTopUpByRef(ref);
      return { success: true, topupId: status.data._id, fromExisting: true };
    }

    throw new Error(`${data.error.code}: ${data.error.message}`);
  } catch (networkError) {
    // Network error - check if order was created
    const status = await checkTopUpByRef(ref);
    if (status.success) {
      return { success: true, topupId: status.data._id, fromExisting: true };
    }
    throw networkError;
  }
}
```

## Phone Number Format

<Warning>
  Phone number must be a **string**, not a number. Leading zero is required.
</Warning>

**Valid formats:**

* ✅ `"0778037340"` (string with leading zero)
* ✅ `"0665983439"`
* ✅ `"0556121212"`

**Invalid formats:**

* ❌ `778037340` (missing leading zero)
* ❌ `0778037340` (number type instead of string)
* ❌ `"+213778037340"` (international format)
* ❌ `"0778 037 340"` (contains spaces)

## Status Lifecycle

After sending a top-up, it goes through these states:

<Steps>
  <Step title="PENDING">
    Order created and queued for processing **Duration:** 2-15 seconds (typical)
  </Step>

  {" "}

  <Step title="HANDLING">
    Currently being processed by operator **Duration:** 3-8 seconds (typical)
  </Step>

  <Step title="Final State">
    **FULFILLED:** Successfully completed ✅ **REFUNDED:** Failed and refunded
    ❌ **UNKNOWN\_ERROR:** Uncertain state (resolves in 24h) ⚠️
  </Step>
</Steps>

[Learn about Status Tracking →](/en/api-reference/mobile/check-by-ref)

## Recommended Integration Flow

<Steps>
  <Step title="Create Order in Your DB">
    ```javascript theme={null}
    const order = await db.orders.create({
      id: generateOrderId(),
      userId: user.id,
      phone: '0778037340',
      amount: 500,
      status: 'PROCESSING'
    });
    ```
  </Step>

  <Step title="Deduct User Balance">
    ```javascript theme={null}
    await db.users.update({
      id: user.id,
      balance: user.balance - 500
    });
    ```
  </Step>

  <Step title="Return Order to User">
    ```javascript theme={null}
    res.json({
      orderId: order.id,
      status: 'PROCESSING',
      message: 'Top-up is being processed'
    });
    ```
  </Step>

  <Step title="Async: Send to API">
    ```javascript theme={null}
    // In background job
    const response = await sendTopup({
      plan_code: 'PREPAID_DJEZZY',
      MSSIDN: order.phone,
      amount: order.amount,
      ref: order.id
    });

    await db.orders.update({
      id: order.id,
      apiTopupId: response.data.topupId
    });
    ```
  </Step>

  <Step title="Poll Status & Update">
    ```javascript theme={null}
    const status = await checkTopupStatus(order.id);

    await db.orders.update({
      id: order.id,
      status: status.data.status
    });

    if (status.data.status === 'REFUNDED') {
      await refundUserBalance(user.id, order.amount);
    }
    ```
  </Step>
</Steps>

## Sandbox Testing

Enable sandbox mode to test without real transactions:

<CardGroup cols={2}>
  <Card title="Normal Flow" icon="check">
    **Use any normal number** (e.g., `0778037340`) Flow: PENDING (5s) → HANDLING
    (15s) → FULFILLED
  </Card>

  {" "}

  <Card title="Refund with Message" icon="message">
    **Use:** `0600000001` Returns: REFUNDED with `refund_message`
  </Card>

  {" "}

  <Card title="Plan Mismatch" icon="list">
    **Use:** `0600000002` Returns: REFUNDED with `suggested_offers`
  </Card>

  <Card title="Unknown Error" icon="question">
    **Use:** `0600000003` Returns: UNKNOWN\_ERROR status
  </Card>
</CardGroup>

<Tip>
  See the [Mobile Top-Up Workflow
  Guide](../../mobile-topup-workflow#sandbox-testing) for complete sandbox
  testing instructions and examples.
</Tip>

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Unique References" icon="fingerprint">
    Always provide unique `ref` to prevent duplicates and enable easy tracking
  </Card>

  <Card title="Validate Before Submit" icon="circle-check">
    Check plan, phone format, and balance client-side to reduce errors
  </Card>

  <Card title="Handle Async" icon="clock">
    Process API calls asynchronously to avoid blocking user requests
  </Card>

  <Card title="Implement Retry Logic" icon="arrows-rotate">
    Retry failed requests with exponential backoff for network errors
  </Card>
</CardGroup>

## Related Endpoints

<CardGroup cols={3}>
  <Card title="List Plans" icon="list" href="/en/api-reference/mobile/list-plans">
    Get available plans
  </Card>

  <Card title="Check by Reference" icon="magnifying-glass" href="/en/api-reference/mobile/check-by-ref">
    Track with your ref
  </Card>

  <Card title="Check by ID" icon="id-card" href="/en/api-reference/mobile/check-by-id">
    Track with topup ID
  </Card>
</CardGroup>
