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

# Check Status by ID

> Check mobile top-up status using internal top-up ID

## Overview

Track mobile top-up status using the `topupId` returned from the send endpoint. Generally, our servers will process your request in about 5-30 seconds.

<Warning>
  **Important:** It's mandatory to understand all possible statuses to properly handle updating and notifying your users.
</Warning>

## Path Parameters

<ParamField path="id" type="string" required>
  Internal top-up ID from `/mobile/send` response
</ParamField>

## Response

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

<ResponseField name="data" type="object" required>
  <Expandable title="properties">
    <ResponseField name="_id" type="string" required>
      Top-up identifier
    </ResponseField>

    <ResponseField name="ref" type="string">
      Your custom reference (if provided)
    </ResponseField>

    <ResponseField name="status" type="string" required>
      Current status: `PENDING`, `HANDLING`, `FULFILLED`, `REFUNDED`, or
      `UNKNOWN_ERROR`
    </ResponseField>

    <ResponseField name="plan_code" type="string" required>
      Plan code used
    </ResponseField>

    <ResponseField name="MSSIDN" type="string" required>
      Phone number
    </ResponseField>

    <ResponseField name="topup_amount" type="number" required>
      Top-up amount in DZD
    </ResponseField>

    <ResponseField name="balance_amount" type="number" required>
      Amount deducted from your balance
    </ResponseField>

    <ResponseField name="created_at" type="string" required>
      Creation timestamp (ISO 8601)
    </ResponseField>

    <ResponseField name="refund_message" type="string">
      Arabic message for end-user (only if REFUNDED or UNKNOWN\_ERROR)
    </ResponseField>

    <ResponseField name="suggested_offers" type="array">
      Alternative plans if plan mismatch (only if REFUNDED)

      <Expandable title="properties">
        <ResponseField name="typename" type="string">
          Display name of the suggested plan
        </ResponseField>

        <ResponseField name="plan_code" type="string">
          Plan code to use
        </ResponseField>

        <ResponseField name="amount" type="number">
          Suggested amount
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="follow_orderid" type="string">
      Follow-up order ID if order was redirected
    </ResponseField>

    <ResponseField name="retry_orderid" type="string">
      Retry order ID if order was retried
    </ResponseField>
  </Expandable>
</ResponseField>

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

## Status Values

<AccordionGroup>
  <Accordion title="PENDING" icon="clock">
    The `PENDING` status indicates that the order has been created and is currently in the queue, awaiting processing by one of our servers. It will be processed as soon as possible.

    **Typical duration:** 2-15 seconds

    **Action:** Continue polling every 5-10 seconds
  </Accordion>

  <Accordion title="HANDLING" icon="gear">
    The `HANDLING` status indicates that the order is currently being processed by one of our servers.

    **Typical duration:** 3-8 seconds

    **Action:** Continue polling every 5-10 seconds
  </Accordion>

  <Accordion title="FULFILLED" icon="circle-check">
    The `FULFILLED` status indicates that the mobile top-up has been successfully sent. ✅

    **Action:** Update order status to completed, notify user
  </Accordion>

  <Accordion title="REFUNDED" icon="rotate-left">
    The `REFUNDED` status indicates that the mobile top-up has been refunded. ❌

    **Fields available:**

    * `refund_message`: Show this to user (in Arabic)
    * `suggested_offers`: Alternative plans (if plan mismatch)

    **Action:** Refund user, display message, offer alternatives
  </Accordion>

  <Accordion title="UNKNOWN_ERROR" icon="triangle-exclamation">
    The `UNKNOWN_ERROR` status indicates that the mobile top-up might have been successful or failed. The status will update within 1 to 12 hours to either FULFILLED or REFUNDED after manual check by our support team. We will be notified immediately and no action from your part is required. ⚠️

    Such errors happen due to the nature of AT Commands and unexpected behaviors from operators (new messages, network issues, etc.).

    <Warning>
      **YOU SHOULD NOT REFUND THE AMOUNT** to your user at this status!
    </Warning>

    **Action:**

    * Show `refund_message` to user
    * DO NOT refund immediately
    * Set a daily cronjob to check UNKNOWN\_ERROR orders and update to FULFILLED or REFUNDED
    * Check again after 24 hours
    * Then handle refunds if status becomes REFUNDED
  </Accordion>
