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.
Table of Contents
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:
X-Authorization: {api_key}
Example:
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
{
"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.
GET /api/v1/user/balance
X-Authorization: {api_key}
Authentication: API Public Key required
Response (200 OK):
{
"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.
GET /api/v1/user/getAllTimeUserBonus
X-Authorization: {api_key}
Response (200 OK):
{
"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.
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:
{
"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):
{
"message": "request sent successfully",
"status": true
}
Response (402 Payment Required):
{
"message": "Insufficient balance",
"status": false
}
Response (413 Payload Too Large):
{
"message": "Request already pending",
"status": false
}
Response (422 Unprocessable Entity):
{
"message": "Invalid phone number format for operator"
}
Get Topup Request Status
Retrieves the status of a specific topup request.
GET /api/v1/topup/requests/{request_number}
X-Authorization: {api_key}
URL Parameters:
request_number (string): Your unique request identifier
Response (200 OK):
{
"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.
GET /api/v1/topup/sent
X-Authorization: {api_key}
Response (200 OK):
{
"topupQueue": {
"REQ-2024-001": "sent",
"REQ-2024-003": "sent",
"REQ-2024-007": "sent"
}
}
Check Existing Operations
Checks which request numbers exist in the system.
POST /api/v1/topup/checkExistingOperation
Content-Type: application/json
Request Body:
{
"ids": ["REQ-2024-001", "REQ-2024-002", "REQ-2024-999"]
}
Response (200 OK):
{
"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.
POST /api/v1/topup/recheckProcessingRequests
Content-Type: application/json
Request Body:
{
"ids": ["REQ-2024-001", "REQ-2024-002"]
}
Response (202 Accepted):
{
"message": "webhook sent successfully"
}
Response (400 Bad Request):
{
"message": "Invalid IDs provided"
}
Topup Configuration Endpoints
List Operators
Returns all active mobile operators.
GET /api/v1/topup/operators
Authentication: None required
Response (200 OK):
{
"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.
Authentication: None required
Response (200 OK):
{
"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.
GET /api/v1/topup/modes/{id}
URL Parameters:
Response (200 OK):
{
"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.
GET /api/v1/voucher/vouchers
Authentication: None required
Response (200 OK):
{
"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.
GET /api/v1/voucher/vouchers/{id}
URL Parameters:
Response (200 OK):
{
"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):
{
"message": "Voucher not found"
}
List Card Categories
Returns all voucher categories.
GET /api/v1/voucher/categories
Authentication: None required
Response (200 OK):
{
"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.
GET /api/voucher/categories/{name_or_id}
URL Parameters:
name_or_id: Category ID (integer) or category name (string)
Response (200 OK):
[
{
"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):
{
"message": "No vouchers found in this category"
}
Create Voucher Request
Purchases a digital voucher.
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:
{
"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):
{
"message": "request sent successfully",
"voucher_serial": "XXXX-XXXX-XXXX-XXXX",
"voucher_key": "ABCD-1234-EFGH-5678",
"status": true
}
Response (402 Payment Required):
{
"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.
GET /api/voucher/sold
X-Authorization: {api_key}
Response (200 OK):
{
"voucherRequests": {
"VCHR-2024-001": "ABCD-1234-EFGH-5678",
"VCHR-2024-002": "IJKL-9012-MNOP-3456"
}
}
Response (404 Not Found):
{
"message": "No vouchers found"
}
Add Voucher to Inventory (Internal)
Adds a new voucher code to inventory.
POST /api/voucher/inventory
Content-Type: application/json
Authentication: None (should be restricted in production)
Request Body:
{
"voucher_id": 1,
"voucher_key": "ABCD-1234-EFGH-5678",
"voucher_serial": "XXXX-XXXX-XXXX-XXXX"
}
Response (201 Created):
{
"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.
GET /api/deposit/all
X-Authorization: {api_key}
Authentication: API Key required
Response (200 OK):
{
"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.
GET /api/deposit/sumDeposits
X-Authorization: {api_key}
Response (200 OK):
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:
POST {your_webhook_url}
Content-Type: application/json
X-Signature: {hmac_signature}
Payload:
{
"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:
$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
-
Validation
- Operator valid and active
- Mode valid and active
- Phone number format correct
- Request timestamp recent (less than 30 seconds)
- Sufficient user balance
-
Duplicate Check
- 3-minute cooldown per phone number
- Prevents duplicate pending requests
-
Balance Deduction
- Amount deducted from user wallet
- Transaction recorded
-
Request Creation
- Status set to “pending”
- Queued for processing
-
Processing
- Automatic or manual processing
- Status updated to “sent”/“failed”
-
Notification
- Webhook sent to client URL
- Up to 5 retry attempts
-
Finalization
- Success: Award bonus points
- Failure: Refund to user balance
Voucher Request Lifecycle
-
Validation
- Voucher category active
- Sufficient inventory
- Sufficient user balance
-
Balance Deduction
- Amount deducted from wallet
- Transaction recorded
-
Voucher Assignment
- Voucher retrieved from inventory
- Serial and key assigned
-
Status Update
- Voucher marked as sold
- Inventory quantity decremented
-
Bonus Award
- Reward points added to bonus wallet
-
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
{
"created_at": new Date().toISOString().slice(0, 19).replace('T', ' ')
}
// PHP
[
'created_at' => now()->format('Y-m-d H:i:s')
]
Webhook Implementation
Implement webhook handler to receive status updates:
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:
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:
// 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:
// 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:
# Sandbox usernames (no real charges)
karaOdin
karaOdin2
kyesmine
developer
These accounts return test data without consuming real inventory or balance.
Sample Integration Test
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
- 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