> ## Documentation Index
> Fetch the complete documentation index at: https://dev.chargily.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Chargily Pro API Documentation

> Welcome to the API documentation of Chargily Pro™. The easiest way to integrate Mobile Credit Topup service of all telecom operators in Algeria. Add Flexy Djezzy, Arselli Mobilis and Storm Ooredoo into your App, Website or any project.

## Table of Contents

* [Overview](#overview)
* [Authentication](#authentication)
* [Rate Limiting](#rate-limiting)
* [Error Handling](#error-handling)
* [Endpoints](#endpoints)
  * [User Management](#user-management-endpoints)
  * [Mobile Topup (Flexy/Airtime)](#mobile-topup-flexy/airtime-endpoints)
  * [Topup Configuration](#topup-configuration-endpoints)
  * [Vouchers/Gift Cards](#vouchers/gift-cards-endpoints)
  * [Deposits](#deposit-endpoints)
* [Webhooks](#webhooks)
* [API Restrictions & Validations](#api-restrictions-&-validations)

***

## Overview

**Base URL**: `https://pro.chargily.net/api/v1`

Chargily Pro provides a RESTful API for managing mobile top-ups and digital voucher sales. The API supports multiple authentication methods and includes comprehensive validation middleware.

### Key Features

* Mobile top-up operations (Ooredoo, Djezzy, Mobilis)
* Digital voucher/card sales
* Wallet & balance management
* Webhook notifications
* Multi-tenant support
* Sandbox mode for testing

***

## Authentication

All API requests require authentication using an API Public Key.

**Header**:

```http theme={null}
X-Authorization: {api_key}
```

**Example**:

```bash theme={null}
curl -X GET https://pro.chargily.net/api/v1/user/balance \
  -H "X-Authorization: {your-api-key-here}"
```

### Getting Your API Public Key

API Public Keys are generated through your account dashboard. Each API Public Key is unique and associated with your user account.

***

## Rate Limiting

* **Limit**: 2,400 requests per minute
* **Applied to**: All API routes
* **Response on limit**: HTTP 429 Too Many Requests

***

## Error Handling

### Standard Error Response

```json theme={null}
{
  "message": "Error description",
  "status": false
}
```

### HTTP Status Codes

| Code | Meaning               | Common Causes                     |
| ---- | --------------------- | --------------------------------- |
| 200  | OK                    | Successful GET request            |
| 201  | Created               | Successful POST request           |
| 202  | Accepted              | Request accepted for processing   |
| 400  | Bad Request           | Invalid request format            |
| 401  | Unauthorized          | Invalid/missing authentication    |
| 402  | Payment Required      | Insufficient balance              |
| 403  | Forbidden             | Mode inactive, permission denied  |
| 404  | Not Found             | Resource doesn't exist            |
| 409  | Conflict              | Duplicate request                 |
| 410  | Gone                  | Subscription expired              |
| 413  | Payload Too Large     | Pending request exists (cooldown) |
| 422  | Unprocessable Entity  | Validation failed                 |
| 429  | Too Many Requests     | Rate limit exceeded               |
| 500  | Internal Server Error | Server-side error                 |

***

## Endpoints

### User Management Endpoints

#### Get User Balance

Returns current balance and bonus for authenticated user.

```http theme={null}
GET /api/v1/user/balance
X-Authorization: {api_key}
```

**Authentication**: API Public Key required

**Response** (200 OK):

```json theme={null}
{
  "balance": 150000,
  "bonus": 5000
}
```

**Note**: Amounts are in cents (divide by 100 for actual value)

**Example**:

* `balance: 150000` = 1,500.00 DZD
* `bonus: 5000` = 50.00 DZD

***

#### Get All-Time User Bonus

Returns total bonus earned by user.

```http theme={null}
GET /api/v1/user/getAllTimeUserBonus
X-Authorization: {api_key}
```

**Response** (200 OK):

```json theme={null}
{
  "allTimeBonus": 25000
}
```

**Note**: Amount in cents (25000 = 250.00 DZD)

***

### Mobile Topup (Flexy/Airtime) Endpoints

#### Create Topup Request

Creates a new mobile top-up request.

```http theme={null}
POST /api/v1/topup/requests
X-Authorization: {api_key}
Content-Type: application/json
```

**Authentication**: API Public Key required

**Middleware Checks**:

* API subscription active
* Operator valid
* Mode valid and active
* Request timestamp recent (less than 30 seconds)
* Sufficient balance
* No duplicate pending request (3-minute cooldown)

**Request Body**:

```json theme={null}
{
  "request_number": "REQ-2024-001",
  "customer_name": "Ahmed Benali",
  "phone_number": "555123456",
  "value": 200,
  "operator": "ooredoo",
  "mode": "normal",
  "country_code": "DZ",
  "webhook_url": "https://your-app.net/webhooks/topup",
  "created_at": "2024-01-15 10:30:00"
}
```

**Validation Rules**:

* `request_number`: Required, unique identifier for your system
* `customer_name`: Required, string
* `phone_number`: Required, must match operator format
* `value`: Required, integer, between 10 and 5000
* `operator`: Required, one of: `ooredoo`, `djezzy`, `mobilis`
* `mode`: (Bundle) Required, valid mode for operator (e.g., "prepaid", "postpaid")
* `country_code`: Required (e.g., "DZ")
* `webhook_url`: Required, valid URL
* `created_at`: Required, datetime, max 30 seconds old

**Response** (201 Created):

```json theme={null}
{
  "message": "request sent successfully",
  "status": true
}
```

**Response** (402 Payment Required):

```json theme={null}
{
  "message": "Insufficient balance",
  "status": false
}
```

**Response** (413 Payload Too Large):

```json theme={null}
{
  "message": "Request already pending",
  "status": false
}
```

**Response** (422 Unprocessable Entity):

```json theme={null}
{
  "message": "Invalid phone number format for operator"
}
```

***

#### Get Topup Request Status

Retrieves the status of a specific topup request.

```http theme={null}
GET /api/v1/topup/requests/{request_number}
X-Authorization: {api_key}
```

**URL Parameters**:

* `request_number` (string): Your unique request identifier

**Response** (200 OK):

```json theme={null}
{
  "topupQueue": {
    "id": 123,
    "request_number": "REQ-2024-001",
    "customer_name": "Ahmed Benali",
    "phone_number": "555123456",
    "value": 200,
    "status": "sent",
    "created_at": "2024-01-15T10:30:00.000000Z",
    "updated_at": "2024-01-15T10:31:00.000000Z"
  }
}
```

**Possible Status Values**:

* `pending`: Awaiting processing
* `sent`: Successfully completed
* `failed`: Operation failed
* `rejected`: Request rejected
* `expired`: Request timed out

***

#### Get All Sent Operations

Returns all successfully sent topup operations for the authenticated user.

```http theme={null}
GET /api/v1/topup/sent
X-Authorization: {api_key}
```

**Response** (200 OK):

```json theme={null}
{
  "topupQueue": {
    "REQ-2024-001": "sent",
    "REQ-2024-003": "sent",
    "REQ-2024-007": "sent"
  }
}
```

***

#### Check Existing Operations

Checks which request numbers exist in the system.

```http theme={null}
POST /api/v1/topup/checkExistingOperation
Content-Type: application/json
```

**Request Body**:

```json theme={null}
{
  "ids": ["REQ-2024-001", "REQ-2024-002", "REQ-2024-999"]
}
```

**Response** (200 OK):

```json theme={null}
{
  "ids": ["REQ-2024-001", "REQ-2024-002"]
}
```

**Note**: Returns only IDs that exist in the database

***

#### Resend Webhook for Processing Requests

Triggers webhook resend for specific request numbers.

```http theme={null}
POST /api/v1/topup/recheckProcessingRequests
Content-Type: application/json
```

**Request Body**:

```json theme={null}
{
  "ids": ["REQ-2024-001", "REQ-2024-002"]
}
```

**Response** (202 Accepted):

```json theme={null}
{
  "message": "webhook sent successfully"
}
```

**Response** (400 Bad Request):

```json theme={null}
{
  "message": "Invalid IDs provided"
}
```

***

### Topup Configuration Endpoints

#### List Operators

Returns all active mobile operators.

```http theme={null}
GET /api/v1/topup/operators
```

**Authentication**: None required

**Response** (200 OK):

```json theme={null}
{
  "operators": [
    {
      "id": 1,
      "name": "Ooredoo",
      "logo": "https://storage.pro.chargily.net/operators/ooredoo.png",
      "country_code": "DZ",
      "first_number": ["05", "06", "07"],
      "number_length": [9],
      "discount": 2.5,
      "mode_discount": 1.5
    },
    {
      "id": 2,
      "name": "Djezzy",
      "logo": "https://storage.pro.chargily.net/operators/djezzy.png",
      "country_code": "DZ",
      "first_number": ["077", "078", "079"],
      "number_length": [9],
      "discount": 2.0,
      "mode_discount": 1.0
    }
  ]
}
```

**Fields**:

* `first_number`: Array of valid phone number prefixes
* `number_length`: Array of valid phone number lengths
* `discount`: Percentage discount for regular recharges
* `mode_discount`: Percentage discount for mode-based recharges

***

#### List Modes

Returns all active recharge modes.

```http theme={null}
GET /api/v1/topup/modes
```

**Authentication**: None required

**Response** (200 OK):

```json theme={null}
{
  "modes": [
    {
      "id": 5,
      "mode": "switch",
      "operator": "ooredoo",
      "value": 200,
      "amount": 205.00,
      "discount": 1.5,
      "operator_id": 1,
      "is_active": true,
      "country_code": "DZ"
    },
    {
      "id": 12,
      "mode": "legend",
      "operator": "djezzy",
      "value": 500,
      "amount": 510.00,
      "discount": 2.0,
      "operator_id": 2,
      "is_active": true,
      "country_code": "DZ"
    }
  ]
}
```

**Fields**:

* `mode`: Name of the bundle (e.g., "PixX1000", "Sama Mix")
* `value`: Face value of the recharge
* `amount`: Actual amount charged (including fees/discounts)
* `discount`: Percentage discount for this mode

***

#### Get Mode Details

Returns details for a specific mode.

```http theme={null}
GET /api/v1/topup/modes/{id}
```

**URL Parameters**:

* `id` (integer): Mode ID

**Response** (200 OK):

```json theme={null}
{
  "mode": {
    "id": 5,
    "mode": "normal",
    "operator": "ooredoo",
    "value": 200,
    "amount": 205.00,
    "discount": 1.5,
    "operator_id": 1,
    "is_active": true,
    "country_code": "DZ"
  }
}
```

***

### Vouchers/Gift Cards Endpoints

#### List All Vouchers

Returns all available digital vouchers, gift and prepaid cards.

```http theme={null}
GET /api/v1/voucher/vouchers
```

**Authentication**: None required

**Response** (200 OK):

```json theme={null}
{
  "cards": [
    {
      "id": 1,
      "name": "Google Play 1000 DZD",
      "image": "https://storage.pro.chargily.net/cards/google-play.png",
      "value": 1000,
      "amount": 1050.00,
      "redeem": "Play Store",
      "discount": 5.0,
      "card_category_id": 1,
      "card_category_name": "Gaming",
      "country_code": "DZ",
      "category_image": "https://storage.pro.chargily.net/categories/gaming.png",
      "out_of_stock": false
    },
    {
      "id": 2,
      "name": "iTunes 500 DZD",
      "image": "https://storage.pro.chargily.net/cards/itunes.png",
      "value": 500,
      "amount": 525.00,
      "redeem": "App Store",
      "discount": 5.0,
      "card_category_id": 2,
      "card_category_name": "Entertainment",
      "country_code": "DZ",
      "category_image": "https://storage.pro.chargily.net/categories/entertainment.png",
      "out_of_stock": true
    }
  ]
}
```

**Fields**:

* `value`: Face value of the card
* `amount`: Selling price
* `redeem`: Where to redeem the card
* `out_of_stock`: Boolean indicating availability

***

#### Get Voucher Details

Returns details for a specific voucher.

```http theme={null}
GET /api/v1/voucher/vouchers/{id}
```

**URL Parameters**:

* `id` (integer): Voucher ID

**Response** (200 OK):

```json theme={null}
{
  "id": 1,
  "name": "Google Play 1000 DZD",
  "image": "https://storage.pro.chargily.net/cards/google-play.png",
  "value": 1000,
  "amount": 1050.00,
  "redeem": "Play Store",
  "discount": 5.0,
  "card_category_id": 1,
  "card_category_name": "Gaming",
  "country_code": "DZ",
  "category_image": "https://storage.pro.chargily.net/categories/gaming.png",
  "out_of_stock": false
}
```

**Response** (404 Not Found):

```json theme={null}
{
  "message": "Voucher not found"
}
```

***

#### List Card Categories

Returns all voucher categories.

```http theme={null}
GET /api/v1/voucher/categories
```

**Authentication**: None required

**Response** (200 OK):

```json theme={null}
{
  "categories": [
    {
      "id": 1,
      "name": "Idoom ADSL/Fibre",
      "image": "https://storage.pro.chargily.net/categories/idoom-adsl-fibre.png",
      "redeem_link": "https://paiement.at.dz/index.php?p=voucher_internet",
      "redeem_link_description": "Recharge your internet subscription",
      "country_code": "DZ"
    },
    {
      "id": 2,
      "name": "Gaming",
      "image": "https://storage.pro.chargily.net/categories/gaming.png",
      "redeem_link": "https://play.google.net/redeem",
      "redeem_link_description": "Enter code in Play Store",
      "country_code": "DZ"
    },
    {
      "id": 3,
      "name": "Entertainment",
      "image": "https://storage.pro.chargily.net/categories/entertainment.png",
      "redeem_link": "https://www.apple.net/redeem",
      "redeem_link_description": "Redeem in App Store",
      "country_code": "DZ"
    }
  ]
}
```

***

#### Get Cards by Category

Returns all cards in a specific category.

```http theme={null}
GET /api/voucher/categories/{name_or_id}
```

**URL Parameters**:

* `name_or_id`: Category ID (integer) or category name (string)

**Response** (200 OK):

```json theme={null}
[
    {
      "id": 1,
      "name": "Google Play 1000 DZD",
      "image": "https://storage.pro.chargily.net/cards/google-play.png",
      "value": 1000,
      "amount": 1050.00,
      "redeem": "Play Store",
      "discount": 5.0,
      "card_category_id": 1,
      "card_category_name": "Gaming",
      "country_code": "DZ",
      "category_image": "https://storage.pro.chargily.net/categories/gaming.png",
      "out_of_stock": false
    }
]
```

**Response** (404 Not Found):

```json theme={null}
{
  "message": "No vouchers found in this category"
}
```

***

#### Create Voucher Request

Purchases a digital voucher.

```http theme={null}
POST /api/voucher/requests
X-Authorization: {api_key}
Content-Type: application/json
```

**Authentication**: API Key required

**Middleware Checks**:

* API subscription active
* Voucher category active
* Sufficient inventory
* Sufficient balance

**Request Body**:

```json theme={null}
{
  "quantity": 1,
  "voucher_name": "Google Play 1000 DZD",
  "value": "1000",
  "customer_name": "Ahmed Benali",
  "request_number": "VCHR-2024-001",
  "country_code": "dz"
}
```

**Validation Rules**:

* `quantity`: Required, integer, > 0
* `voucher_name`: Required, string
* `value`: Required, string (face value)
* `customer_name`: Required, string
* `request_number`: Required, unique identifier
* `country_code`: Required, string

**Response** (201 Created):

```json theme={null}
{
  "message": "request sent successfully",
  "voucher_serial": "XXXX-XXXX-XXXX-XXXX",
  "voucher_key": "ABCD-1234-EFGH-5678",
  "status": true
}
```

**Response** (402 Payment Required):

```json theme={null}
{
  "message": "Insufficient balance"
}
```

**Side Effects**:

* Deducts amount from user balance
* Assigns voucher from inventory
* Marks voucher as sold
* Awards bonus points

**Sandbox Mode**:
For testing users (karaOdin, karaOdin2, kyesmine, developer), returns encrypted/test voucher data without consuming inventory.

***

#### Get All Successful Vouchers

Returns all successfully sold vouchers for the authenticated user.

```http theme={null}
GET /api/voucher/sold
X-Authorization: {api_key}
```

**Response** (200 OK):

```json theme={null}
{
  "voucherRequests": {
    "VCHR-2024-001": "ABCD-1234-EFGH-5678",
    "VCHR-2024-002": "IJKL-9012-MNOP-3456"
  }
}
```

**Response** (404 Not Found):

```json theme={null}
{
  "message": "No vouchers found"
}
```

***

#### Add Voucher to Inventory (Internal)

Adds a new voucher code to inventory.

```http theme={null}
POST /api/voucher/inventory
Content-Type: application/json
```

**Authentication**: None (should be restricted in production)

**Request Body**:

```json theme={null}
{
  "voucher_id": 1,
  "voucher_key": "ABCD-1234-EFGH-5678",
  "voucher_serial": "XXXX-XXXX-XXXX-XXXX"
}
```

**Response** (201 Created):

```json theme={null}
{
  "message": "Voucher stored successfully",
  "status": true
}
```

**Side Effects**:

* Increments card quantity
* Makes voucher available for purchase

***

### Deposit Endpoints

#### Get All Deposits

Returns deposit history for authenticated user.

```http theme={null}
GET /api/deposit/all
X-Authorization: {api_key}
```

**Authentication**: API Key required

**Response** (200 OK):

```json theme={null}
{
  "deposits": [
    {
      "id": 1,
      "user_id": 1,
      "amount": 10000.00,
      "status": "approved",
      "created_at": "2024-01-10T12:00:00.000000Z",
      "updated_at": "2024-01-10T12:30:00.000000Z"
    },
    {
      "id": 2,
      "user_id": 1,
      "amount": 5000.00,
      "status": "pending",
      "created_at": "2024-01-15T10:00:00.000000Z",
      "updated_at": "2024-01-15T10:00:00.000000Z"
    }
  ]
}
```

**Possible Status Values**:

* `pending`: Awaiting approval
* `approved`: Deposit confirmed
* `rejected`: Deposit declined

***

#### Get Sum of Approved Deposits

Returns total of all approved deposits for authenticated user.

```http theme={null}
GET /api/deposit/sumDeposits
X-Authorization: {api_key}
```

**Response** (200 OK):

```json theme={null}
{
  "total": 50000.00
}
```

***

## Webhooks

### Webhook Notifications

The system sends webhook notifications when topup request status changes.

**Package**: spatie/laravel-webhook-server

**Trigger Events**:

* Status changed to: `sent`, `failed`, `rejected`, `expired`

**Webhook Request**:

```http theme={null}
POST {your_webhook_url}
Content-Type: application/json
X-Signature: {hmac_signature}
```

**Payload**:

```json theme={null}
{
  "payload": {
    "id": 123,
    "request_number": "REQ-2024-001",
    "customer_name": "Ahmed Benali",
    "phone_number": "555123456",
    "value": 200,
    "username": "johndoe",
    "user_id": 1,
    "mode": "normal",
    "mode_id": 5,
    "operator": "ooredoo",
    "status": "sent",
    "created_at": "2024-01-15 10:30:00",
    "updated_at": "2024-01-15 10:31:00"
  }
}
```

### Webhook Security

**Signature Verification**:
The webhook includes an HMAC signature in the `X-Signature` header. Verify using your secret key:

```php theme={null}
$signature = hash_hmac('sha256', $payload, $user->secret);
if (!hash_equals($signature, $request->header('X-Signature'))) {
    // Invalid signature
}
```

**Retry Logic**:

* Maximum 5 retry attempts
* Exponential backoff between retries
* SSL verification disabled (for development)

***

## API Restrictions & Validations

All API requests go through automatic validation to ensure data integrity and prevent errors. Below are the restrictions you may encounter:

### Subscription Status

* Your API subscription must be active
* **Error (410 Gone)**: "Subscription is expired"

### Authentication

* Valid API key required in `X-Authorization` header
* **Error (401 Unauthorized)**: "Unauthorized"

### Balance Requirements

* Sufficient balance required for purchases
* **Error (402 Payment Required)**: "Insufficient balance"

### Operator Validation

When creating topup requests:

* Operator must exist and be active
* Phone number length must match operator requirements
* Phone number must start with valid prefix for the operator

**Possible Errors**:

* **404**: "Operator not found"
* **422**: "Invalid phone number length"
* **422**: "Invalid phone number format for operator"

### Topup Mode Validation

* Mode must exist for the specified operator
* Mode must be active
* Value must match mode requirements (for fixed-value modes)

**Possible Errors**:

* **404**: "Mode not found for operator"
* **403**: "Mode is not active"

### Request Timing & Duplicates

* Request timestamp must be within 30 seconds
* No duplicate requests (same `request_number` + `customer_name`)

**Possible Errors**:

* **400**: "Request timestamp too old (max 30 seconds)"
* **409**: "Duplicate request detected"

### Voucher Validation

* Voucher category must be active and available
* Sufficient inventory must be available for requested quantity

**Possible Errors**:

* **404**: "this voucher is not available"
* **402**: "Insufficient voucher inventory"

***

## Business Logic

### Topup Request Lifecycle

1. **Validation**
   * Operator valid and active
   * Mode valid and active
   * Phone number format correct
   * Request timestamp recent (less than 30 seconds)
   * Sufficient user balance

2. **Duplicate Check**
   * 3-minute cooldown per phone number
   * Prevents duplicate pending requests

3. **Balance Deduction**
   * Amount deducted from user wallet
   * Transaction recorded

4. **Request Creation**
   * Status set to "pending"
   * Queued for processing

5. **Processing**
   * Automatic or manual processing
   * Status updated to "sent"/"failed"

6. **Notification**
   * Webhook sent to client URL
   * Up to 5 retry attempts

7. **Finalization**
   * Success: Award bonus points
   * Failure: Refund to user balance

***

### Voucher Request Lifecycle

1. **Validation**
   * Voucher category active
   * Sufficient inventory
   * Sufficient user balance

2. **Balance Deduction**
   * Amount deducted from wallet
   * Transaction recorded

3. **Voucher Assignment**
   * Voucher retrieved from inventory
   * Serial and key assigned

4. **Status Update**
   * Voucher marked as sold
   * Inventory quantity decremented

5. **Bonus Award**
   * Reward points added to bonus wallet

6. **Response**
   * Serial and key returned to client

***

### Sandbox Mode

**Purpose**: Testing without consuming real inventory

**Sandbox Users**:

* karaOdin
* karaOdin2
* kyesmine
* developer

**Behavior**:

* Returns encrypted/test voucher data
* Does not consume inventory
* Does not deduct real balance
* Useful for integration testing

***

## Best Practices

### Request Timestamps

Always include current timestamp in `created_at` field:

```javascript theme={null}
// JavaScript
{
  "created_at": new Date().toISOString().slice(0, 19).replace('T', ' ')
}
```

```php theme={null}
// PHP
[
    'created_at' => now()->format('Y-m-d H:i:s')
]
```

***

### Webhook Implementation

Implement webhook handler to receive status updates:

```php theme={null}
Route::post('/webhooks/topup', function (Request $request) {
    // Verify signature
    $signature = hash_hmac('sha256', $request->getContent(), config('app.webhook_secret'));
    if (!hash_equals($signature, $request->header('X-Signature'))) {
        abort(401, 'Invalid signature');
    }

    // Process webhook
    $payload = $request->input('payload');
    $status = $payload['status'];
    $request_number = $payload['request_number'];

    // Update your local database
    // Send notification to customer
    // etc.

    return response()->json(['status' => 'ok']);
});
```

***

### Error Handling

Always handle API errors gracefully:

```javascript theme={null}
try {
  const response = await fetch('/api/topup/requests', {
    method: 'POST',
    headers: {
      'X-Authorization': apiKey,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(requestData)
  });

  if (!response.ok) {
    const error = await response.json();
    if (response.status === 402) {
      // Insufficient balance - show top-up prompt
    } else if (response.status === 413) {
      // Duplicate request - show pending message
    } else {
      // Generic error handling
    }
  }

  const result = await response.json();
  // Handle success
} catch (error) {
  // Network error handling
}
```

***

### Balance Management

Check balance before expensive operations:

```javascript theme={null}
// Check balance first
const balanceResponse = await fetch('/api/user/balance', {
  headers: { 'X-Authorization': apiKey }
});
const { balance } = await balanceResponse.json();

// Balance is in cents, convert to display
const balanceInDZD = balance / 100;

if (balanceInDZD >= requiredAmount) {
  // Proceed with operation
} else {
  // Show insufficient balance message
}
```

***

### Idempotency

Use unique `request_number` for idempotent requests:

```javascript theme={null}
// Generate unique request number
const requestNumber = `REQ-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

const response = await fetch('/api/topup/requests', {
  method: 'POST',
  headers: {
    'X-Authorization': apiKey,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    request_number: requestNumber,
    // ... other fields
  })
});
```

This ensures duplicate API calls don't create duplicate charges.

***

## Testing

### Testing with Sandbox Mode

Use sandbox accounts for testing:

```bash theme={null}
# Sandbox usernames (no real charges)
karaOdin
karaOdin2
kyesmine
developer
```

These accounts return test data without consuming real inventory or balance.

***

### Sample Integration Test

```php theme={null}
public function test_create_topup_request()
{
    $user = User::factory()->create();
    $apiKey = ApiKey::create(['user_id' => $user->id]);

    // Fund user wallet
    $user->deposit(10000); // 100.00 DZD

    $response = $this->withHeaders([
        'X-Authorization' => $apiKey->key,
    ])->postJson('/api/topup/requests', [
        'request_number' => 'TEST-001',
        'customer_name' => 'Test Customer',
        'phone_number' => '555123456',
        'value' => 200,
        'operator' => 'ooredoo',
        'mode' => 'flexy',
        'country_code' => 'DZ',
        'webhook_url' => 'https://your-app.net/webhooks/topup',
        'created_at' => now()->format('Y-m-d H:i:s'),
    ]);

    $response->assertStatus(201)
             ->assertJson([
                 'message' => 'request sent successfully',
                 'status' => true
             ]);

    $this->assertDatabaseHas('topup_queue', [
        'request_number' => 'TEST-001',
        'status' => 'pending'
    ]);
}
```

***

## Support

For API support:

* **Email**: [support@chargily.net](mailto:support@chargily.net)
* **Documentation**: Check this file
* **Issue Tracker**: Contact your account manager

***

## Changelog

### Version 1.0 (2024-01-15)

* Initial API documentation
* All endpoints documented
* Authentication methods described
* Error handling documented
* Webhook system explained