</AccordionGroup>

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.oneclickdz.com/v3/mobile/check-id/6901616fe9e88196b4eb64b0 \
    -H "X-Access-Token: YOUR_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const topupId = "6901616fe9e88196b4eb64b0";
  const response = await fetch(
    `https://api.oneclickdz.com/v3/mobile/check-id/${topupId}`,
    { headers: { "X-Access-Token": "YOUR_API_KEY" } }
  );
  const { data } = await response.json();
  console.log(data.status);
  ```

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

  topup_id = '6901616fe9e88196b4eb64b0'
  response = requests.get(
      f'https://api.oneclickdz.com/v3/mobile/check-id/{topup_id}',
      headers={'X-Access-Token': 'YOUR_API_KEY'}
  )
  status = response.json()['data']['status']
  ```

  ```php PHP theme={null}
  <?php
  $topupId = '6901616fe9e88196b4eb64b0';
  $ch = curl_init("https://api.oneclickdz.com/v3/mobile/check-id/{$topupId}");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Access-Token: YOUR_API_KEY']);
  $response = json_decode(curl_exec($ch), true);
  $status = $response['data']['status'];
  ?>
  ```
</CodeGroup>

### Fulfilled Response

```json theme={null}
{
  "success": true,
  "data": {
    "_id": "6901616fe9e88196b4eb64b0",
    "ref": "order-12345",
    "status": "FULFILLED",
    "plan_code": "PREPAID_DJEZZY",
    "MSSIDN": "0778037340",
    "topup_amount": 500,
    "balance_amount": 490,
    "created_at": "2025-10-29T00:35:59.378Z"
  },
  "meta": {
    "timestamp": "2025-10-29T00:36:15.606Z"
  },
  "requestId": "req_1730160975_abc123"
}
```

### Refunded Response

```json theme={null}
{
  "success": true,
  "data": {
    "_id": "6901616fe9e88196b4eb64b0",
    "ref": "order-12345",
    "status": "REFUNDED",
    "plan_code": "PREPAID_DJEZZY",
    "MSSIDN": "0778037340",
    "topup_amount": 500,
    "balance_amount": 490,
    "created_at": "2025-10-29T00:35:59.378Z",
    "refund_message": "الرصيد المطلوب غير متوفر لهذا الرقم"
  },
  "meta": {
    "timestamp": "2025-10-29T00:36:15.606Z"
  },
  "requestId": "req_1730160975_def456"
}
```

### Refunded with Suggested Offers

When the plan code doesn't match the phone number type, you'll receive suggested alternatives:

```json theme={null}
{
  "success": true,
  "data": {
    "_id": "6901616fe9e88196b4eb64b0",
    "ref": "order-12345",
    "status": "REFUNDED",
    "plan_code": "PREPAID_DJEZZY",
    "MSSIDN": "0778037340",
    "topup_amount": 500,
    "balance_amount": 490,
    "created_at": "2025-10-29T00:35:59.378Z",
    "refund_message": "هذا العرض غير متوافق مع هذا الرقم جرب فاتورة",
    "suggested_offers": [
      {
        "typename": "📋 FACTURE | فاتورة",
        "plan_code": "FACTURE_DJEZZY",
        "amount": 500
      }
    ]
  },
  "meta": {
    "timestamp": "2025-10-29T00:36:15.606Z"
  },
  "requestId": "req_1730160975_ghi789"
}
```

<Note>
  **PLEASE NOTE:** `suggested_offers` is an array and can contain multiple items, for example when you send GETMENU plans.
</Note>

## Polling Strategy

### Syncing Our API with Your Backend

