Phonify Get an API key

PHONIFY API Documentation v1

Base URL: https://api.phonifydz.com/api/v1 Alternative: https://www.phonifydz.com/api/v1 Version: 1.0.0 | Last Updated: 2026-03-16


Table of Contents

  1. Authentication
  2. Rate Limiting
  3. Response Format
  4. Error Codes Reference
  5. Account Endpoints
  6. Flexy (Mobile Topup)
  7. Flexy Gros (Wholesale)
  8. PUBG Mobile
  9. Free Fire
  10. IPTV
  11. Digital Products (Wasmou)
  12. TikTok
  13. Vouchers
  14. Webhooks & Polling
  15. SDK Examples
  16. MCP Server (AI Integration)
  17. 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

  1. Log into your PHONIFY account
  2. Navigate to API Settings (/api-settings)
  3. Click Generate Key and name your key
  4. Copy the key immediately — it is shown only once

IP Whitelisting (Optional)

You can restrict API key usage to specific IP addresses:


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:

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):

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

5. Transaction Flow

POST /flexy/topup → status: "processing" → (async SIM recharge) → "success" or "failed"
  1. Submit — API validates input, checks balance, reserves funds, returns transaction_id
  2. Processing — Background job sends the actual recharge via SIM hardware
  3. Complete — Status updates to success (funds confirmed) or failed (funds refunded)

Typical processing time: 5–15 seconds. Poll /flexy/status/{id} to check.

6. Balance Management

7. Duplicate Protection

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):

  1. POST /flexy/ooredoo-debt-check with target_number, amount, topup_type (prepaid or offer). Returns has_debt and debt_amount. The result is remembered 10 minutes.
  2. POST /flexy/topup with pay_debt: true (debt is added to your cost and paid with the topup) or pay_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_number 7 for 500 DA, menu_number 8 for 100 DA) are normal-SIM offers, not PIXX. Send topup_type: "offer" with the amount, menu_number, offer_id and offer_name exactly 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_LOW or 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:

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-player before /pubg/topup. The topup endpoint requires player_name which 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_name from 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-player before /freefire/topup. The topup endpoint requires player_name which 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_name from 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_fields array tells you what inputs are required before purchasing. If input_fields is not empty, you must send these values in the inputs object 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_fields contains a required field and you don't provide it in inputs, the API returns MISSING_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.

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:

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:

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:

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

Rate Limiting

Balance

Service Operations

Error Handling


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:


PHONIFY API v1 — Built for developers, by PHONIFY.