Overview
The first step in integrating mobile top-ups is to fetch the list of available plans from the API. Plans include all operators (Mobilis, Djezzy, Ooredoo, Pixx) with both dynamic and fixed amount options.Plans are stable and rarely change. Cache them in your database to reduce API calls and improve performance.
API Reference
View complete API documentation for the GET /v3/mobile/plans endpoint
Understanding Plan Types
Dynamic Plans
Dynamic plans allow variable amounts within a min/max range:{
"code": "PREPAID_DJEZZY",
"name": "Djezzy Prepaid",
"operator": "Djezzy",
"cost": 0.98,
"isEnabled": true,
"min_amount": 50,
"max_amount": 5000
}
- Prepaid top-ups (credit)
- Postpaid bill payments (facture)
- International calling credit
Fixed Plans
Fixed plans have predetermined amounts:{
"code": "PIXX_500",
"name": "Pixx 500 DA",
"operator": "Pixx",
"cost": 0.97,
"isEnabled": true,
"amount": 500
}
- Special promotional offers
- Data packs
- Service activations
GetMenu Plans
Special plans that retrieve available offers for a specific number:{
"code": "GETMENU_Mobilis",
"name": "Mobilis GetMenu",
"operator": "Mobilis",
"cost": 0.98,
"isEnabled": true,
"min_amount": 50,
"max_amount": 5000
}
suggested_offers with all available plans for that specific number.
Fetching Plans from API
Basic Request
curl https://api.oneclickdz.com/v3/mobile/plans \
-H "X-Access-Token: YOUR_API_KEY"
const fetch = require('node-fetch');
async function fetchMobilePlans() {
const response = await fetch('https://api.oneclickdz.com/v3/mobile/plans', {
headers: {
'X-Access-Token': process.env.ONECLICKDZ_API_KEY
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error('Failed to fetch plans');
}
return {
dynamicPlans: data.data.dynamicPlans,
fixedPlans: data.data.fixedPlans
};
}
// Usage
const { dynamicPlans, fixedPlans } = await fetchMobilePlans();
console.log(`Loaded ${dynamicPlans.length} dynamic plans and ${fixedPlans.length} fixed plans`);
import requests
import os
def fetch_mobile_plans():
"""Fetch mobile plans from API"""
response = requests.get(
'https://api.oneclickdz.com/v3/mobile/plans',
headers={'X-Access-Token': os.getenv('ONECLICKDZ_API_KEY')}
)
response.raise_for_status()
data = response.json()
if not data['success']:
raise Exception('Failed to fetch plans')
return {
'dynamic_plans': data['data']['dynamicPlans'],
'fixed_plans': data['data']['fixedPlans']
}
# Usage
plans = fetch_mobile_plans()
print(f"Loaded {len(plans['dynamic_plans'])} dynamic and {len(plans['fixed_plans'])} fixed plans")
<?php
function fetchMobilePlans() {
$ch = curl_init('https://api.oneclickdz.com/v3/mobile/plans');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-Access-Token: ' . getenv('ONECLICKDZ_API_KEY')
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("HTTP error! status: $httpCode");
}
$data = json_decode($response, true);
if (!$data['success']) {
throw new Exception('Failed to fetch plans');
}
return [
'dynamic_plans' => $data['data']['dynamicPlans'],
'fixed_plans' => $data['data']['fixedPlans']
];
}
// Usage
$plans = fetchMobilePlans();
echo "Loaded " . count($plans['dynamic_plans']) . " dynamic and " . count($plans['fixed_plans']) . " fixed plans\n";
Response Structure
{
"success": true,
"data": {
"dynamicPlans": [
{
"code": "PREPAID_MOBILIS",
"name": "📱 PREPAID | عادي",
"operator": "Mobilis",
"cost": 0.96,
"isEnabled": true,
"min_amount": 40,
"max_amount": 3999
},
{
"code": "GROS_MOBILIS",
"name": "📱 GROS MOBILIS | جملة",
"operator": "GMobilis",
"isEnabled": true,
"min_amount": 5000,
"max_amount": 300000
}
],
"fixedPlans": [
{
"code": "MIX500_MOBILIS",
"name": "📱🌐 AUTO | MIX 500",
"operator": "Mobilis",
"isEnabled": true,
"cost": 0.96,
"amount": 500
},
{
"code": "MIX1000_OOREDOO",
"name": "📱🌐 AUTO | MIX 1000",
"operator": "Ooredoo",
"isEnabled": true,
"cost": 0.99,
"amount": 1000
}
]
},
"meta": {
"timestamp": "2025-10-29T00:35:59.220Z"
}
}
Understanding the Cost Field
Thecost field represents your wholesale price multiplier:
- How It Works
- Calculating Sell Price
- Important Note
Example: cost = 0.98For a 1000 DZD top-up:
- Face value: 1000 DZD (what user receives)
- Your cost: 1000 × 0.98 = 980 DZD (what you pay)
- Your margin: 1000 - 980 = 20 DZD (2%)
Add your markup:
const faceValue = 1000;
const cost = 0.98;
const ourCost = faceValue * cost; // 980 DZD
// Add 5% profit margin
const markup = 0.05;
const sellPrice = ourCost * (1 + markup); // 1029 DZD
const profit = sellPrice - ourCost; // 49 DZD
Never expose the
cost field to end users!This is your wholesale pricing. Always calculate and display your own retail prices.Caching Plans in Database
Since plans rarely change, cache them locally with your pricing:Database Schema
CREATE TABLE mobile_plans (
id SERIAL PRIMARY KEY,
code VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
operator VARCHAR(50) NOT NULL,
type VARCHAR(20) NOT NULL, -- 'dynamic' or 'fixed'
-- Cost info
wholesale_cost DECIMAL(5,4) NOT NULL,
sell_price_multiplier DECIMAL(5,4) NOT NULL,
-- Amount limits
min_amount INTEGER,
max_amount INTEGER,
fixed_amount INTEGER,
-- Status
is_enabled BOOLEAN DEFAULT true,
is_visible BOOLEAN DEFAULT true,
-- Metadata
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_mobile_plans_operator ON mobile_plans(operator);
CREATE INDEX idx_mobile_plans_enabled ON mobile_plans(is_enabled);
CREATE INDEX idx_mobile_plans_code ON mobile_plans(code);
const mobilePlanSchema = new mongoose.Schema({
code: { type: String, required: true, unique: true },
name: { type: String, required: true },
operator: { type: String, required: true, index: true },
type: { type: String, enum: ['dynamic', 'fixed'], required: true },
wholesaleCost: { type: Number, required: true },
sellPriceMultiplier: { type: Number, required: true },
minAmount: { type: Number },
maxAmount: { type: Number },
fixedAmount: { type: Number },
isEnabled: { type: Boolean, default: true, index: true },
isVisible: { type: Boolean, default: true },
lastSyncedAt: { type: Date, default: Date.now }
}, {
timestamps: true
});
const MobilePlan = mongoose.model('MobilePlan', mobilePlanSchema);
Sync Function
const db = require('./database');
async function syncMobilePlans() {
try {
console.log('🔄 Syncing mobile plans...');
// Fetch plans from API
const plans = await fetchMobilePlans();
// Calculate your pricing
const PROFIT_MARGIN = 0.05; // 5% profit
for (const plan of plans) {
const isFixed = !!plan.amount;
const planData = {
code: plan.code,
name: plan.name,
operator: plan.operator,
type: isFixed ? 'fixed' : 'dynamic',
wholesaleCost: plan.cost,
sellPriceMultiplier: plan.cost * (1 + PROFIT_MARGIN),
minAmount: plan.min_amount || null,
maxAmount: plan.max_amount || null,
fixedAmount: plan.amount || null,
isEnabled: plan.isEnabled,
lastSyncedAt: new Date()
};
// Upsert (insert or update)
await db.mobilePlans.upsert({
where: { code: plan.code },
update: planData,
create: planData
});
}
console.log(`✅ Successfully synced ${plans.length} plans`);
// Log statistics
const stats = {
total: plans.length,
dynamic: plans.filter(p => !p.amount).length,
fixed: plans.filter(p => p.amount).length,
enabled: plans.filter(p => p.isEnabled).length
};
console.log('📊 Plan statistics:', stats);
return stats;
} catch (error) {
console.error('❌ Failed to sync plans:', error);
throw error;
}
}
// Run sync on startup
syncMobilePlans();
// Schedule daily sync at midnight
const cron = require('node-cron');
cron.schedule('0 0 * * *', () => {
console.log('⏰ Running scheduled plan sync...');
syncMobilePlans();
});
from datetime import datetime
import schedule
import time
def sync_mobile_plans():
"""Sync mobile plans to database"""
try:
print('🔄 Syncing mobile plans...')
# Fetch plans from API
plans = fetch_mobile_plans()
# Calculate your pricing
PROFIT_MARGIN = 0.05 # 5% profit
for plan in plans:
is_fixed = 'amount' in plan
plan_data = {
'code': plan['code'],
'name': plan['name'],
'operator': plan['operator'],
'type': 'fixed' if is_fixed else 'dynamic',
'wholesale_cost': plan['cost'],
'sell_price_multiplier': plan['cost'] * (1 + PROFIT_MARGIN),
'min_amount': plan.get('min_amount'),
'max_amount': plan.get('max_amount'),
'fixed_amount': plan.get('amount'),
'is_enabled': plan['isEnabled'],
'last_synced_at': datetime.now()
}
# Upsert to database
db.mobile_plans.upsert(
filter={'code': plan['code']},
data=plan_data
)
print(f'✅ Successfully synced {len(plans)} plans')
# Log statistics
stats = {
'total': len(plans),
'dynamic': len([p for p in plans if 'amount' not in p]),
'fixed': len([p for p in plans if 'amount' in p]),
'enabled': len([p for p in plans if p['isEnabled']])
}
print('📊 Plan statistics:', stats)
return stats
except Exception as error:
print(f'❌ Failed to sync plans: {error}')
raise
# Run sync on startup
sync_mobile_plans()
# Schedule daily sync at midnight
schedule.every().day.at("00:00").do(sync_mobile_plans)
# Keep scheduler running
while True:
schedule.run_pending()
time.sleep(60)
<?php
function syncMobilePlans($db) {
try {
echo "🔄 Syncing mobile plans...\n";
// Fetch plans from API
$plans = fetchMobilePlans();
// Calculate your pricing
$profitMargin = 0.05; // 5% profit
foreach ($plans as $plan) {
$isFixed = isset($plan['amount']);
$planData = [
'code' => $plan['code'],
'name' => $plan['name'],
'operator' => $plan['operator'],
'type' => $isFixed ? 'fixed' : 'dynamic',
'wholesale_cost' => $plan['cost'],
'sell_price_multiplier' => $plan['cost'] * (1 + $profitMargin),
'min_amount' => $plan['min_amount'] ?? null,
'max_amount' => $plan['max_amount'] ?? null,
'fixed_amount' => $plan['amount'] ?? null,
'is_enabled' => $plan['isEnabled'],
'last_synced_at' => date('Y-m-d H:i:s')
];
// Upsert to database
$db->query(
"INSERT INTO mobile_plans
(code, name, operator, type, wholesale_cost, sell_price_multiplier,
min_amount, max_amount, fixed_amount, is_enabled, last_synced_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
operator = VALUES(operator),
type = VALUES(type),
wholesale_cost = VALUES(wholesale_cost),
sell_price_multiplier = VALUES(sell_price_multiplier),
min_amount = VALUES(min_amount),
max_amount = VALUES(max_amount),
fixed_amount = VALUES(fixed_amount),
is_enabled = VALUES(is_enabled),
last_synced_at = VALUES(last_synced_at)",
array_values($planData)
);
}
echo "✅ Successfully synced " . count($plans) . " plans\n";
// Log statistics
$stats = [
'total' => count($plans),
'dynamic' => count(array_filter($plans, fn($p) => !isset($p['amount']))),
'fixed' => count(array_filter($plans, fn($p) => isset($p['amount']))),
'enabled' => count(array_filter($plans, fn($p) => $p['isEnabled']))
];
echo "📊 Plan statistics: " . json_encode($stats) . "\n";
return $stats;
} catch (Exception $error) {
echo "❌ Failed to sync plans: " . $error->getMessage() . "\n";
throw $error;
}
}
// Run sync on startup
syncMobilePlans($db);
// For scheduled execution, use cron:
// 0 0 * * * /usr/bin/php /path/to/sync_plans.php
Serving Plans to Users
When users request plans, serve from your database with your pricing:async function getUserPlans(filters = {}) {
const { operator, type, enabled = true } = filters;
const query = { isEnabled: enabled, isVisible: true };
if (operator) query.operator = operator;
if (type) query.type = type;
const plans = await db.mobilePlans.findMany({
where: query,
orderBy: [
{ operator: 'asc' },
{ fixedAmount: 'asc' },
{ name: 'asc' }
]
});
// Transform for user display (remove wholesale cost!)
return plans.map(plan => ({
code: plan.code,
name: plan.name,
operator: plan.operator,
type: plan.type,
minAmount: plan.minAmount,
maxAmount: plan.maxAmount,
amount: plan.fixedAmount,
// Don't expose wholesale cost!
// Only expose what user needs
}));
}
// API endpoint
app.get('/api/mobile/plans', async (req, res) => {
try {
const { operator } = req.query;
const plans = await getUserPlans({ operator });
res.json({ success: true, data: { plans } });
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
def get_user_plans(operator=None, plan_type=None, enabled=True):
"""Get plans for user display"""
query = {
'is_enabled': enabled,
'is_visible': True
}
if operator:
query['operator'] = operator
if plan_type:
query['type'] = plan_type
plans = db.mobile_plans.find(query).sort([
('operator', 1),
('fixed_amount', 1),
('name', 1)
])
# Transform for user display (remove wholesale cost!)
return [{
'code': plan['code'],
'name': plan['name'],
'operator': plan['operator'],
'type': plan['type'],
'minAmount': plan.get('min_amount'),
'maxAmount': plan.get('max_amount'),
'amount': plan.get('fixed_amount')
# Don't expose wholesale cost!
} for plan in plans]
# Flask endpoint
@app.route('/api/mobile/plans', methods=['GET'])
def api_mobile_plans():
try:
operator = request.args.get('operator')
plans = get_user_plans(operator=operator)
return jsonify({'success': True, 'data': {'plans': plans}})
except Exception as error:
return jsonify({'success': False, 'error': str(error)}), 500
<?php
function getUserPlans($db, $operator = null, $type = null, $enabled = true) {
$conditions = ['is_enabled = ?', 'is_visible = ?'];
$params = [$enabled, true];
if ($operator) {
$conditions[] = 'operator = ?';
$params[] = $operator;
}
if ($type) {
$conditions[] = 'type = ?';
$params[] = $type;
}
$where = implode(' AND ', $conditions);
$query = "SELECT code, name, operator, type, min_amount, max_amount, fixed_amount
FROM mobile_plans
WHERE $where
ORDER BY operator, fixed_amount, name";
$plans = $db->query($query, $params)->fetchAll(PDO::FETCH_ASSOC);
// Transform for user display
return array_map(function($plan) {
return [
'code' => $plan['code'],
'name' => $plan['name'],
'operator' => $plan['operator'],
'type' => $plan['type'],
'minAmount' => $plan['min_amount'],
'maxAmount' => $plan['max_amount'],
'amount' => $plan['fixed_amount']
// Don't expose wholesale cost!
];
}, $plans);
}
// API endpoint
if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_SERVER['REQUEST_URI'] === '/api/mobile/plans') {
try {
$operator = $_GET['operator'] ?? null;
$plans = getUserPlans($db, $operator);
header('Content-Type: application/json');
echo json_encode(['success' => true, 'data' => ['plans' => $plans]]);
} catch (Exception $error) {
http_response_code(500);
echo json_encode(['success' => false, 'error' => $error->getMessage()]);
}
}
Filtering Plans
By Operator
const mobilisPlans = await getUserPlans({ operator: 'Mobilis' });
const djezzyPlans = await getUserPlans({ operator: 'Djezzy' });
const ooredooPlans = await getUserPlans({ operator: 'Ooredoo' });
mobilis_plans = get_user_plans(operator='Mobilis')
djezzy_plans = get_user_plans(operator='Djezzy')
ooredoo_plans = get_user_plans(operator='Ooredoo')
$mobilisPlans = getUserPlans($db, 'Mobilis');
$djezzyPlans = getUserPlans($db, 'Djezzy');
$ooredooPlans = getUserPlans($db, 'Ooredoo');
By Phone Number
function getOperatorFromPhone(phone) {
const prefixMap = {
'065': ['Mobilis'],
'066': ['Mobilis', 'Ooredoo'],
'067': ['Mobilis'],
'077': ['Djezzy', 'Mobilis'],
'078': ['Djezzy'],
'055': ['Ooredoo', 'Djezzy', 'Mobilis'],
'056': ['Ooredoo'],
'068': ['Pixx']
};
const prefix = phone.substring(0, 3);
return prefixMap[prefix] || [];
}
async function getPlansForPhone(phone) {
const operators = getOperatorFromPhone(phone);
if (operators.length === 0) {
throw new Error('Invalid phone number prefix');
}
const plans = await db.mobilePlans.findMany({
where: {
operator: { in: operators },
isEnabled: true,
isVisible: true
}
});
return plans;
}
def get_operator_from_phone(phone):
prefix_map = {
'065': ['Mobilis'],
'066': ['Mobilis', 'Ooredoo'],
'067': ['Mobilis'],
'077': ['Djezzy', 'Mobilis'],
'078': ['Djezzy'],
'055': ['Ooredoo', 'Djezzy', 'Mobilis'],
'056': ['Ooredoo'],
'068': ['Pixx']
}
prefix = phone[:3]
return prefix_map.get(prefix, [])
def get_plans_for_phone(phone):
operators = get_operator_from_phone(phone)
if not operators:
raise ValueError('Invalid phone number prefix')
plans = db.mobile_plans.find({
'operator': {'$in': operators},
'is_enabled': True,
'is_visible': True
})
return list(plans)
<?php
function getOperatorFromPhone($phone) {
$prefixMap = [
'065' => ['Mobilis'],
'066' => ['Mobilis', 'Ooredoo'],
'067' => ['Mobilis'],
'077' => ['Djezzy', 'Mobilis'],
'078' => ['Djezzy'],
'055' => ['Ooredoo', 'Djezzy', 'Mobilis'],
'056' => ['Ooredoo'],
'068' => ['Pixx']
];
$prefix = substr($phone, 0, 3);
return $prefixMap[$prefix] ?? [];
}
function getPlansForPhone($db, $phone) {
$operators = getOperatorFromPhone($phone);
if (empty($operators)) {
throw new Exception('Invalid phone number prefix');
}
$placeholders = implode(',', array_fill(0, count($operators), '?'));
$query = "SELECT * FROM mobile_plans
WHERE operator IN ($placeholders)
AND is_enabled = 1
AND is_visible = 1";
return $db->query($query, $operators)->fetchAll(PDO::FETCH_ASSOC);
}
Best Practices
Cache Locally
Cache Locally
Always cache plans in your database. Plans rarely change, and caching dramatically improves performance.
Hide Wholesale Costs
Hide Wholesale Costs
Never expose the
cost field to end users. Only show your retail pricing.Sync Regularly
Sync Regularly
Set up a daily cron job to sync plans. This ensures you have the latest offerings and availability.
Check isEnabled
Check isEnabled
Always verify
isEnabled before displaying plans. Disabled plans may be temporarily unavailable due to operator issues.Add Profit Margin
Add Profit Margin
Calculate your selling price by adding a reasonable profit margin (typically 3-10%) to the wholesale cost.
Index Your Database
Index Your Database
Create indexes on frequently queried fields (operator, code, isEnabled) for fast lookups.
Next Steps
Step 2: Validation
Learn how to validate phone numbers and user inputs
Plans API Reference
Complete API documentation for GET /v3/mobile/plans
Account Balance API
Learn how to check your account balance
Pricing Guide
Understand API pricing and cost structure
Response Format
Learn about API response structures
Models Reference
View all data models and schemas

