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

# Migration from v2 to v3

> Simple upgrade guide for migrating from API v2 to v3

## Overview

This guide shows you exactly what to change when migrating from OneClickDz Flexy API v2 to v3. Each example shows your current v2 code, lists what to change, and shows the v3 result.

**Available in multiple languages**: JavaScript/Node.js, PHP, and Python examples included.

<Warning>
  **API v2 Deprecation**: API v2 will be deprecated on **October 30, 2026**.
  Please migrate before this date to avoid service interruption.
</Warning>

## What's New in v3

<CardGroup cols={2}>
  <Card title="Standardized Responses" icon="layer-group">
    All responses wrapped in `{(success, data, meta, requestId)}` structure
  </Card>

  <Card title="Better Error Handling" icon="shield-check">
    Structured errors with codes, messages, and details for easier debugging
  </Card>

  <Card title="Enhanced Debugging" icon="info-circle">
    Every response includes timestamps and unique request IDs
  </Card>

  <Card title="Cleaner Endpoints" icon="route">
    Logical grouping: `/mobile/*`, `/internet/*`, `/gift-cards/*`, `/account/*`
  </Card>

  <Card title="Separate API Keys" icon="key">
    Generate dedicated sandbox keys for testing while keeping your production key
  </Card>

  <Card title="IP Whitelisting" icon="shield-halved">
    Add IP restrictions directly from the settings page for enhanced security
  </Card>
</CardGroup>

## Quick Migration Checklist

<Steps>
  <Step title="Generate Sandbox API Key (Optional)">
    Create a sandbox API key from your dashboard settings page for testing v3 before migrating production
  </Step>

  <Step title="Configure IP Whitelist (Optional)">
    Add IP restrictions for enhanced security directly from settings
  </Step>

  <Step title="Update Authentication Header">
    Change from `authorization` to `X-Access-Token` in all API requests
  </Step>

  <Step title="Update Response Handling">
    Access data through `result.data` instead of directly from the response
  </Step>

  <Step title="Update Error Handling">
    Check `result.success` boolean and handle structured `result.error` object
  </Step>

  <Step title="Update Endpoint Paths">
    Map v2 paths to new v3 paths (see complete reference table below)
  </Step>

  <Step title="Update Pagination Logic">
    Access pagination info from `result.data.pagination` instead of root level
  </Step>
</Steps>

## Choose Your Migration Strategy

<Info>
  **Good News**: Both v2 and v3 APIs work simultaneously until v2 deprecation.
  Your current production API key works with both v2 and v3 endpoints, giving you flexibility in how you migrate.
</Info>

<Note>
  **Base URL Change**: v3 uses a new base URL: - v2:
  `https://flexy-api.oneclickdz.com/v2` - v3: `https://api.oneclickdz.com/v3`
</Note>

<Info>
  **New Security Features**: v3 introduces separate sandbox API key management for testing, plus IP whitelisting directly from your dashboard settings page. Your production key continues to work with both v2 and v3.
</Info>

Choose the approach that fits your needs:

<CardGroup cols={2}>
  <Card title="🆕 Fresh Start" icon="rocket">
    **Best for**: Complete rebuild or new projects

    * Generate sandbox key for testing from settings
    * Configure IP whitelist for production key
    * Test everything in sandbox with v3
    * Use production key when ready to go live

    ✅ Clean slate, no legacy code
    ✅ Enhanced security with IP restrictions
  </Card>

  <Card title="🔄 Gradual Migration" icon="arrows-rotate">
    **Best for**: Existing production systems

    * Keep using current production key for v2 endpoints
    * Generate sandbox key to test v3 endpoints safely
    * Migrate endpoints one by one to v3
    * Run v2 and v3 side-by-side with same production key

    ✅ Zero downtime, less risk
    ✅ Migrate at your own pace
  </Card>
</CardGroup>

**Recommended**: Most teams choose **gradual migration** to minimize risk. You can update critical endpoints first while keeping others on v2.

### Smart Migration: Start with Gift Cards

<Info>
  **Pro Tip**: If you're interested in gift cards, integrate them on v3 first! You don't need to migrate your existing mobile and internet top-ups to start using gift cards on v3. This gives you a low-risk way to test v3 while keeping your critical operations on v2. Use your sandbox key to test, then switch to production when ready.
</Info>

**Suggested Migration Order**:

1. **Phase 1**: Integrate gift cards on v3 (if applicable)
   * Generate sandbox key for testing
   * Use `/v3/gift-cards/*` endpoints in sandbox
   * Test thoroughly, then use production key
   * Keep mobile/internet on v2

2. **Phase 2**: Migrate account & validation endpoints
   * `/v3/validate`
   * `/v3/account/balance`
   * `/v3/account/transactions`

3. **Phase 3**: Migrate mobile top-ups gradually
   * Test with low-volume operations first
   * Monitor for issues
   * Scale up migration

4. **Phase 4**: Migrate internet top-ups
   * Complete before deprecation deadline

This approach lets you **migrate at your own pace** until the deprecation date while taking advantage of new v3 features.

***

## The 3 Main Changes

### 1. Authentication Header

**What to change**: Rename the header from `authorization` to `X-Access-Token`

<Info>
  **New Key Management**: v3 allows you to generate a separate sandbox API key for testing from your dashboard settings. You can also add IP whitelist restrictions for enhanced security. Your existing production API key works with both v2 and v3 endpoints.