<Steps>
  <Step title="Start Polling">
    Begin checking status immediately after sending top-up. One simple approach is setting an interval of 5-10 seconds on your frontend to check the status. Once the status is FULFILLED, REFUNDED, or UNKNOWN\_ERROR, clear the interval and stop sending requests.

    ```javascript theme={null}
    const topupId = response.data.topupId;
    const status = await pollStatus(topupId);
    ```
  </Step>

  <Step title="Poll Interval">
    Check every 5-10 seconds while PENDING or HANDLING. This approach will keep requests minimal (5-10 per transaction).

    On each GET request from your users, you can read the status in your database. Only if the status is not `FULFILLED`, `REFUNDED`, or `UNKNOWN_ERROR`, send a check request to our API, update your database, then reply to your user.

    ```javascript theme={null}
    async function pollStatus(topupId, maxAttempts = 60) {
      for (let i = 0; i < maxAttempts; i++) {
        const { data } = await checkTopupStatus(topupId);
        
        if (['FULFILLED', 'REFUNDED', 'UNKNOWN_ERROR'].includes(data.status)) {
          return data;
        }
        
        await sleep(5000); // Wait 5 seconds
      }
      throw new Error('Timeout');
    }
    ```
  </Step>

  <Step title="Handle Result">
    Update order based on final status

    ```javascript theme={null}
    if (status === 'FULFILLED') {
      await markOrderComplete(orderId);
    } else if (status === 'REFUNDED') {
      await refundUser(orderId);
      await notifyUser(refund_message);
    } else if (status === 'UNKNOWN_ERROR') {
      await markForReview(orderId);
      // DO NOT refund yet - wait for daily cronjob
    }
    ```
  </Step>
</Steps>

## Complete Polling Example

<CodeGroup>
  ```javascript Node.js theme={null}
  async function pollTopupStatus(topupId) {
    const maxAttempts = 60; // 5 minutes max
    const pollInterval = 5000; // 5 seconds

    for (let i = 0; i < maxAttempts; i++) {
      const response = await fetch(
        `https://api.oneclickdz.com/v3/mobile/check-id/${topupId}`,
        { headers: { "X-Access-Token": process.env.API_KEY } }
      );

      const { data } = await response.json();

      // Final states
      if (["FULFILLED", "REFUNDED", "UNKNOWN_ERROR"].includes(data.status)) {
        return data;
      }

      // Still processing
      console.log(`Attempt ${i + 1}: Status is ${data.status}`);
      await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }

    throw new Error("Polling timeout after 5 minutes");
  }

  // Usage
  try {
    const result = await pollTopupStatus("6901616fe9e88196b4eb64b0");
    console.log("Final status:", result.status);
  } catch (error) {
    console.error("Polling failed:", error.message);
  }
  ```

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

  def poll_topup_status(topup_id, max_attempts=60, poll_interval=5):
      """Poll top-up status until completion or timeout"""
      url = f'https://api.oneclickdz.com/v3/mobile/check-id/{topup_id}'
      headers = {'X-Access-Token': 'YOUR_API_KEY'}

      for attempt in range(max_attempts):
          response = requests.get(url, headers=headers)
          data = response.json()['data']

          # Check if final state
          if data['status'] in ['FULFILLED', 'REFUNDED', 'UNKNOWN_ERROR']:
              return data

          # Still processing
          print(f"Attempt {attempt + 1}: Status is {data['status']}")
          time.sleep(poll_interval)

      raise TimeoutError('Polling timeout after 5 minutes')

  # Usage
  try:
      result = poll_topup_status('6901616fe9e88196b4eb64b0')
      print(f"Final status: {result['status']}")
  except TimeoutError as e:
      print(f"Error: {e}")
  ```
</CodeGroup>

## Handling UNKNOWN\_ERROR

<Warning>Do NOT refund immediately when status is UNKNOWN\_ERROR!</Warning>

The `UNKNOWN_ERROR` status indicates uncertainty about whether the top-up succeeded or failed. This happens due to the nature of AT Commands and unexpected behaviors from operators (new messages, network issues, etc.).

