PHONIFY API Documentation v1
Base URL:
https://api.phonifydz.com/api/v1Alternative:https://www.phonifydz.com/api/v1Version: 1.0.0 | Last Updated: 2026-03-16
Table of Contents
- Authentication
- Rate Limiting
- Response Format
- Error Codes Reference
- Account Endpoints
- Flexy (Mobile Topup)
- Flexy Gros (Wholesale)
- PUBG Mobile
- Free Fire
- IPTV
- Digital Products (Wasmou)
- TikTok
- Vouchers
- Webhooks & Polling
- SDK Examples
- MCP Server (AI Integration)
- Testing Checklist
Authentication
All API requests require an API key passed via the X-API-Key header.
GET /api/v1/account/balance HTTP/1.1
Host: api.phonifydz.com
X-API-Key: phz_your_api_key_here
Obtaining an API Key
- Log into your PHONIFY account
- Navigate to API Settings (
/api-settings) - Click Generate Key and name your key
- Copy the key immediately — it is shown only once
IP Whitelisting (Optional)
You can restrict API key usage to specific IP addresses:
- Navigate to API Settings → click IPs on a key
- Add one or more IP addresses
- When empty, all IPs are allowed
Rate Limiting
| Limit | Default | Header |
|---|---|---|
| Per minute | 30 requests | X-RateLimit-Limit |
| Per day | 1,000 requests | — |
| Per month | 20,000 requests | — |
Rate Limit Headers
Every response includes:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests per minute |
X-RateLimit-Remaining |
Requests remaining in current window |
X-RateLimit-Reset |
Unix timestamp when the window resets |
X-Request-Id |
Unique request identifier for debugging |
When Rate Limited
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Max 30 requests/minute."
},
"meta": {
"timestamp": "2026-03-16T03:00:00+01:00",
"request_id": "req_abc123"
}
}
HTTP Status: 429 Too Many Requests
Retry-After header is included with seconds to wait.
Response Format
Success Response
{
"success": true,
"message": "Human-readable success message",
"data": {
// Response payload
},
"meta": {
"timestamp": "2026-03-16T03:00:00+01:00",
"request_id": "req_abc123"
}
}
Error Response
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"details": {
// Optional: validation errors or additional context
}
},
"meta": {
"timestamp": "2026-03-16T03:00:00+01:00",
"request_id": "req_abc123"
}
}
Error Codes Reference
Authentication Errors (4xx)
| Code | HTTP | Description | Resolution |
|---|---|---|---|
MISSING_API_KEY |
401 | No API key provided | Add X-API-Key header |
INVALID_API_KEY |
401 | API key not found in system | Check key is correct |
API_KEY_INACTIVE |
403 | Key has been deactivated | Contact admin or re-enable in settings |
API_KEY_EXPIRED |
403 | Key has passed expiry date | Generate a new key |
IP_NOT_WHITELISTED |
403 | Request IP is not in whitelist | Add your IP in API Settings |
ACCOUNT_SUSPENDED |
403 | User account is banned | Contact support |
PERMISSION_DENIED |
403 | Key lacks permission for this service | Admin must update key permissions |
Validation Errors (422)
| Code | HTTP | Description | Resolution |
|---|---|---|---|
VALIDATION_ERROR |
422 | Request body failed validation | Check details field for specific errors |
Business Logic Errors (4xx)
| Code | HTTP | Description | Resolution |
|---|---|---|---|
INSUFFICIENT_BALANCE |
402 | Not enough balance | Top up your account. details shows required vs available |
NO_WALLET |
400 | No client/wallet record | Contact admin to setup wallet |
PRODUCT_NOT_FOUND |
404 | Product/plan does not exist | Verify product_id against /products endpoint |
PLAN_NOT_FOUND |
404 | Plan does not exist or is inactive | Verify plan_id against /plans endpoint |
VARIANT_NOT_FOUND |
404 | Variant key invalid | Verify variant_key against /products endpoint |
NOT_FOUND |
404 | Transaction/order not found | Verify the ID belongs to your account |
OUT_OF_STOCK |
409 | Product has no stock available | Try again later or choose another product |
INSUFFICIENT_STOCK |
409 | Not enough vouchers available | details shows available vs requested |
DUPLICATE_ORDER |
429 | Same order within 30 seconds | Wait before retrying. Use idempotency keys |
INVALID_PHONE |
422 | Phone number operator cannot be detected | Verify phone number format (10 digits, starts with 05/06/07) |
AMOUNT_OUT_OF_RANGE |
422 | Amount outside allowed range | Check operator min/max limits |
Rate Limiting Errors (429)
| Code | HTTP | Description | Resolution |
|---|---|---|---|
RATE_LIMIT_EXCEEDED |
429 | Too many requests per minute | Wait and retry. Check Retry-After header |
DAILY_LIMIT_EXCEEDED |
429 | Daily request quota exhausted | Wait until next day or contact admin |
Server Errors (5xx)
| Code | HTTP | Description | Resolution |
|---|---|---|---|
TOPUP_FAILED |
500 | Internal topup processing error | Retry. No charge was made |
ORDER_FAILED |
500 | Order processing error | Retry. No charge was made |
PURCHASE_FAILED |
500 | Purchase processing error | Retry. No charge was made |
RECHARGE_FAILED |
500 | TikTok recharge error | Retry |
OFFERS_FETCH_FAILED |
502 | External API failed to return offers | Retry later |
PRODUCTS_FETCH_FAILED |
502 | External API failed to return products | Retry later |
PLAYER_CHECK_FAILED |
502 | Player verification service unavailable | Retry later |
SERVICE_MAINTENANCE |
503 | Service is under maintenance | Check back later |
Account Endpoints
GET /account/balance
Returns your current balance.
Response:
{
"success": true,
"data": {
"balance": 107.05,
"available_balance": 68.65,
"reserved_balance": 38.40,
"currency": "DZD"
}
}
| Field | Type | Description |
|---|---|---|
balance |
float | Total balance |
available_balance |
float | Balance available for new transactions |
reserved_balance |
float | Balance reserved for pending transactions |
GET /account/profile
Returns your profile and API key information.
Response:
{
"success": true,
"data": {
"user": {
"id": 1,
"name": "Yahia",
"email": "user@example.com",
"phone": "+213 6XX XX XX XX",
"shop_name": "My Shop"
},
"api_key": {
"name": "Test API Key",
"prefix": "phz_6PsKsfvb",
"permissions": ["flexy", "pubg", "freefire", "iptv", "tiktok", "vouchers"],
"rate_limit_per_minute": 30,
"daily_limit": 1000,
"monthly_limit": 20000,
"requests_today": 15,
"requests_this_month": 42,
"expires_at": null
}
}
}
GET /account/services
Lists all available services and whether your key has access.
Response:
{
"success": true,
"data": {
"services": {
"flexy": {
"name": "Flexy Mobile Topup",
"description": "Recharge Mobilis, Ooredoo, Djezzy prepaid lines",
"enabled": true
},
"pubg": {
"name": "PUBG Mobile",
"description": "PUBG UC top-up for players",
"enabled": true
}
}
}
}
Flexy (Mobile Topup)
How Flexy Works
Flexy is the core mobile credit recharge system. Here's the complete flow:
1. Operator Detection
Phone numbers are auto-detected by prefix:
06xx→ Mobilis05xx→ Ooredoo07xx→ Djezzy
2. Topup Types
| Type | Description | Operators | Amount Range |
|---|---|---|---|
prepaid |
Standard credit topup | All | Mobilis: 40–4999 DA, Others: 100–4999 DA |
offer |
Operator bundle (MIX, NET, TALK) | All | Per offer amount |
facture |
Bill payment | All | 100–4999 DA |
international |
International credit | Mobilis only | 200–4000 DA |
3. Offers & Categories
Each operator has different offer types (offer_category):
- MIX — Voice + Data bundles
- NET — Data-only bundles
- TALK — Voice-only bundles
Ooredoo/Djezzy offers require a menu_number field (provided in the offers list).
Mobilis offers ≥500 DA and ≤2000 DA may use PIXX SIMs (handled automatically).
4. Pricing & Discounts
- Each user has per-operator discount percentages (returned by
/flexy/operators) - The
costin responses is the amount deducted from your balance (after discount) - The
amountis the face value sent to the recipient - Example: 100 DA topup with 4% discount → cost = 96 DA, recipient gets 100 DA
5. Transaction Flow
POST /flexy/topup → status: "processing" → (async SIM recharge) → "success" or "failed"
- Submit — API validates input, checks balance, reserves funds, returns
transaction_id - Processing — Background job sends the actual recharge via SIM hardware
- Complete — Status updates to
success(funds confirmed) orfailed(funds refunded)
Typical processing time: 5–15 seconds. Poll /flexy/status/{id} to check.
6. Balance Management
- Funds are reserved (not deducted) at submission time
- On success: reserved amount is converted to a confirmed deduction
- On failure: reserved amount is released back to available balance
available_balance = balance - reserved_balance
7. Duplicate Protection
- Idempotency key: Send the same
idempotency_keyto prevent duplicate charges. The API returns the original transaction. - Processing window: The API also blocks duplicate requests for the same number + amount within a 2-minute window if a transaction is still processing.
8. Ooredoo Debt (SABBAQLI) — required flow
Ooredoo (05xx) prepaid and offer topups need a debt decision while SABBAQLI is on (requires_debt_check: true in /flexy/operators):
POST /flexy/ooredoo-debt-checkwithtarget_number,amount,topup_type(prepaidoroffer). Returnshas_debtanddebt_amount. The result is remembered 10 minutes.POST /flexy/topupwithpay_debt: true(debt is added to your cost and paid with the topup) orpay_debt: false(debt skipped).
The debt amount is taken from the server-side probe, not from the request. Without pay_debt the API answers PAY_DEBT_REQUIRED (422). With pay_debt: true and no recent probe you get DEBT_CHECK_REQUIRED (422).
9. Minimum Amounts
| Operator | Prepaid Min | Offer Min | International Min |
|---|---|---|---|
| Mobilis | 40 DA | 40 DA | 200 DA |
| Ooredoo | 100 DA | 50 DA | N/A |
| Djezzy | 100 DA | 50 DA | N/A |
GET /flexy/operators
List operators, allowed topup types, limits and your discount rates.
{
"data": {
"operators": [
{
"id": "mobilis",
"name": "Mobilis",
"prefixes": ["06"],
"topup_types": ["prepaid", "offer", "facture", "international"],
"min_amount": 40,
"min_amount_by_type": {"prepaid": 40, "offer": 40, "facture": 40, "international": 200},
"max_amount": 4999,
"max_amount_by_type": {"international": 4000},
"discount_percentage": 4.0,
"requires_debt_check": false
}
]
}
}
POST /flexy/ooredoo-debt-check
Ooredoo only. Probes the line for SABBAQLI debt (takes a few seconds). See Ooredoo Debt above.
| Field | Type | Required | Description |
|---|---|---|---|
target_number |
string | Yes | Ooredoo number (05xxxxxxxx) |
amount |
number | Yes | Intended topup amount (50–4999) |
topup_type |
string | Yes | prepaid or offer |
{
"data": {
"target_number": "0699123456",
"has_debt": true,
"debt_amount": 120,
"debt_check_required": true,
"valid_for_seconds": 600
}
}
Errors: NOT_OOREDOO (422), DEBT_CHECK_FAILED (502, retry).
POST /flexy/offers
Get real-time offers for a specific phone number. This queries the actual SIM hardware and may take 5–15 seconds to respond.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
target_number |
string | Yes | 10-digit phone number (e.g. 0699123456) |
Example:
curl -X POST https://api.phonifydz.com/api/v1/flexy/offers \
-H "X-API-Key: phz_your_key" \
-H "Content-Type: application/json" \
-d '{"target_number": "0699123456"}'
Response (Mobilis example, ~6s):
{
"data": {
"operator": "mobilis",
"target_number": "0699123456",
"offers": [
{"menu_number": "99", "name": "Sama Mix", "amount": 50, "category": "MIX"},
{"menu_number": "1", "name": "Sama Mix 100", "amount": 100, "category": "MIX"},
{"menu_number": "2", "name": "Sama Mix 500", "amount": 500, "category": "MIX"},
{"menu_number": "6", "name": "Sama Talk 500", "amount": 500, "category": "TALK"},
{"menu_number": "1", "name": "Sama Net 500", "amount": 500, "category": "NET"}
]
}
}
Response (Ooredoo example, ~10s):
{
"data": {
"operator": "ooredoo",
"target_number": "0699123456",
"offers": [
{"menu_number": "72", "name": "190 DA (10 Go 72H Promo)", "amount": 190},
{"menu_number": "74", "name": "90 DA (5 Go 24H Promo)", "amount": 90},
{"menu_number": "8", "name": "1000 DA", "amount": 1000}
]
}
}
Note: Offers are fetched in real-time from SIM hardware and are specific to each phone number. Response time is typically 5–15 seconds. The operator is auto-detected from the phone number prefix.
POST /flexy/topup
Perform a flexy mobile topup.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
target_number |
string | Yes | 10-digit phone number (e.g. 0699123456) |
amount |
number | Yes | Amount in DA (40–4999) |
topup_type |
string | Yes | prepaid, offer, facture, or international |
offer_id |
string | If topup_type=offer |
Offer ID from /offers |
offer_name |
string | No | Offer name for reference |
offer_category |
string | No | MIX, NET, TALK, MOBILIS, DJEZZY, or OOREDOO |
menu_number |
string | No | Menu number for offer selection |
idempotency_key |
string | No | Prevent duplicate transactions (max 64 chars) |
pay_debt |
boolean | Ooredoo prepaid/offer |
true pays the debt found by /flexy/ooredoo-debt-check, false skips it |
debt_amount |
number | No | Ignored when a recent debt-check exists |
Mobilis Revolution 500 and 100: these two offers (
menu_number7 for 500 DA,menu_number8 for 100 DA) are normal-SIM offers, not PIXX. Sendtopup_type: "offer"with theamount,menu_number,offer_idandoffer_nameexactly as returned by/flexy/offers. They are charged at the normal Mobilis rate. If no normal Mobilis SIM is available the order fails (SIM_BALANCE_LOWor a failed transaction); it never falls back to PIXX. The other Revolution offers (1000–2500 DA, menu 1–6) and Sama/PixX 500 stay on PIXX.
Topup Type Notes:
prepaid— Standard prepaid balance topup (all operators)offer— Operator offer/plan topup (requiresoffer_id)facture— Bill payment topup (all operators)international— International topup (Mobilis only, amount must be 200–4000 DA)
Example:
curl -X POST https://api.phonifydz.com/api/v1/flexy/topup \
-H "X-API-Key: phz_your_key" \
-H "Content-Type: application/json" \
-d '{
"target_number": "0699123456",
"amount": 100,
"topup_type": "prepaid",
"idempotency_key": "order_abc123"
}'
Success Response (same object shape as /flexy/status):
{
"success": true,
"message": "Topup submitted for processing.",
"data": {
"transaction_id": 113299,
"status": "processing",
"final": false,
"operator": "mobilis",
"topup_type": "prepaid",
"target_number": "0699123456",
"amount": 100,
"cost": 96.0,
"discount_percentage": 4.0
}
}
Error codes: VALIDATION_ERROR, INVALID_PHONE, AMOUNT_TOO_LOW, INVALID_AMOUNT, INTERNATIONAL_MOBILIS_ONLY (all 422); PAY_DEBT_REQUIRED, DEBT_CHECK_REQUIRED (422); INSUFFICIENT_BALANCE (402); DUPLICATE_PROCESSING (429, same number + amount still processing); SIM_BALANCE_LOW (503, retry later); PERMISSION_DENIED (403).
Idempotent Duplicate Response: the original transaction (full object) with "idempotent": true.
GET /flexy/status/{transaction_id}
Check a flexy transaction. Poll every 3–5 s until final is true.
| Status | Meaning |
|---|---|
processing |
In progress. Funds are reserved. |
pending_verification |
Result being confirmed with the operator server; usually resolves by itself in minutes. You may call POST /flexy/verify/{id}. |
success |
Delivered. Funds deducted. final: true |
failed |
Not delivered. Funds released. final: true |
refunded |
Reversed after success. final: true |
debt_detected |
Ooredoo debt found on a line sent without a debt decision; expires to failed. Retry with the debt-check flow. |
{
"data": {
"transaction_id": 113299,
"status": "success",
"final": true,
"operator": "mobilis",
"topup_type": "prepaid",
"target_number": "0699123456",
"amount": 100.0,
"cost": 96.0,
"discount_percentage": 4.0,
"offer_id": null,
"offer_name": null,
"debt_amount": null,
"debt_paid": false,
"balance_after": 1520.5,
"response_message": "Votre demande est prise en charge",
"created_at": "2026-03-16T03:04:10+01:00",
"updated_at": "2026-03-16T03:04:21+01:00"
}
}
POST /flexy/verify/{transaction_id}
Asks the operator server for the real result of a processing, pending_verification or failed transaction and applies it (success or refund). Returns outcome, message, status, cost, balance_after. Error: NOT_ELIGIBLE (422).
GET /flexy/history
Newest first.
| Parameter | Default | Description |
|---|---|---|
per_page |
20 | 1–100 |
page |
1 | Page number |
status |
— | processing, success, failed, pending_verification, debt_detected, refunded |
operator |
— | mobilis, ooredoo, djezzy |
target_number |
— | Exact 10-digit number |
from, to |
— | Dates (YYYY-MM-DD), inclusive |
Each item has the same shape as /flexy/status.
Flexy Gros (Wholesale)
Wholesale (bulk) flexy for large amounts. Same async flow as regular Flexy — balance is reserved, then confirmed or refunded.
POST /flexy-gros/topup
| Field | Type | Required | Description |
|---|---|---|---|
target_number |
string | Yes | 10-digit phone number |
amount |
number | Yes | Amount in DA (see limits below) |
Operator Limits:
| Operator | Min | Max |
|---|---|---|
| Mobilis (06) | 5,000 DA | 300,000 DA |
| Djezzy (07) | 5,000 DA | 500,000 DA |
| Ooredoo (05) | 10,000 DA | 500,000 DA |
Duplicate Protection: Same number + amount within 30 seconds is blocked if a transaction is still processing.
Success Response:
{
"success": true,
"message": "Flexy Gros topup submitted.",
"data": {
"transaction_id": 5001,
"status": "processing",
"operator": "mobilis",
"target_number": "0699123456",
"amount": 50000,
"cost": 48000.0
}
}
GET /flexy-gros/status/{transaction_id}
Check wholesale flexy transaction status. Same response format as regular Flexy.
Possible Statuses: processing, success, failed
GET /flexy-gros/history
Paginated wholesale flexy history.
| Param | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 20 | Results per page |
PUBG Mobile
How PUBG Topup Works
The flow is: products → check-player → topup → poll status.
IMPORTANT: You MUST call
/pubg/check-playerbefore/pubg/topup. The topup endpoint requiresplayer_namewhich is returned by the check-player response. This also verifies the player ID is valid before spending balance.
Duplicate Protection: Same player + product within 30 seconds is blocked if a previous order is still pending, processing, or wait.
GET /pubg/products
List available PUBG UC products with stock and pricing. Returns error SERVICE_DISABLED (503) if PUBG service is disabled by admin.
{
"data": {
"products": [
{
"id": 1,
"product_id": "3edbc01f-...",
"product_name": "60 UC Auto Pubg By ID",
"uc_amount": 60,
"price": 230.05,
"available": true,
"stock_level": "high",
"discount_percentage": 30.0
}
]
}
}
stock_level |
Meaning |
|---|---|
high |
> 5 in stock |
low |
1–5 in stock |
out |
Out of stock — cannot be ordered |
POST /pubg/check-player
Verify a PUBG player ID. This must be called before topup.
| Field | Type | Required | Description |
|---|---|---|---|
player_id |
string | Yes | PUBG player ID (3–20 chars) |
Example:
curl -X POST https://api.phonifydz.com/api/v1/pubg/check-player \
-H "X-API-Key: phz_your_key" \
-H "Content-Type: application/json" \
-d '{"player_id":"12975615323"}'
Success Response:
{
"success": true,
"data": {
"player_id": "12975615323",
"player_name": "madriya"
}
}
Save the
player_namefrom this response — you need it for the topup request.
Error Response:
{
"success": false,
"error": {
"code": "PLAYER_NOT_FOUND",
"message": "Player not found."
}
}
POST /pubg/topup
Top up PUBG UC for a verified player. Requires check-player first.
| Field | Type | Required | Description |
|---|---|---|---|
player_id |
string | Yes | PUBG player ID |
player_name |
string | Yes | Player nickname from /check-player response |
product_id |
string | Yes | Product product_id from /pubg/products |
Success Response:
{
"success": true,
"message": "PUBG top-up order placed.",
"data": {
"order_id": 334,
"status": "pending",
"product_name": "60 UC Auto Pubg By ID",
"player_id": "12975615323",
"player_name": "TestPlayer",
"amount": 230.05
}
}
| Error Code | Description |
|---|---|
SERVICE_DISABLED |
PUBG service is currently disabled |
OUT_OF_STOCK |
Selected product has no stock |
DUPLICATE_ORDER |
Same player + product within 30s |
INSUFFICIENT_BALANCE |
Not enough balance for this product |
GET /pubg/status/{order_id}
Check PUBG order status.
Possible Statuses: pending, processing, wait, success, failed, reject
| Status | Description |
|---|---|
pending |
Order created, awaiting processing |
processing |
Being processed by upstream provider |
wait |
Queued, waiting for delivery |
success |
UC delivered to the player |
failed |
Order failed — balance is auto-refunded |
reject |
Rejected by provider — balance is auto-refunded |
GET /pubg/history
| Param | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 20 | Results per page |
Free Fire
How Free Fire Topup Works
The flow is: plans → check-player → topup → poll status.
IMPORTANT: You MUST call
/freefire/check-playerbefore/freefire/topup. The topup endpoint requiresplayer_namewhich is returned by the check-player response. This also verifies the player ID is valid and the account exists before spending balance.
Duplicate Protection: Same player + plan within 30 seconds is blocked if a previous order is still pending or processing.
GET /freefire/plans
List active Free Fire diamond plans with pricing.
{
"data": {
"plans": [
{
"id": 6,
"plan_id": "5c1ea4a8-...",
"plan_name": "100 💎 + 10 Global",
"price": 242.47,
"discount_percentage": 50.0
}
]
}
}
| Field | Description |
|---|---|
id |
Internal plan ID — use as plan_id in the topup request |
plan_id |
External plan identifier |
plan_name |
Display name with diamond amount |
price |
Your price after discount |
discount_percentage |
Your account's Free Fire discount |
POST /freefire/check-player
Verify a Free Fire player ID. This must be called before topup.
| Field | Type | Required | Description |
|---|---|---|---|
player_id |
string | Yes | Free Fire player ID (3–20 chars) |
Example:
curl -X POST https://api.phonifydz.com/api/v1/freefire/check-player \
-H "X-API-Key: phz_your_key" \
-H "Content-Type: application/json" \
-d '{"player_id":"12975615323"}'
Success Response:
{
"success": true,
"data": {
"player_id": "12975615323",
"player_name": "PlayerName",
"region": null
}
}
Save the
player_namefrom this response — you need it for the topup request.
Error Response (Not MENA region): 422 PLAYER_NOT_MENA_REGION — only MENA-region accounts can be topped up.
Error Response (Player Not Found):
{
"success": false,
"error": {
"code": "PLAYER_NOT_FOUND",
"message": "Player not found."
}
}
POST /freefire/topup
Top up Free Fire diamonds for a verified player. Requires check-player first.
| Field | Type | Required | Description |
|---|---|---|---|
player_id |
string | Yes | Free Fire player ID (3–20 chars) |
player_name |
string | Yes | Player name from /check-player response |
plan_id |
integer | Yes | Plan id from /freefire/plans |
Success Response:
{
"success": true,
"message": "Free Fire topup order placed.",
"data": {
"order_id": 150,
"trx_id": "FF-ABCDEF123456",
"status": "pending",
"plan_name": "100 💎 + 10 Global",
"player_id": "12975615323",
"amount": 242.47
}
}
| Error Code | Description |
|---|---|
PLAN_NOT_FOUND |
Plan ID does not exist or is inactive |
PLAN_CONFIG_ERROR |
Plan has no upstream provider configured |
TOPUP_FAILED |
Provider refused the order (422) — nothing charged |
DUPLICATE_ORDER |
Same player + plan within 30s |
INSUFFICIENT_BALANCE |
Not enough balance |
GET /freefire/status/{order_id}
Check Free Fire order status.
Possible Statuses: pending, processing, success, failed
| Status | Description |
|---|---|
pending |
Order created, being dispatched |
processing |
Being processed (usually about 30 seconds; may take longer during provider verification) |
success |
Diamonds delivered to the player |
failed |
Order failed — balance is auto-refunded |
GET /freefire/history
| Param | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 20 | Results per page |
IPTV
The IPTV section covers IPTV Extreme products (auto/manual delivery) and Mango VIP subscriptions. All pricing is dynamically calculated based on your account's IPTV discount percentage.
GET /iptv/products
List all available IPTV products and their variants from the Elite API. Returns products from categories: IPTV, IPTV Extreme.
{
"data": {
"products": [
{
"key": "crazy-extreme",
"name": "Crazy Extreme",
"category": "IPTV Extreme",
"variants": [
{
"key": "crazy-extreme-1m",
"name": "Crazy Extreme 1M",
"price": 350.00,
"duration": "1 Month"
},
{
"key": "crazy-extreme-3m",
"name": "Crazy Extreme 3M",
"price": 900.00,
"duration": "3 Months"
}
]
}
],
"discount_percentage": 10.0
}
}
| Response Field | Description |
|---|---|
products[].key |
Unique product identifier — use in purchase |
products[].category |
Product category: "IPTV" or "IPTV Extreme" |
products[].variants[].key |
Unique variant identifier — use in purchase |
products[].variants[].price |
Your price after discount |
products[].variants[].duration |
Duration in months (integer) or label string |
discount_percentage |
Your account's IPTV discount percentage |
POST /iptv/purchase
Purchase an IPTV subscription. Most orders are asynchronous — poll /iptv/status/{id} for the final result.
| Field | Type | Required | Description |
|---|---|---|---|
product_key |
string | Yes | Product key from /iptv/products |
variant_key |
string | Yes | Variant key from product variants |
quantity |
integer | Yes | Number of subscriptions (1–10) |
Success Response (queued):
{
"success": true,
"message": "Order queued for processing.",
"data": {
"order_id": 123,
"status": "processing",
"queue_position": 3,
"product_name": "Crazy Extreme",
"variant_name": "Crazy Extreme 3M",
"amount": 810.00
}
}
Success Response (instant):
{
"success": true,
"message": "IPTV purchase completed.",
"data": {
"order_id": 124,
"status": "completed",
"product_name": "Formula TEST EXTR",
"variant_name": "Formula TEST EXTR 1M",
"code": "user: abc123 / pass: xyz789",
"amount": 44.00
}
}
GET /iptv/status/{order_id}
Check IPTV order status. For processing orders, this also queries the upstream provider for real-time status.
| Status | Description |
|---|---|
processing |
Order is being processed or queued |
completed |
Subscription activated — code contains credentials |
failed |
Order failed — balance is auto-refunded |
GET /iptv/history
Paginated list of all your IPTV orders (including Mango VIP orders).
| Param | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 20 | Results per page |
POST /iptv/mango/check
Check a Mango VIP serial number and get available offers with pricing. This must be called before purchasing a Mango package.
| Field | Type | Required | Description |
|---|---|---|---|
serial_number |
string | Yes | Mango device serial number (6–20 chars) |
Success Response:
{
"success": true,
"data": {
"serial_number": "ABC123456789",
"is_valid": true,
"has_subscription": true,
"services": [
{
"service_name": "Funcam",
"status": "active",
"expiry_date": "2026-06-15",
"days_left": 91,
"offers": [
{
"package_name": "3M",
"duration": "3 Months",
"price": 1200.00,
"can_buy": true
},
{
"package_name": "12M",
"duration": "12 Months",
"price": 3500.00,
"can_buy": true
}
]
},
{
"service_name": "Forever",
"status": "expired",
"expiry_date": "2025-12-01",
"days_left": 0,
"offers": [
{
"package_name": "12M",
"duration": "12 Months",
"price": 4000.00,
"can_buy": true
}
]
}
]
}
}
| Error Code | Description |
|---|---|
INVALID_SERIAL |
Serial number rejected by the provider |
SERIAL_NOT_FOUND |
Serial number does not exist |
CHECK_FAILED |
Provider service unavailable |
POST /iptv/mango/purchase
Purchase a Mango VIP package for a serial number. You must call /iptv/mango/check first to get available packages.
| Field | Type | Required | Description |
|---|---|---|---|
serial_number |
string | Yes | Serial number (verified via /mango/check) |
service_name |
string | Yes | Service name from check response (e.g. "Funcam") |
package_name |
string | Yes | Package name from check response (e.g. "12M") |
Success Response:
{
"success": true,
"message": "Mango VIP package activated.",
"data": {
"order_id": 456,
"status": "completed",
"message": "Package activated successfully.",
"amount": 3500.00
}
}
Failure (balance refunded automatically):
{
"success": false,
"error": "Purchase failed.",
"error_code": "PURCHASE_FAILED",
"details": { "order_id": 456 }
}
Digital Products (Wasmou)
Digital products include items like game top-ups, streaming subscriptions, gift cards, and other digital services. Some products require input fields (e.g. a player ID or email address).
GET /products
List all available digital products with pricing.
| Param | Type | Default | Description |
|---|---|---|---|
search |
string | — | Search by product name |
category |
string | — | Filter by category |
per_page |
integer | 20 | Results per page |
Response:
{
"success": true,
"data": {
"products": [
{
"id": 42,
"name": "MEGA OTT IPTV",
"description": "Premium IPTV service",
"category": "iptv",
"image": "https://...",
"variants": [
{
"id": 101,
"wasmou_variant_id": "was_v_123",
"name": "1 Month",
"price": 1350.00,
"has_discount": true,
"discount_percentage": 50.0,
"input_fields": []
}
]
},
{
"id": 55,
"name": "Canva Pro",
"description": "Canva Pro subscription",
"category": "design",
"image": "https://...",
"variants": [
{
"id": 5596,
"wasmou_variant_id": "was_v_5596",
"name": "LIFETIME",
"price": 450.00,
"has_discount": true,
"discount_percentage": 50.0,
"input_fields": [
{
"type": "email",
"label": "Email Address",
"rules": "required|email",
"placeholder": "Enter your email"
}
]
}
]
}
],
"pagination": { "current_page": 1, "last_page": 3, "per_page": 20, "total": 58 }
}
}
Important: The
input_fieldsarray tells you what inputs are required before purchasing. Ifinput_fieldsis not empty, you must send these values in theinputsobject when calling/products/purchase.
Input Fields Structure
Each entry in input_fields is an object with these properties:
| Key | Description |
|---|---|
type |
Input type and field identifier: text, email, number, etc. Use this as the key in the inputs object |
label |
Human-readable label (may be in Arabic or other languages) |
rules |
Validation rules (e.g. "required", `"required |
placeholder |
Placeholder text for the input |
name |
Optional field name — if present, use this instead of type as the key in inputs |
Real example — Canva Pro requires an email:
"input_fields": [
{
"type": "email",
"label": "Email Address",
"rules": "required|email",
"placeholder": "Enter your email"
}
]
When purchasing, pass: "inputs": { "email": "user@example.com" }
Real example — FOREVER requires a serial number:
"input_fields": [
{
"type": "text",
"label": "السريال",
"rules": "required",
"placeholder": "اكتب السريال هنا"
}
]
When purchasing, pass: "inputs": { "text": "YOUR_SERIAL_HERE" }
Note: If
input_fieldscontains a required field and you don't provide it ininputs, the API returnsMISSING_INPUTS(422).
GET /products/{id}
Get a specific product with its variants and pricing details.
{
"success": true,
"data": {
"product": {
"id": 42,
"name": "MEGA OTT IPTV",
"description": "Premium IPTV service",
"category": "iptv",
"image": "https://...",
"variants": [
{
"id": 101,
"wasmou_variant_id": "was_v_123",
"name": "1 Month",
"price": 1350.00,
"has_discount": true,
"discount_percentage": 50.0,
"input_fields": []
}
]
}
}
}
POST /products/purchase
Purchase a digital product variant. If the product has input_fields, you must provide the required inputs.
| Field | Type | Required | Description |
|---|---|---|---|
variant_id |
integer | Yes | Variant id from /products response |
quantity |
integer | Yes | Number to purchase (1–100) |
inputs |
object | Conditional | Required inputs if variant has input_fields |
Example request with inputs:
{
"variant_id": 102,
"quantity": 1,
"inputs": {
"email": "customer@example.com"
}
}
Example request without inputs:
{
"variant_id": 101,
"quantity": 1
}
Success Response:
{
"success": true,
"message": "Product purchased successfully.",
"data": {
"order_id": 789,
"status": "completed",
"product_name": "MEGA OTT IPTV",
"variant_name": "1 Month",
"quantity": 1,
"total_price": 1350.00,
"key": "ABCD-1234-EFGH-5678"
}
}
If the order is asynchronous:
{
"success": true,
"message": "Order submitted, processing...",
"data": {
"order_id": 790,
"status": "processing",
"product_name": "MEGA OTT IPTV",
"variant_name": "3 Months",
"quantity": 1,
"total_price": 3500.00
}
}
GET /products/status/{order_id}
Check digital product order status. For pending/processing orders, this queries the upstream provider.
| Status | Description |
|---|---|
pending |
Order created, not yet submitted to provider |
processing |
Submitted to provider, awaiting fulfillment |
completed |
Fulfilled — key contains the product code/key |
failed |
Failed — balance is auto-refunded |
Response (completed):
{
"success": true,
"data": {
"order_id": 789,
"status": "completed",
"product_name": "MEGA OTT IPTV",
"variant_name": "1 Month",
"key": "ABCD-1234-EFGH-5678",
"created_at": "2026-03-16T10:30:00+00:00"
}
}
GET /products/history
Paginated list of your digital product orders.
| Param | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 20 | Results per page |
TikTok
How TikTok Recharge Works
TikTok coin recharges are processed manually by the PHONIFY team. After placing an order, delivery may take up to a few hours. Poll the status endpoint to track progress.
GET /tiktok/pricing
Get current TikTok coin rates, limits, and example prices.
{
"data": {
"rate_per_coin": 2.78,
"currency": "DZD",
"min_coins": 100,
"max_coins": 100000,
"example_prices": [
{ "coins": 100, "price": 278 },
{ "coins": 500, "price": 1390 },
{ "coins": 1000, "price": 2780 }
]
}
}
| Field | Description |
|---|---|
rate_per_coin |
Price per coin in DZD |
min_coins |
Minimum coins per order |
max_coins |
Maximum coins per order |
Price formula: total_price = ceil(coin_amount × rate_per_coin)
POST /tiktok/recharge
Place a TikTok coin recharge order.
| Field | Type | Required | Description |
|---|---|---|---|
coin_amount |
number | Yes | Number of coins (between min_coins and max_coins from /pricing) |
tiktok_email |
string | Yes | TikTok account email address |
tiktok_password |
string | No | TikTok password (some accounts require this for login) |
Note: Delivery is manual and may take up to a few hours. The coins are sent to the TikTok account associated with the provided email.
Success Response:
{
"success": true,
"message": "TikTok recharge order placed.",
"data": {
"order_id": 55,
"status": "pending",
"coin_amount": 500,
"total_price": 1390,
"tiktok_email": "user@example.com"
}
}
GET /tiktok/status/{order_id}
Check TikTok recharge order status.
| Status | Description |
|---|---|
pending |
Order created, awaiting manual processing |
processing |
Being processed by the team |
completed |
Coins delivered to the TikTok account |
failed |
Order failed — balance is auto-refunded |
Response includes: order_id, status, coin_amount, total_price, tiktok_email, delivered_at, created_at
GET /tiktok/history
| Param | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 20 | Results per page |
Vouchers
GET /vouchers/categories
List available voucher categories with stock and pricing.
{
"data": {
"categories": [
{
"category": "mobilis",
"total_available": 5401,
"denominations": [
{
"face_value": 100.0,
"price": 97.0,
"discount_percentage": 3.0,
"available": 4999
},
{
"face_value": 1000.0,
"price": 970.0,
"discount_percentage": 3.0,
"available": 3
}
]
}
]
}
}
POST /vouchers/purchase
Purchase digital vouchers. Returns actual voucher codes immediately (synchronous).
| Field | Type | Required | Description |
|---|---|---|---|
category |
string | Yes | mobilis, ooredoo, djezzy, adsl, 4g, pubg_uc |
amount |
number | Yes | Face value (must match an available denomination from /categories) |
quantity |
integer | Yes | Number of vouchers (1–100) |
Success Response:
{
"success": true,
"message": "Voucher purchase completed.",
"data": {
"order_id": 45,
"status": "completed",
"category": "mobilis",
"quantity": 2,
"unit_price": 97.0,
"total_price": 194.0,
"discount_percentage": 3.0,
"vouchers": [
{
"code": "1234567890123",
"serial": "SER123456",
"face_value": 100.0
},
{
"code": "9876543210987",
"serial": "SER789012",
"face_value": 100.0
}
]
}
}
GET /vouchers/history
| Param | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 20 | Results per page |
| Error Code | Description |
|---|---|
INSUFFICIENT_STOCK |
Not enough vouchers in stock for the requested amount/quantity |
INSUFFICIENT_BALANCE |
Not enough balance |
OUT_OF_STOCK |
No vouchers available for this denomination |
Webhooks & Polling
The PHONIFY API uses asynchronous processing for most services. After submitting an order, you should poll the status endpoint.
Recommended Polling Strategy
1. Submit order → get order_id
2. Wait 3 seconds
3. GET /status/{order_id}
4. If status is "processing" or "pending":
- Wait 5 seconds, check again
- Increase wait to 10s, then 30s
- Maximum polling: 5 minutes
5. If status is "success" → done
6. If status is "failed" → balance is auto-refunded
Status Flow
pending → processing → success
↘ failed (auto-refund)
Idempotency
Use idempotency_key on Flexy endpoints to prevent duplicate charges:
curl -X POST .../flexy/topup \
-d '{"idempotency_key": "unique-order-id-123", ...}'
If you send the same idempotency_key again, the API returns the original transaction without creating a new one.
SDK Examples
cURL
# Check balance
curl -H "X-API-Key: phz_your_key" \
https://api.phonifydz.com/api/v1/account/balance
# Flexy topup
curl -X POST \
-H "X-API-Key: phz_your_key" \
-H "Content-Type: application/json" \
-d '{"target_number":"0699123456","amount":100,"topup_type":"prepaid"}' \
https://api.phonifydz.com/api/v1/flexy/topup
Python
import requests
API_KEY = "phz_your_key"
BASE_URL = "https://api.phonifydz.com/api/v1"
HEADERS = {"X-API-Key": API_KEY}
# Get balance
r = requests.get(f"{BASE_URL}/account/balance", headers=HEADERS)
print(r.json()["data"]["available_balance"])
# Flexy topup with polling
r = requests.post(f"{BASE_URL}/flexy/topup", headers=HEADERS, json={
"target_number": "0699123456",
"amount": 100,
"topup_type": "prepaid",
"idempotency_key": "my-unique-id"
})
data = r.json()["data"]
txn_id = data["transaction_id"]
import time
for _ in range(10):
time.sleep(5)
status = requests.get(f"{BASE_URL}/flexy/status/{txn_id}", headers=HEADERS)
s = status.json()["data"]["status"]
if s in ("success", "failed"):
print(f"Final status: {s}")
break
PHP
$apiKey = 'phz_your_key';
$baseUrl = 'https://api.phonifydz.com/api/v1';
$ch = curl_init("$baseUrl/account/balance");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: $apiKey"],
]);
$response = json_decode(curl_exec($ch), true);
echo $response['data']['available_balance'];
JavaScript (Node.js)
const API_KEY = 'phz_your_key';
const BASE = 'https://api.phonifydz.com/api/v1';
const res = await fetch(`${BASE}/account/balance`, {
headers: { 'X-API-Key': API_KEY }
});
const { data } = await res.json();
console.log(data.available_balance);
MCP Server (AI Integration)
PHONIFY provides a hosted MCP server that lets AI assistants (like Cursor, Claude, or any MCP-compatible client) interact with the PHONIFY API directly. No downloads or local setup required — just point your AI client to the URL and pass your API key.
Server URL: https://mcp.phonifydz.com/mcp
What is MCP?
The Model Context Protocol is an open standard that allows AI applications to connect to external tools and data sources. With the PHONIFY MCP server, your AI assistant can:
- Check balances, list products, and query pricing
- Place orders (Flexy, PUBG, Free Fire, IPTV, TikTok, Vouchers, Digital Products)
- Poll order statuses and retrieve activation codes/keys
- Access full API documentation and error code references
Setup
Configure your AI client to connect to the hosted MCP server. No installation needed.
Cursor IDE — add to .cursor/mcp.json:
{
"mcpServers": {
"phonify": {
"type": "streamable-http",
"url": "https://mcp.phonifydz.com/mcp",
"headers": {
"X-API-Key": "phz_your_api_key"
}
}
}
}
Claude Desktop — add to claude_desktop_config.json:
{
"mcpServers": {
"phonify": {
"type": "streamable-http",
"url": "https://mcp.phonifydz.com/mcp",
"headers": {
"X-API-Key": "phz_your_api_key"
}
}
}
}
Any MCP client — connect to:
- URL:
https://mcp.phonifydz.com/mcp - Method:
POST(Streamable HTTP transport) - Auth: Pass your PHONIFY API key in the
X-API-Keyheader
Server Info
You can check if the MCP server is running by visiting the root URL:
GET https://mcp.phonifydz.com/
Returns server info, version, and the number of available tools.
Available Tools (37)
The MCP server exposes 37 tools covering every API endpoint:
| Category | Tools |
|---|---|
| Account | get_balance, get_profile, list_services |
| Flexy | flexy_operators, flexy_offers, flexy_topup, flexy_status, flexy_history |
| Flexy Gros | flexy_gros_topup, flexy_gros_status, flexy_gros_history |
| PUBG | pubg_products, pubg_check_player, pubg_topup, pubg_status, pubg_history |
| Free Fire | freefire_plans, freefire_check_player, freefire_topup, freefire_status, freefire_history |
| IPTV | iptv_products, iptv_purchase, iptv_status, iptv_history, iptv_mango_check, iptv_mango_purchase |
| Digital Products | products_list, products_show, products_purchase, products_status, products_history |
| TikTok | tiktok_pricing, tiktok_recharge, tiktok_status, tiktok_history |
| Vouchers | voucher_categories, voucher_purchase, voucher_history |
MCP Resources
The server also provides two read-only resources that AI assistants can reference:
| Resource URI | Description |
|---|---|
phonify://docs/api |
Full API endpoint overview with all routes and key flows |
phonify://docs/errors |
Complete error codes reference with HTTP status codes |
Example Conversation with AI
Once connected, you can ask your AI assistant things like:
- "Check my PHONIFY balance" → calls
get_balance - "Send 100 DA flexy to 0699123456" → calls
flexy_topup - "What IPTV products are available?" → calls
iptv_products - "Top up 60 UC for PUBG player 12975615323" → calls
pubg_check_playerfirst, thenpubg_topup - "Buy 5 Mobilis 100 DA vouchers" → calls
voucher_purchase - "Order Canva Pro with email user@example.com" → calls
products_purchasewithinputs
The AI handles the correct flow automatically (e.g. calling check_player before topup for PUBG/FreeFire).
Testing Checklist
Use this checklist when integrating with the PHONIFY API:
Authentication
- Request without API key →
MISSING_API_KEY(401) - Request with invalid key →
INVALID_API_KEY(401) - Request with deactivated key →
API_KEY_INACTIVE(403) - Request from non-whitelisted IP →
IP_NOT_WHITELISTED(403)
Rate Limiting
- Verify
X-RateLimit-*headers are present - Exceed per-minute limit →
RATE_LIMIT_EXCEEDED(429) - Check
Retry-Afterheader on 429
Balance
- Check balance before purchase
- Attempt purchase with insufficient balance →
INSUFFICIENT_BALANCE(402) - Verify balance deducted after successful purchase
- Verify balance restored after failed purchase
Service Operations
- List products/plans/pricing before ordering
- PUBG / Free Fire: Call
/check-playerfirst, getplayer_name, then pass it to/topup - Submit order with valid parameters →
success(200) - Submit order with missing fields →
VALIDATION_ERROR(422) - Submit duplicate order →
DUPLICATE_ORDER(429) - Test idempotency key deduplication (Flexy)
- Poll status until final state (
successorfailed) - Digital Products: Check
input_fieldsand pass requiredinputs - IPTV Mango: Call
/mango/checkbefore/mango/purchase
Error Handling
- Handle 5xx errors with retry logic
- Handle 502 (external service down) gracefully
- Handle 503 (service maintenance) gracefully
- Parse error codes programmatically (not messages)
Glossary
| Term | Description |
|---|---|
| DA / DZD | Algerian Dinar — all prices are in this currency |
| Flexy | Mobile prepaid credit recharge |
| Flexy Gros | Wholesale/bulk flexy (minimum 5,000 DA) |
| PUBG UC | PUBG Mobile Unknown Cash (in-game currency) |
| Free Fire Diamonds | Free Fire in-game currency |
| IPTV Extreme | IPTV subscriptions via Elite API (auto/manual delivery) |
| Mango VIP | IPTV service activated via serial number on Mango devices |
| Digital Products | Wasmou-based digital products (game top-ups, streaming, gift cards) |
| input_fields | Required user inputs for certain digital products (e.g. email, player ID) |
| Idempotency Key | Unique identifier to prevent duplicate transactions |
| Reserved Balance | Balance locked for pending transactions |
| MCP | Model Context Protocol — open standard for AI tool integration |
Support
For API integration support:
- Create a ticket in PHONIFY
- Contact your account manager
PHONIFY API v1 — Built for developers, by PHONIFY.
Get an API key