</Info>

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    // Your current v2 code:
    fetch("https://flexy-api.oneclickdz.com/v2/plans/listAll", {
      headers: {
        authorization: "YOUR_API_KEY",
      },
    });
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    // Your current v2 code:
    $ch = curl_init('https://flexy-api.oneclickdz.com/v2/plans/listAll');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'authorization: YOUR_API_KEY'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Your current v2 code:
    import requests

    response = requests.get(
    'https://flexy-api.oneclickdz.com/v2/plans/listAll',
    headers={'authorization': 'YOUR_API_KEY'}
    )

    ```
  </Tab>
</Tabs>

**Changes needed**:

* ❌ Remove: `authorization: "YOUR_API_KEY"`
* ✅ Add: `X-Access-Token: "YOUR_API_KEY"`
* ✅ Update: Base URL to `https://api.oneclickdz.com/v3`

<Info>
  The authentication mechanism remains the same - only the header name changes. Your existing API key works with both v2 and v3.
</Info>

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    // Updated v3 code:
    fetch("https://api.oneclickdz.com/v3/mobile/plans", {
      headers: {
        "X-Access-Token": "YOUR_API_KEY",
      },
    });
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    // Updated v3 code:
    $ch = curl_init('https://api.oneclickdz.com/v3/mobile/plans');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-Access-Token: YOUR_API_KEY'
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Updated v3 code:
    import requests

    response = requests.get(
    'https://api.oneclickdz.com/v3/mobile/plans',
    headers={'X-Access-Token': 'YOUR_API_KEY'}
    )

    ```
  </Tab>
</Tabs>

***

### 2. Response Structure

**What to change**: All responses are now wrapped in a standard structure

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    // Your current v2 code:
    const response = await fetch("https://flexy-api.oneclickdz.com/v2/account/balance", {
      headers: { authorization: API_KEY },
    });
    const data = await response.json();
    console.log("Balance:", data.balance); // Direct access
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    // Your current v2 code:
    $ch = curl_init('https://flexy-api.oneclickdz.com/v2/account/balance');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['authorization: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $data = json_decode($response, true);
    echo "Balance: " . $data['balance']; // Direct access
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Your current v2 code:
    response = requests.get(
        'https://flexy-api.oneclickdz.com/v2/account/balance',
        headers={'authorization': API_KEY}
    )
    data = response.json()
    print(f"Balance: {data['balance']}")  # Direct access
    ```
  </Tab>
</Tabs>

**Changes needed**:

* ❌ Remove: Direct access to `data.balance`
* ✅ Add: Check `result.success` first
* ✅ Add: Access through `result.data.balance`
* ✅ Bonus: Use `result.requestId` for debugging
* ✅ Bonus: Use `result.meta.timestamp` for timing info

<Warning>
  **Breaking Change**: In v3, you must always check the `success` boolean before
  accessing data. Direct field access will cause errors if the request failed.