<Steps>
  <Step title="Show Message">
    Display `refund_message` to user explaining the delay

    Example message: "هذه العملية حالتها غير معروفة، يرجى التأكد مع عميلك، ولن نتحمل أي مسؤولية عن أي خسائر. إذا فشلت العملية بالفعل، فلا تقلق، سيتم استرداد أموالك خلال 24 ساعة"
  </Step>

  <Step title="Mark for Review">
    Flag the order for manual review or automated recheck. We will be notified immediately and no action from your part is required.
  </Step>

  <Step title="Automated Recheck">
    Set up daily cronjob to check uncertain orders. The status will update within 1-12 hours (up to 24h) to either FULFILLED or REFUNDED after manual check by our support team.

    ```javascript theme={null}
    // Daily at midnight
    cron.schedule('0 0 * * *', async () => {
      const uncertainOrders = await db.orders.find({
        status: 'UNKNOWN_ERROR',
        createdAt: { $lt: new Date(Date.now() - 24*60*60*1000) }
      });
      
      for (const order of uncertainOrders) {
        const status = await checkTopupStatus(order.apiTopupId);
        await updateOrderStatus(order.id, status.data.status);
        
        // Now handle refund if status is REFUNDED
        if (status.data.status === 'REFUNDED') {
          await processRefund(order);
        }
      }
    });
    ```
  </Step>

  <Step title="Final Resolution">
    Status will update to FULFILLED or REFUNDED within 24 hours. Only then should you process refunds if the final status is REFUNDED.
  </Step>
</Steps>

## Best Practices

<CardGroup cols={2}>
  <Card title="Cache Status" icon="database">
    Store status in your database to minimize API calls. Only query our API when status is not final (PENDING/HANDLING).
  </Card>

  <Card title="Stop on Final State" icon="hand">
    Don't poll after FULFILLED, REFUNDED, or UNKNOWN\_ERROR
  </Card>

  <Card title="Timeout Protection" icon="timer">
    Set maximum polling attempts (typically 60 = 5 minutes)
  </Card>

  <Card title="Error Handling" icon="shield">
    Handle network errors with retry logic
  </Card>

  <Card title="Show Arabic Messages" icon="language">
    Always display refund\_message to users - it's in Arabic and explains the issue
  </Card>

  <Card title="Offer Alternatives" icon="arrows-split">
    When suggested\_offers is present, update your UI to show these plans instead of all plans
  </Card>
</CardGroup>

## Handling Refunds

A `refund_message` (Arabic message) will be added to the top-up operation, and you should show it as-is to your client.

**Example scenarios:**

### Wrong Phone Number

If your client types a wrong phone number:

```
refund_message: "رقم الهاتف خاطئ، يرجى التأكد مع العميل"
```

### Plan Mismatch

If trying to send PREPAID\_DJEZZY to a facture (postpaid) phone number:

```json theme={null}
{
  "refund_message": "هذا العرض غير متوافق مع هذا الرقم جرب فاتورة",
  "suggested_offers": [
    {
      "typename": "📋 FACTURE | فاتورة",
      "plan_code": "FACTURE_DJEZZY",
      "amount": 500
    }
  ]
}
```

### Suggested Offers Array

The `suggested_offers` array shows the correct plans for the phone number. Update your app UI to represent these new plans instead of showing all plans.

<Note>
  `suggested_offers` is an array and can contain multiple items, especially when you send GETMENU requests.
</Note>

## Sandbox Testing

Enable the sandbox at your [settings page](https://app.oneclickdz.com/#/settings) to test requests without affecting your balance.

All operations will emulate a real operation:

<Steps>
  <Step title="Normal Flow">
    **Any normal phone number:**

    * First 5 seconds: status `PENDING`
    * First 15 seconds: status `HANDLING`
    * Then: status `FULFILLED`

    After integrating the fulfilled flow, proceed to test other statuses.
  </Step>

  <Step title="Test Refund with Message">
    **Phone: 0600000001**

    Test REFUNDED status with `refund_message` that should be shown to client
  </Step>

  <Step title="Test Refund with Suggestions">
    **Phone: 0600000002**

    Test REFUNDED status with `refund_message` and `suggested_offers`
  </Step>

  <Step title="Test Unknown Error">
    **Phone: 0600000003**

    Test `UNKNOWN_ERROR` status to ensure your daily cronjob logic works correctly
  </Step>
</Steps>

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Check by Reference" icon="tag" href="/en/api-reference/mobile/check-by-ref">
    Track using your custom reference
  </Card>

  <Card title="Send Top-Up" icon="paper-plane" href="/en/api-reference/mobile/send-topup">
    Create a new top-up
  </Card>
</CardGroup>