</Warning>

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    // Updated v3 code:
    const response = await fetch("https://api.oneclickdz.com/v3/account/balance", {
      headers: { "X-Access-Token": API_KEY },
    });
    const result = await response.json();

    if (result.success) {
    console.log("Balance:", result.data.balance);
    console.log("Request ID:", result.requestId); // For debugging
    console.log("Timestamp:", result.meta.timestamp); // Response time
    } else {
    console.error("Error:", result.error.message);
    }

    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    // Updated v3 code:
    $ch = curl_init('https://api.oneclickdz.com/v3/account/balance');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Access-Token: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $result = json_decode($response, true);

    if ($result['success']) {
        echo "Balance: " . $result['data']['balance'] . "\n";
        echo "Request ID: " . $result['requestId'] . "\n";
        echo "Timestamp: " . $result['meta']['timestamp'] . "\n";
    } else {
        error_log("Error: " . $result['error']['message']);
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Updated v3 code:
    response = requests.get(
        'https://api.oneclickdz.com/v3/account/balance',
        headers={'X-Access-Token': API_KEY}
    )
    result = response.json()

    if result['success']:
    print(f"Balance: {result['data']['balance']}")
    print(f"Request ID: {result['requestId']}") # For debugging
    print(f"Timestamp: {result['meta']['timestamp']}")
    else:
    print(f"Error: {result['error']['message']}")

    ```
  </Tab>
</Tabs>

***

### 3. Error Handling

**What to change**: Errors are now structured objects with codes

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    // Your current v2 code:
    const response = await fetch("https://flexy-api.oneclickdz.com/v2/topup/sendTopup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        authorization: API_KEY,
      },
      body: JSON.stringify(data),
    });

    const result = await response.json();
    if (result.error) {
      console.error("Error:", result.message); // Simple string
    }
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    // Your current v2 code:
    $ch = curl_init('https://flexy-api.oneclickdz.com/v2/topup/sendTopup');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'authorization: ' . $apiKey
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $result = json_decode($response, true);

    if (isset($result['error'])) {
    error_log("Error: " . $result['message']); // Simple string
    }

    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Your current v2 code:
    response = requests.post(
        'https://flexy-api.oneclickdz.com/v2/topup/sendTopup',
        headers={
            'Content-Type': 'application/json',
            'authorization': API_KEY
        },
        json=data
    )
    result = response.json()

    if 'error' in result:
        print(f"Error: {result['message']}")  # Simple string
    ```
  </Tab>
</Tabs>

**Changes needed**:

* ❌ Remove: Checking `result.error` string
* ✅ Add: Check `result.success === false`
* ✅ Add: Access `result.error.code` and `result.error.message`
* ✅ Add: Handle different error codes with switch/if statements
* ✅ Add: Log `result.requestId` for support tickets

<Info>
  **Pro Tip**: Always save the `requestId` when logging errors. Our support team
  needs this to trace issues in our system.
</Info>

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    // Updated v3 code:
    const response = await fetch("https://api.oneclickdz.com/v3/mobile/send", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Access-Token": API_KEY,
      },
      body: JSON.stringify(data),
    });

    const result = await response.json();

    if (!result.success) {
    console.error(`Error [${result.error.code}]: ${result.error.message}`);
    console.log("Request ID:", result.requestId);

    // Handle specific errors
    switch (result.error.code) {
    case "INSUFFICIENT_BALANCE":
    alert("Please top up your account");
    break;
    case "ERR_PHONE":
    alert(`Invalid phone number: ${result.error.message}`);
    if (result.error.details?.pattern) {
    console.log("Expected format:", result.error.details.pattern);
    }
    break;
    case "DUPLICATED_REF":
    alert("This order reference was already used");
    break;
    default:
    alert("An error occurred. Please try again.");
    }
    }

    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    // Updated v3 code:
    $ch = curl_init('https://api.oneclickdz.com/v3/mobile/send');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'X-Access-Token: ' . $apiKey
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $result = json_decode($response, true);

    if (!$result['success']) {
        $errorCode = $result['error']['code'];
        $errorMessage = $result['error']['message'];
        error_log("Error [{$errorCode}]: {$errorMessage}");
        error_log("Request ID: " . $result['requestId']);

        // Handle specific errors
        switch ($errorCode) {
            case 'INSUFFICIENT_BALANCE':
                echo "Please top up your account\n";
                break;
            case 'ERR_PHONE':
                echo "Invalid phone number: {$errorMessage}\n";
                if (isset($result['error']['details']['pattern'])) {
                    echo "Expected format: " . $result['error']['details']['pattern'] . "\n";
                }
                break;
            case 'DUPLICATED_REF':
                echo "This order reference was already used\n";
                break;
            default:
                echo "An error occurred. Please try again\n";
        }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Updated v3 code:
    response = requests.post(
        'https://api.oneclickdz.com/v3/mobile/send',
        headers={
            'Content-Type': 'application/json',
            'X-Access-Token': API_KEY
        },
        json=data
    )
    result = response.json()

    if not result['success']:
    error_code = result['error']['code']
    error_message = result['error']['message']
    print(f"Error [{error_code}]: {error_message}")
    print(f"Request ID: {result['requestId']}")

        # Handle specific errors
        if error_code == 'INSUFFICIENT_BALANCE':
            print("Please top up your account")
        elif error_code == 'ERR_PHONE':
            print(f"Invalid phone number: {error_message}")
            if 'pattern' in result['error'].get('details', {}):
                print(f"Expected format: {result['error']['details']['pattern']}")
        elif error_code == 'DUPLICATED_REF':
            print("This order reference was already used")
        else:
            print("An error occurred. Please try again.")

    ```
  </Tab>
</Tabs>

***

## Step-by-Step Migration Examples

### Example 1: Get Mobile Plans

**Step 1 - Your current v2 code:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch("https://flexy-api.oneclickdz.com/v2/plans/listAll", {
      headers: {
        authorization: API_KEY,
      },
    });

    const data = await response.json();
    const dynamicPlans = data.dymanicPlans; // Note: typo in v2
    const fixedPlans = data.fixedPlans;
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    $ch = curl_init('https://flexy-api.oneclickdz.com/v2/plans/listAll');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['authorization: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $data = json_decode($response, true);

    $dynamicPlans = $data['dymanicPlans']; // Note: typo in v2
    $fixedPlans = $data['fixedPlans'];

    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.get(
        'https://flexy-api.oneclickdz.com/v2/plans/listAll',
        headers={'authorization': API_KEY}
    )
    data = response.json()

    dynamic_plans = data['dymanicPlans']  # Note: typo in v2
    fixed_plans = data['fixedPlans']
    ```
  </Tab>
</Tabs>

**Step 2 - What to change:**

1. Change base URL: `https://flexy-api.oneclickdz.com` → `https://api.oneclickdz.com`
2. Change endpoint: `/v2/plans/listAll` → `/v3/mobile/plans`
3. Change header: `authorization` → `X-Access-Token`
4. Add success check: `if (result.success)`
5. Access through data: `data.dymanicPlans` → `result.data.dynamicPlans` (typo fixed!)

<Warning>
  **MUST Fix**: v3 fixes the typo in "dymanicPlans" to "dynamicPlans". Update
  your code to use the correct spelling.
</Warning>

**Step 3 - Your new v3 code:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch("https://api.oneclickdz.com/v3/mobile/plans", {
      headers: {
        "X-Access-Token": API_KEY,
      },
    });

    const result = await response.json();
    if (result.success) {
    const dynamicPlans = result.data.dynamicPlans; // Typo fixed!
    const fixedPlans = result.data.fixedPlans;
    }

    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    $ch = curl_init('https://api.oneclickdz.com/v3/mobile/plans');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Access-Token: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $result = json_decode($response, true);

    if ($result['success']) {
        $dynamicPlans = $result['data']['dynamicPlans']; // Typo fixed!
        $fixedPlans = $result['data']['fixedPlans'];
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.get(
        'https://api.oneclickdz.com/v3/mobile/plans',
        headers={'X-Access-Token': API_KEY}
    )
    result = response.json()

    if result['success']:
    dynamic_plans = result['data']['dynamicPlans'] # Typo fixed!
    fixed_plans = result['data']['fixedPlans']

    ```
  </Tab>
</Tabs>

***

### Example 2: Send Mobile Top-Up

**Step 1 - Your current v2 code:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch("https://flexy-api.oneclickdz.com/v2/topup/sendTopup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        authorization: API_KEY,
      },
      body: JSON.stringify({
        plan_code: "PREPAID_DJEZZY",
        MSSIDN: "0778037340",
        amount: 500,
        ref: "order-123",
      }),
    });

    const data = await response.json();
    console.log("Topup ID:", data.topupId);
    console.log("New Balance:", data.newBalance);
    ```
  </Tab>

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

    $ch = curl_init('https://flexy-api.oneclickdz.com/v2/topup/sendTopup');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'authorization: ' . $apiKey
    ]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $result = json_decode($response, true);

    echo "Topup ID: " . $result['topupId'] . "\n";
    echo "New Balance: " . $result['newBalance'] . "\n";

    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    data = {
        'plan_code': 'PREPAID_DJEZZY',
        'MSSIDN': '0778037340',
        'amount': 500,
        'ref': 'order-123'
    }

    response = requests.post(
        'https://flexy-api.oneclickdz.com/v2/topup/sendTopup',
        headers={
            'Content-Type': 'application/json',
            'authorization': API_KEY
        },
        json=data
    )
    result = response.json()

    print(f"Topup ID: {result['topupId']}")
    print(f"New Balance: {result['newBalance']}")
    ```
  </Tab>
</Tabs>

**Step 2 - What to change:**

1. Change base URL: `https://flexy-api.oneclickdz.com` → `https://api.oneclickdz.com`
2. Change endpoint: `/v2/topup/sendTopup` → `/v3/mobile/send`
3. Change header: `authorization` → `X-Access-Token`
4. Add success check: `if (result.success)`
5. Access through data: `data.topupId` → `result.data.topupId`
6. Add comprehensive error handling

<Warning>
  **Important**: Always handle errors when sending top-ups. Network issues,
  insufficient balance, or invalid data can cause failures. Save the `requestId`
  for tracking.
</Warning>

**Step 3 - Your new v3 code:**

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

    const result = await response.json();
    if (result.success) {
    console.log("✅ Top-up sent successfully!");
    console.log("Topup ID:", result.data.topupId);
    console.log("New Balance:", result.data.newBalance);
    console.log("Request ID:", result.requestId);
    } else {
    console.error(`❌ Error [${result.error.code}]: ${result.error.message}`);
    console.error("Request ID:", result.requestId);
    }

    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    // ...existing code...

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

    if ($result['success']) {
        echo "✅ Top-up sent successfully!\n";
        echo "Topup ID: " . $result['data']['topupId'] . "\n";
        echo "New Balance: " . $result['data']['newBalance'] . "\n";
        echo "Request ID: " . $result['requestId'] . "\n";
    } else {
        error_log("❌ Error [{$result['error']['code']}]: {$result['error']['message']}");
        error_log("Request ID: " . $result['requestId']);
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # ...existing code...

    response = requests.post(
    'https://api.oneclickdz.com/v3/mobile/send',
    headers={
    'Content-Type': 'application/json',
    'X-Access-Token': API_KEY
    },
    json=data
    )
    result = response.json()

    if result['success']:
    print("✅ Top-up sent successfully!")
    print(f"Topup ID: {result['data']['topupId']}")
    print(f"New Balance: {result['data']['newBalance']}")
    print(f"Request ID: {result['requestId']}")
    else:
    print(f"❌ Error [{result['error']['code']}]: {result['error']['message']}")
    print(f"Request ID: {result['requestId']}")

    ```
  </Tab>
</Tabs>

***

### Example 3: Check Top-Up Status

**Step 1 - Your current v2 code:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch(
      "https://flexy-api.oneclickdz.com/v2/topup/checkStatus/REF/order-123",
      {
        headers: { authorization: API_KEY },
      }
    );

    const data = await response.json();
    console.log("Status:", data.topup.status);
    console.log("Phone:", data.topup.MSSIDN);
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    $ch = curl_init('https://flexy-api.oneclickdz.com/v2/topup/checkStatus/REF/order-123');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['authorization: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $data = json_decode($response, true);

    echo "Status: " . $data['topup']['status'] . "\n";
    echo "Phone: " . $data['topup']['MSSIDN'] . "\n";

    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.get(
        'https://flexy-api.oneclickdz.com/v2/topup/checkStatus/REF/order-123',
        headers={'authorization': API_KEY}
    )
    data = response.json()

    print(f"Status: {data['topup']['status']}")
    print(f"Phone: {data['topup']['MSSIDN']}")
    ```
  </Tab>
</Tabs>

**Step 2 - What to change:**

1. Change base URL: `https://flexy-api.oneclickdz.com` → `https://api.oneclickdz.com`
2. Change endpoint: `/v2/topup/checkStatus/REF/:ref` → `/v3/mobile/check-ref/:ref`
3. Change header: `authorization` → `X-Access-Token`
4. Add success check: `if (result.success)`
5. Access directly: `data.topup.status` → `result.data.status` (no more nested `topup` object)
6. Handle new refund information fields

<Info>
  **New Feature**: v3 includes detailed refund information with suggested
  alternative offers when a top-up is refunded.
</Info>

**Step 3 - Your new v3 code:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch(
      "https://api.oneclickdz.com/v3/mobile/check-ref/order-123",
      {
        headers: { "X-Access-Token": API_KEY },
      }
    );

    const result = await response.json();
    if (result.success) {
    console.log("Status:", result.data.status);
    console.log("Phone:", result.data.MSSIDN);

    // New in v3: Enhanced refund information
    if (result.data.status === "REFUNDED") {
    console.log("❌ Top-up was refunded");
    console.log("Reason:", result.data.refund_message);
    if (result.data.suggested_offers) {
    console.log("💡 Try these offers instead:");
    console.log(result.data.suggested_offers);
    }
    } else if (result.data.status === "FULFILLED") {
    console.log("✅ Top-up completed successfully");
    }
    }

    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    $ch = curl_init('https://api.oneclickdz.com/v3/mobile/check-ref/order-123');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Access-Token: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $result = json_decode($response, true);

    if ($result['success']) {
        echo "Status: " . $result['data']['status'] . "\n";
        echo "Phone: " . $result['data']['MSSIDN'] . "\n";

        // New in v3: Enhanced refund information
        if ($result['data']['status'] === 'REFUNDED') {
            echo "❌ Top-up was refunded\n";
            echo "Reason: " . $result['data']['refund_message'] . "\n";
            if (isset($result['data']['suggested_offers'])) {
                echo "💡 Try these offers instead:\n";
                print_r($result['data']['suggested_offers']);
            }
        } elseif ($result['data']['status'] === 'FULFILLED') {
            echo "✅ Top-up completed successfully\n";
        }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.get(
        'https://api.oneclickdz.com/v3/mobile/check-ref/order-123',
        headers={'X-Access-Token': API_KEY}
    )
    result = response.json()

    if result['success']:
    print(f"Status: {result['data']['status']}")
    print(f"Phone: {result['data']['MSSIDN']}")

        # New in v3: Enhanced refund information
        if result['data']['status'] == 'REFUNDED':
            print("❌ Top-up was refunded")
            print(f"Reason: {result['data']['refund_message']}")
            if 'suggested_offers' in result['data']:
                print("💡 Try these offers instead:")
                print(result['data']['suggested_offers'])
        elif result['data']['status'] == 'FULFILLED':
            print("✅ Top-up completed successfully")

    ```
  </Tab>
</Tabs>

***

### Example 4: List Transactions with Pagination

**Step 1 - Your current v2 code:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch(
      "https://flexy-api.oneclickdz.com/v2/account/transactions?page=1&pageSize=20",
      {
        headers: { authorization: API_KEY },
      }
    );

    const data = await response.json();
    console.log("Transactions:", data.transactions);
    console.log("Total:", data.totalResults);
    console.log("Page:", data.currentPage);
    console.log("Pages:", data.totalPages);
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    $ch = curl_init('https://flexy-api.oneclickdz.com/v2/account/transactions?page=1&pageSize=20');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['authorization: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $data = json_decode($response, true);

    echo "Transactions: " . count($data['transactions']) . "\n";
    echo "Total: " . $data['totalResults'] . "\n";
    echo "Page: " . $data['currentPage'] . "\n";
    echo "Pages: " . $data['totalPages'] . "\n";

    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.get(
        'https://flexy-api.oneclickdz.com/v2/account/transactions?page=1&pageSize=20',
        headers={'authorization': API_KEY}
    )
    data = response.json()

    print(f"Transactions: {len(data['transactions'])}")
    print(f"Total: {data['totalResults']}")
    print(f"Page: {data['currentPage']}")
    print(f"Pages: {data['totalPages']}")
    ```
  </Tab>
</Tabs>

**Step 2 - What to change:**

1. Change base URL: `https://flexy-api.oneclickdz.com` → `https://api.oneclickdz.com`
2. Change version: `/v2/account/transactions` → `/v3/account/transactions`
3. Change header: `authorization` → `X-Access-Token`
4. Add success check: `if (result.success)`
5. Access items: `data.transactions` → `result.data.items`
6. Access pagination: Root level fields → `result.data.pagination` object

<Warning>
  **Breaking Change**: Pagination structure has moved from root level to
  `data.pagination`. Update all list/pagination logic.
</Warning>

**Step 3 - Your new v3 code:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch(
      "https://api.oneclickdz.com/v3/account/transactions?page=1&pageSize=20",
      {
        headers: { "X-Access-Token": API_KEY },
      }
    );

    const result = await response.json();
    if (result.success) {
    console.log("Transactions:", result.data.items);
    console.log("Total:", result.data.pagination.totalResults);
    console.log("Page:", result.data.pagination.page);
    console.log("Pages:", result.data.pagination.totalPages);
    console.log("Page Size:", result.data.pagination.pageSize);
    }

    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    $ch = curl_init('https://api.oneclickdz.com/v3/account/transactions?page=1&pageSize=20');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Access-Token: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $result = json_decode($response, true);

    if ($result['success']) {
        echo "Transactions: " . count($result['data']['items']) . "\n";
        echo "Total: " . $result['data']['pagination']['totalResults'] . "\n";
        echo "Page: " . $result['data']['pagination']['page'] . "\n";
        echo "Pages: " . $result['data']['pagination']['totalPages'] . "\n";
        echo "Page Size: " . $result['data']['pagination']['pageSize'] . "\n";
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.get(
        'https://api.oneclickdz.com/v3/account/transactions?page=1&pageSize=20',
        headers={'X-Access-Token': API_KEY}
    )
    result = response.json()

    if result['success']:
    print(f"Transactions: {len(result['data']['items'])}")
    print(f"Total: {result['data']['pagination']['totalResults']}")
    print(f"Page: {result['data']['pagination']['page']}")
    print(f"Pages: {result['data']['pagination']['totalPages']}")
    print(f"Page Size: {result['data']['pagination']['pageSize']}")

    ```
  </Tab>
</Tabs>

***

### Example 5: Check Internet Products

**Step 1 - Your current v2 code:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch(
      "https://flexy-api.oneclickdz.com/v2/internet/checkCards/ADSL",
      {
        headers: { authorization: API_KEY },
      }
    );

    const cards = await response.json(); // Direct array
    cards.forEach((card) => {
      console.log(`${card.value} DA - Available: ${card.available}`);
    });
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    $ch = curl_init('https://flexy-api.oneclickdz.com/v2/internet/checkCards/ADSL');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['authorization: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $cards = json_decode($response, true); // Direct array

    foreach ($cards as $card) {
    echo $card['value'] . " DA - Available: " . $card['available'] . "\n";
    }

    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.get(
        'https://flexy-api.oneclickdz.com/v2/internet/checkCards/ADSL',
        headers={'authorization': API_KEY}
    )
    cards = response.json()  # Direct array

    for card in cards:
        print(f"{card['value']} DA - Available: {card['available']}")
    ```
  </Tab>
</Tabs>

**Step 2 - What to change:**

1. Change base URL: `https://flexy-api.oneclickdz.com` → `https://api.oneclickdz.com`
2. Change endpoint: `/v2/internet/checkCards/:type` → `/v3/internet/products?type=:type`
3. Change from path param to query param: `/ADSL` → `?type=ADSL`
4. Change header: `authorization` → `X-Access-Token`
5. Add success check: `if (result.success)`
6. Access array: Direct array → `result.data.products`

<Info>
  **API Design**: v3 uses query parameters instead of path parameters for type
  selection, making it easier to add filters in the future.
</Info>

**Step 3 - Your new v3 code:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch(
      "https://api.oneclickdz.com/v3/internet/products?type=ADSL",
      {
        headers: { "X-Access-Token": API_KEY },
      }
    );

    const result = await response.json();
    if (result.success) {
    result.data.products.forEach((card) => {
    console.log(`${card.value} DA - Available: ${card.available}`);
    });
    }

    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    $ch = curl_init('https://api.oneclickdz.com/v3/internet/products?type=ADSL');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Access-Token: ' . $apiKey]);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    $result = json_decode($response, true);

    if ($result['success']) {
        foreach ($result['data']['products'] as $card) {
            echo $card['value'] . " DA - Available: " . $card['available'] . "\n";
        }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.get(
        'https://api.oneclickdz.com/v3/internet/products?type=ADSL',
        headers={'X-Access-Token': API_KEY}
    )
    result = response.json()

    if result['success']:
    for card in result['data']['products']:
    print(f"{card['value']} DA - Available: {card['available']}")

    ```
  </Tab>
</Tabs>

***

## Migrate Your API Client

Here's how to update your API client wrapper:

**Step 1 - Your current v2 client:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    class FlexyAPI {
      constructor(apiKey) {
        this.apiKey = apiKey;
        this.baseUrl = "https://flexy-api.oneclickdz.com";
      }

      async request(endpoint, options = {}) {
        const response = await fetch(`${this.baseUrl}${endpoint}`, {
          ...options,
          headers: {
            ...options.headers,
            authorization: this.apiKey,
          },
        });
        return response.json();
      }

      async getBalance() {
        const data = await this.request("/v2/account/balance");
        return data.balance;
      }

      async sendTopup(params) {
        return await this.request("/v2/topup/sendTopup", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(params),
        });
      }
    }
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    class FlexyAPI {
        private $apiKey;
        private $baseUrl = 'https://flexy-api.oneclickdz.com';

        public function __construct($apiKey) {
            $this->apiKey = $apiKey;
        }

        private function request($endpoint, $options = []) {
            $ch = curl_init($this->baseUrl . $endpoint);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'authorization: ' . $this->apiKey
            ]);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            if (isset($options['method']) && $options['method'] === 'POST') {
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $options['body']);
            }

            $response = curl_exec($ch);
            return json_decode($response, true);
        }

        public function getBalance() {
            $data = $this->request('/v2/account/balance');
            return $data['balance'];
        }

        public function sendTopup($params) {
            return $this->request('/v2/topup/sendTopup', [
                'method' => 'POST',
                'body' => json_encode($params)
            ]);
        }

    }

    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import requests

    class FlexyAPI:
        def __init__(self, api_key):
            self.api_key = api_key
            self.base_url = 'https://flexy-api.oneclickdz.com'

        def request(self, endpoint, method='GET', data=None):
            headers = {'authorization': self.api_key}
            url = f"{self.base_url}{endpoint}"

            if method == 'GET':
                response = requests.get(url, headers=headers)
            else:
                headers['Content-Type'] = 'application/json'
                response = requests.post(url, headers=headers, json=data)

            return response.json()

        def get_balance(self):
            data = self.request('/v2/account/balance')
            return data['balance']

        def send_topup(self, params):
            return self.request('/v2/topup/sendTopup', method='POST', data=params)
    ```
  </Tab>
</Tabs>

**Step 2 - What to change:**

1. Change base URL: `https://flexy-api.oneclickdz.com` → `https://api.oneclickdz.com`
2. Change header: `authorization` → `X-Access-Token`
3. Add response wrapper handling in `request()` method
4. Check `result.success` and handle errors
5. Update all endpoint paths to v3
6. Throw structured errors with codes and request IDs

<Note>
  **Best Practice**: Centralize error handling in your API client. This makes it
  easier to add logging, monitoring, and user notifications.
</Note>

**Step 3 - Your new v3 client:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    class FlexyAPI {
      constructor(apiKey) {
        this.apiKey = apiKey;
        this.baseUrl = "https://api.oneclickdz.com";
      }

    async request(endpoint, options = {}) {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
    ...options,
    headers: {
    ...options.headers,
    "X-Access-Token": this.apiKey, // Changed header name
    },
    });

        const result = await response.json();

        // Handle v3 response wrapper
        if (!result.success) {
          const error = new Error(result.error.message);
          error.code = result.error.code;
          error.details = result.error.details;
          error.requestId = result.requestId;
          throw error;
        }

        return result.data; // Return unwrapped data

    }

    async getBalance() {
    const data = await this.request("/v3/account/balance"); // Updated path
    return data.balance;
    }

    async sendTopup(params) {
    return await this.request("/v3/mobile/send", { // Updated path
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(params),
    });
    }
    }

    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    class FlexyAPI {
        private $apiKey;
        private $baseUrl = 'https://api.oneclickdz.com';

        public function __construct($apiKey) {
            $this->apiKey = $apiKey;
        }

        private function request($endpoint, $options = []) {
            $ch = curl_init($this->baseUrl . $endpoint);
            $headers = ['X-Access-Token: ' . $this->apiKey]; // Changed header name

            if (isset($options['method']) && $options['method'] === 'POST') {
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $options['body']);
                $headers[] = 'Content-Type: application/json';
            }

            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $response = curl_exec($ch);
            $result = json_decode($response, true);

            // Handle v3 response wrapper
            if (!$result['success']) {
                $error = [
                    'message' => $result['error']['message'],
                    'code' => $result['error']['code'],
                    'details' => $result['error']['details'] ?? null,
                    'requestId' => $result['requestId']
                ];
                throw new Exception(json_encode($error));
            }

            return $result['data']; // Return unwrapped data
        }

        public function getBalance() {
            $data = $this->request('/v3/account/balance'); // Updated path
            return $data['balance'];
        }

        public function sendTopup($params) {
            return $this->request('/v3/mobile/send', [ // Updated path
                'method' => 'POST',
                'body' => json_encode($params)
            ]);
        }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import requests

    class FlexyAPIError(Exception):
    """Custom exception for API errors"""
    def **init**(self, message, code, details=None, request_id=None):
    super().**init**(message)
    self.code = code
    self.details = details
    self.request_id = request_id

    class FlexyAPI:
    def **init**(self, api_key):
    self.api_key = api_key
    self.base_url = 'https://api.oneclickdz.com'

        def request(self, endpoint, method='GET', data=None):
            headers = {'X-Access-Token': self.api_key}  # Changed header name
            url = f"{self.base_url}{endpoint}"

            if method == 'GET':
                response = requests.get(url, headers=headers)
            else:
                headers['Content-Type'] = 'application/json'
                response = requests.post(url, headers=headers, json=data)

            result = response.json()

            # Handle v3 response wrapper
            if not result['success']:
                raise FlexyAPIError(
                    result['error']['message'],
                    result['error']['code'],
                    result['error'].get('details'),
                    result['requestId']
                )

            return result['data']  # Return unwrapped data

        def get_balance(self):
            data = self.request('/v3/account/balance')  # Updated path
            return data['balance']

        def send_topup(self, params):
            return self.request('/v3/mobile/send', method='POST', data=params)  # Updated path

    ```
  </Tab>
</Tabs>

***

## Complete Endpoint Reference

| Service            | v2 Endpoint                             | v3 Endpoint                                   | Notes                                 |
| ------------------ | --------------------------------------- | --------------------------------------------- | ------------------------------------- |
| **Authentication** |                                         |                                               |                                       |
| Header             | `authorization: KEY`                    | `X-Access-Token: KEY`                         | Header name changed                   |
| Validate           | `GET /v2/validate`                      | `GET /v3/validate`                            | Response wrapped                      |
| **Mobile**         |                                         |                                               |                                       |
| List Plans         | `GET /v2/plans/listAll`                 | `GET /v3/mobile/plans`                        | Path changed, typo fixed              |
| Send Top-Up        | `POST /v2/topup/sendTopup`              | `POST /v3/mobile/send`                        | Path simplified                       |
| Check by Ref       | `GET /v2/topup/checkStatus/REF/:ref`    | `GET /v3/mobile/check-ref/:ref`               | Path simplified, refund info added    |
| Check by ID        | `GET /v2/topup/checkStatus/ID/:id`      | `GET /v3/mobile/check-id/:id`                 | Path simplified                       |
| List Top-Ups       | `GET /v2/topup/list`                    | `GET /v3/mobile/list`                         | Pagination moved to `data.pagination` |
| **Internet**       |                                         |                                               |                                       |
| Products           | `GET /v2/internet/checkCards/:type`     | `GET /v3/internet/products?type=`             | Path param → query param              |
| Check Number       | `GET /v2/internet/checkNumber/:t/:n`    | `GET /v3/internet/check-number?type=&number=` | Path params → query params            |
| Send               | `POST /v2/internet/sendTopup`           | `POST /v3/internet/send`                      | Path simplified                       |
| Check by Ref       | `GET /v2/internet/checkStatus/REF/:ref` | `GET /v3/internet/check-ref/:ref`             | Path simplified                       |
| Check by ID        | `GET /v2/internet/checkStatus/ID/:id`   | `GET /v3/internet/check-id/:id`               | Path simplified                       |
| List               | `GET /v2/internet/list`                 | `GET /v3/internet/list`                       | Pagination moved                      |
| **Gift Cards**     |                                         |                                               |                                       |
| Catalog            | `GET /v2/gift-cards/catalogue`          | `GET /v3/gift-cards/catalog`                  | Spelling fixed                        |
| Check Product      | `GET /v2/gift-cards/checkProduct/:id`   | `GET /v3/gift-cards/checkProduct/:id`         | Response wrapped                      |
| Place Order        | `POST /v2/gift-cards/placeOrder`        | `POST /v3/gift-cards/placeOrder`              | Response wrapped                      |
| Check Order        | `GET /v2/gift-cards/checkOrder/:id`     | `GET /v3/gift-cards/checkOrder/:id`           | Response wrapped                      |
| List Orders        | `GET /v2/gift-cards/list`               | `GET /v3/gift-cards/list`                     | Pagination moved                      |
| **Account**        |                                         |                                               |                                       |
| Balance            | `GET /v2/account/balance`               | `GET /v3/account/balance`                     | Response wrapped                      |
| Transactions       | `GET /v2/account/transactions`          | `GET /v3/account/transactions`                | Pagination moved                      |

***

## Error Code Reference

Common v2 errors and their v3 equivalents:

| v2 Error                 | v3 Error Code          | What Changed                                   |
| ------------------------ | ---------------------- | ---------------------------------------------- |
| `"NO-BALANCE"`           | `INSUFFICIENT_BALANCE` | Now a code with structured message             |
| `"DUPLICATED-REF"`       | `DUPLICATED_REF`       | Same code, now with details object             |
| `"ERR_VALIDATION"`       | `ERR_VALIDATION`       | Includes `details.field` and \`details.pattern |
| `"ERR_PHONE"`            | `ERR_PHONE`            | Includes validation pattern in details         |
| `"ERR_STOCK"`            | `ERR_STOCK`            | Product availability information               |
| `"Forbidden"`            | `ERR_AUTH`             | More descriptive, unified auth errors          |
| `"Access token missing"` | `ERR_AUTH`             | Same code for all auth errors                  |
| `"NOT_FOUND"`            | `NOT_FOUND`            | Resource not found (order, product, etc.)      |

**How to handle v3 errors:**

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    // v2 way:
    if (result.error === "NO-BALANCE") {
      // handle
    }

    // v3 way:
    if (!result.success && result.error.code === "INSUFFICIENT_BALANCE") {
      console.log("Request ID:", result.requestId); // Save for support
      // handle with more context
      if (result.error.details?.currentBalance) {
        console.log("Current balance:", result.error.details.currentBalance);
      }
    }
    ```
  </Tab>

  <Tab title="PHP">
    ```php theme={null}
    // v2 way:
    if ($result['error'] === 'NO-BALANCE') {
        // handle
    }

    // v3 way:
    if (!$result['success'] && $result['error']['code'] === 'INSUFFICIENT_BALANCE') {
        error_log("Request ID: " . $result['requestId']); // Save for support
        // handle with more context
        if (isset($result['error']['details']['currentBalance'])) {
    echo "Current balance: " . $result['error']['details']['currentBalance'] . "\n";
    }
    }

    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # v2 way:
    if result.get('error') == 'NO-BALANCE':
        # handle

    # v3 way:
    if not result['success'] and result['error']['code'] == 'INSUFFICIENT_BALANCE':
        print(f"Request ID: {result['requestId']}")  # Save for support
        # handle with more context
        if 'currentBalance' in result['error'].get('details', {}):
            print(f"Current balance: {result['error']['details']['currentBalance']}")
    ```
  </Tab>
</Tabs>

***

<Warning>
  **Critical Deadline**: After October 30, 2026, all v2 API requests will fail.
  Plan your migration accordingly to avoid service disruption.
</Warning>

***

## Common Migration Issues & Solutions

<AccordionGroup>
  <Accordion title="Issue: Getting 401 Unauthorized Errors" icon="circle-exclamation">
    **Problem**: Changed endpoint but forgot to update header name.

    **Solution**: Replace `authorization` with `X-Access-Token` in ALL requests.

    ```javascript theme={null}
    // ❌ Wrong
    headers: { authorization: API_KEY }

    // ✅ Correct
    headers: { "X-Access-Token": API_KEY }
    ```
  </Accordion>

  {" "}

  <Accordion title="Issue: Getting 'undefined' When Accessing Data" icon="circle-exclamation">
    **Problem**: Trying to access data directly instead of through `result.data`.
    **Solution**: Always access response data through the `data` property.

    ````javascript // ❌ Wrong const balance = result.balance; // ✅ Correct const theme={null}
    balance = result.data.balance; ```
    </Accordion>

    {" "}

    <Accordion title="Issue: Pagination Not Working" icon="circle-exclamation">
    **Problem**: Looking for pagination fields at root level. **Solution**: Access
    pagination through `data.pagination`. ```javascript // ❌ Wrong const total =
    result.totalResults; const page = result.currentPage; // ✅ Correct const
    total = result.data.pagination.totalResults; const page =
    result.data.pagination.page; ```
    </Accordion>

    <Accordion title="Issue: Error Handling Not Working" icon="circle-exclamation">
      **Problem**: Checking for old error format.
      
      **Solution**: Check `success` boolean and access structured error object.
      
      ```javascript
      // ❌ Wrong
      if (result.error) {
        console.log(result.message);
      }
      
      // ✅ Correct
      if (!result.success) {
        console.log(result.error.code, result.error.message);
        console.log("Request ID:", result.requestId);
      }
    ````
  </Accordion>

  <Accordion title="Issue: 404 Not Found Errors" icon="circle-exclamation">
    **Problem**: Using old v2 endpoint paths.

    **Solution**: Update all paths according to the reference table above.

    ```javascript theme={null}
    // ❌ Wrong
    POST /v2/topup/sendTopup
    GET /v2/internet/checkCards/ADSL

    // ✅ Correct
    POST /v3/mobile/send
    GET /v3/internet/products?type=ADSL
    ```
  </Accordion>
</AccordionGroup>

***

## Testing Checklist

Before deploying to production, verify:

* [ ] All authentication headers changed to `X-Access-Token`
* [ ] All endpoint paths updated to v3
* [ ] Success/error checking implemented everywhere
* [ ] Data accessed through `result.data`
* [ ] Pagination accessed through `result.data.pagination`
* [ ] Error codes handled with `result.error.code`
* [ ] Request IDs logged for debugging
* [ ] Tested in sandbox mode first
* [ ] All existing functionality still works
* [ ] Error scenarios tested (insufficient balance, invalid data, etc.)

***

## Need Help?

<CardGroup cols={2}>
  <Card title="API v3 Documentation" href="/en/quickstart" icon="book">
    Complete v3 API reference and examples
  </Card>

  <Card title="Error Handling Guide" href="error-handling-best-practices" icon="shield-check">
    Best practices for error handling
  </Card>

  <Card title="Dashboard Settings" icon="gear">
    **Access**: Login to your dashboard

    **Generate Sandbox Key**: Create a sandbox key for safe testing

    **IP Whitelist**: Add IP restrictions for enhanced security
  </Card>

  <Card title="Support" icon="headset">
    **Email**: [support@oneclickdz.com](mailto:support@oneclickdz.com)

    **Important**: Always include your request ID when reporting issues
  </Card>
</CardGroup>

<Info>
  **Migration Support**: Our team is here to help! Email us with "API v3
  Migration Support" in the subject line, and include: - Your current
  implementation details - Specific errors (with request IDs) - Code samples
  showing the issue
</Info>
