Xtream-Masters Reseller API

Programmatically manage lines, MAG devices, Enigma2 devices, activecodes, sub-resellers, and more through a simple REST-style API.

Authentication

All API requests require your api_key parameter. Include it as a query parameter on every request.

Where do I find my API key? Your API key is available in your reseller panel under API Settings. Each reseller account has a unique key tied to their api_access_code.

Base URL

All requests are made to the following base URL format:

http://{server-dns}:{stream-port}/{api-access-code}/reseller/index.php

Replace {server-dns}, {stream-port}, and {api-access-code} with your server details.

Quick Start

Get up and running in minutes 1. Obtain your API key from the reseller panel.
2. Note your server DNS, streaming port, and API access code.
3. Make your first request to user_info to verify connectivity.
4. For WHMCS integration, install the WHMCS Addon.
Quick Test
curl "http://your-server:port/access-code/reseller/index.php?api_key=YOUR_KEY&action=user_info"

Global Parameters

These parameters are available on all or most endpoints.

ParameterTypeRequiredDescription
api_keystringRequiredYour unique API key for authentication
actionstringRequiredThe API action to perform
startintOptionalPagination offset for list endpoints (default: 0)
limitintOptionalNumber of records to return (default: 50)
show_columnsstringOptionalComma-separated list of column names to include in the response
dry_runintOptionalNew. 1 = validate, price and shape the write, then change nothing. The reply is the record that would have been written plus cost, credits_before and credits_after. Available on every create / edit / extend action.
request_idstringOptionalNew. An id of your own on a create. A retry with the same value inside 24 hours returns the record the first call made, with "replayed": true, and charges nothing. Survives within one running server; after a restart a replay behaves as a fresh call.
countintOptionalNew. On create_line and create_activecode: make N records in one call, up to 100. Gated on the group's Generate Mass Paid / Trial switches and their counts.
idsstringOptionalNew. On every delete / disable / enable: a comma-separated list, up to 100. The reply lists each id with its own status plus succeeded and failed.
searchstringOptionalNew. The search term, without the DataTables search[value] nesting. Both forms work.
order_bystringOptionalNew. Sort by column NAME instead of by the positional order[0][column] index. An unknown name is refused and the reply lists the ones this endpoint knows.
order_dirstringOptionalNew. asc or desc.
sinceintOptionalNew. Unix time — only records created at or after it. With until this is a delta sync: poll for what changed instead of re-reading everything.
untilintOptionalNew. Unix time — only records created at or before it.
add_special_bouquetsintOptionalNew. See create_line.
hide_columnsstringOptionalComma-separated list of column names to exclude from the response

Warnings

A successful call that could not do everything you asked tells you what it skipped and where to turn it on.

Response
{
  "status": "STATUS_SUCCESS",
  "data": { ... the line ... },
  "warnings": [
    {
      "code": "connections_not_allowed",
      "field": "max_connections",
      "message": "This package sells a single screen, so max_connections was ignored and the package's value was used.",
      "setting": "Panel -> Manage Packages -> (test package 24h) -> Multiple Connections"
    }
  ]
}
codeWhat it means
connections_not_allowedThe package sells one screen; max_connections was ignored.
connections_cappedYou asked for more screens than the package allows.
connection_pricing_missingThe package has no multi-connection price list, so the charge was the base price × screens.
adult_not_enabledYour group may not sell adult content.
adult_not_configuredThe server’s adult bouquet set is empty or switched off.
forced_country_not_allowedYour group may not force a country.
forced_country_from_packageThe package forces its own country, which wins.
date_on_usage_unavailableStart-on-first-use is not available on this server.
custom_pack_not_foundNo saved arrangement with that id belongs to you.
custom_pack_needs_upgradeThat arrangement is in the older format; open it once in the panel to convert it.
reset_password_unnecessaryYour group may set passwords directly — send password.
mass_generation_disabledYour group may not mass-generate.

What’s New

Everything below is additive. A request that sends none of the new parameters behaves exactly as it always did, byte for byte.

Added
  • Dry runs. dry_run=1 on any write validates it, prices it and answers with the record it would have created — without writing or charging.
  • Idempotent creates. request_id=<your id> makes a retried create safe: the same record comes back instead of a second one and a second charge.
  • Bulk. count=N on creates, ids=1,2,3 on delete / disable / enable, up to 100 per call, with a per-item result list.
  • Pagination and filters on every list. total, start, limit, returned, has_more, plus search, order_by, order_dir and column filters.
  • Delta sync. since / until on the lists, so a client can poll for what changed.
  • Machine-readable errors. Every failure now carries error_code, and where one field is at fault, field and a one-sentence hint. The status string is unchanged.
  • Permission refusals name the setting. A refusal caused by a group setting carries setting with the exact panel path, e.g. Panel -> Manage Groups -> (your group) -> "Generate Mass Paid".
  • Nothing is dropped in silence. If the install cannot do what you asked — the group lacks the permission, the package sells fewer screens, the adult set is switched off — the call still succeeds and the reply carries a warnings array saying which parameter was ignored, why, and the exact panel path that would make it work.
  • Everything the panel’s line editor can set, the API can set. private_cdn, allow_epg, forced_country, reset_password, custom_playlist_id and the adult switch were all panel-only; created_by is now recorded, where every line the API created used to be attributed to nobody.
  • Credit ledger. The new credits_log action reads back the charges and refunds on your account.
  • Adult bouquets. add_special_bouquets=1 on a line adds them and sets is_adult, 0 removes them. There was no way to do either over the API before.
  • Expiry deltas. An extend reports exp_date_before, exp_date_after and exp_date_added.
Fixed — behaviour you may have been working around
  • activity_logs and live_connections never worked at all. Both queried a column the database does not have, so both returned the literal null on GET and on POST alike. They return rows now.
  • List endpoints answered null on POST. get_lines, get_mags, get_enigmas, get_users, get_activecodes, activity_logs, live_connections and user_logs returned the literal four bytes null for a POSTed request — which is what every example in this document showed. They now work on POST and on GET.
  • show_columns / hide_columns deleted rows from packages. show_columns=id,package_name returned one package with every column; hide_columns=groups dropped a package entirely. They now filter columns, on every action.
  • delete / disable / enable ignored ownership. Any api_key could delete, disable or enable any line, MAG or Enigma device on the server by id. They are now scoped to your own sub-tree.
  • The Delete Users and User Delete Refund group permissions were ignored. Both are honoured now, on lines, devices and codes.
  • A chosen ActiveCode that was taken was silently swapped for a random one, and the call still reported success.
  • Duplicate sub-reseller usernames were accepted — the uniqueness check ran against the wrong table.
  • edit_user failed unless username was re-sent, so changing only a note or an email did nothing and returned STATUS_FAILURE.
  • Deleting a sub-reseller orphaned its lines (member_id = NULL): still streaming, owned by nobody, invisible to every listing. They are re-homed to you now.
  • create_user and edit_user returned the account’s password hash. They no longer do.
  • Every edit wrote a junk credit-ledger row reading charge: "-". Only real charges are recorded now.
  • Extra screens were free. max_connections=5 granted five screens and charged the one-screen price; the package’s own multiconx_pricing ladder is applied now, on trials too.
  • Concurrent writes could lose a charge. Credit arithmetic is now a single additive statement, so two calls in flight cannot overwrite each other.

General

Retrieve reseller account information and available packages.

GET user_info Get Reseller Account Info

Returns your reseller account details including credits, status, and account settings.

Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=user_info"
Response
{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 152,
    "username": "myreseller",
    "email": "reseller@example.com",
    "credits": 485.00,
    "status": 1,
    "reseller_dns": "my.dns.com",
    "created_at": "2024-01-15 10:30:00",
    "member_group_id": 3,
    "allowed_pages": ["lines", "mag", "enigma", "activecodes"]
  }
}
GET packages Get Available Packages

Returns the list of packages available to your reseller account with pricing and duration details.

Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=packages"
Response
{
  "status": "STATUS_SUCCESS",
  "data": [
    {
      "id": 1,
      "package_name": "1 Month",
      "credits": 5.00,
      "duration_months": 1,
      "is_trial": 0,
      "is_official": 1,
      "groups": [1, 3, 5]
    },
    {
      "id": 2,
      "package_name": "24h Trial",
      "credits": 0.00,
      "duration_months": 0,
      "is_trial": 1,
      "is_official": 1,
      "groups": [1, 3]
    }
  ]
}

Line Management (M3U)

Create, manage, and control M3U lines for your customers.

POST create_line Create Line

Creates a new M3U line. Username and password are auto-generated if not provided.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
  • count=N — create up to 100 lines in one call (gated on the group's Generate Mass Paid / Trial switches and their counts).
  • max_connections is honoured up to the package's multiple_connections cap.
ParameterTypeRequiredDescription
packageintRequiredPackage ID from the packages endpoint
trialintRequired0 = regular, 1 = trial
usernamestringOptionalCustom username (auto-generated if empty)
passwordstringOptionalCustom password (auto-generated if empty)
is_isplockintOptional0 = disabled, 1 = enable ISP lock
allowed_ips[]arrayOptionalArray of allowed IP addresses
reseller_notesstringOptionalInternal notes for the reseller
bouquets_selected[]arrayOptionalArray of bouquet IDs to assign
max_connectionsintOptionalPriced. Screens to sell, up to the package’s multiple_connections cap. The package’s multiconx_pricing ladder decides the charge — e.g. 2 screens for 2 credits, 5 for 5 — falling back to base price × screens when the ladder has no entry for that count. It used to grant the extra screens and charge the one-screen price.
add_special_bouquetsintOptionalNew. 1 merges the adult bouquets into the line and sets is_adult; 0 removes them and clears it. Omit it and the line keeps whatever it has. Requires the group’s Enable Adult permission and an enabled Special Bouquets set.
private_cdnintOptionalNew. 1 routes the line through the private CDN, 0 off. Omit to leave it as it is.
allow_epgintOptionalNew. 1 includes the line in XMLTV EPG output, 0 excludes it (lighter on low-power devices). Omit to leave it as it is.
forced_countrystringOptionalNew. Two-letter country the line is locked to. Requires the group’s Can Force Country permission; a package that forces its own country overrides it. Either case comes back in warnings.
reset_passwordintOptionalNew. 1 issues a fresh password. For groups that may not choose passwords — a group that may should send password.
custom_playlist_idintOptionalNew. Apply one of your saved category arrangements (Custom Packs) to the line. Only your own packs; a pack in the older bouquet-based format is reported in warnings instead of being applied.
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=create_line" \
  -d "package=1" \
  -d "trial=0" \
  -d "username=customer1" \
  -d "password=securepass" \
  -d "reseller_notes=Premium customer"
Response
{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 4521,
    "username": "customer1",
    "password": "securepass",
    "package_id": 1,
    "exp_date": 1740000000,
    "is_trial": 0,
    "status": 1,
    "reseller_notes": "Premium customer",
    "created_at": "2025-01-15 14:30:00"
  }
}
POST edit_line Edit / Extend Line

Edit an existing line's properties or extend it with a new package. Provide the package parameter to extend the line's expiry.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
  • exp_date_before, exp_date_after and exp_date_added are reported when the expiry moves.
ParameterTypeRequiredDescription
idintRequiredLine ID to edit
packageintOptionalPackage ID to extend with
usernamestringOptionalNew username
passwordstringOptionalNew password
is_isplockintOptional0 = disabled, 1 = enable ISP lock
allowed_ips[]arrayOptionalArray of allowed IP addresses
reseller_notesstringOptionalInternal notes
bouquets_selected[]arrayOptionalArray of bouquet IDs
max_connectionsintOptionalPriced. Screens to sell, up to the package’s multiple_connections cap. The package’s multiconx_pricing ladder decides the charge — e.g. 2 screens for 2 credits, 5 for 5 — falling back to base price × screens when the ladder has no entry for that count. It used to grant the extra screens and charge the one-screen price.
add_special_bouquetsintOptionalNew. 1 merges the adult bouquets into the line and sets is_adult; 0 removes them and clears it. Omit it and the line keeps whatever it has. Requires the group’s Enable Adult permission and an enabled Special Bouquets set.
private_cdnintOptionalNew. 1 routes the line through the private CDN, 0 off. Omit to leave it as it is.
allow_epgintOptionalNew. 1 includes the line in XMLTV EPG output, 0 excludes it (lighter on low-power devices). Omit to leave it as it is.
forced_countrystringOptionalNew. Two-letter country the line is locked to. Requires the group’s Can Force Country permission; a package that forces its own country overrides it. Either case comes back in warnings.
reset_passwordintOptionalNew. 1 issues a fresh password. For groups that may not choose passwords — a group that may should send password.
custom_playlist_idintOptionalNew. Apply one of your saved category arrangements (Custom Packs) to the line. Only your own packs; a pack in the older bouquet-based format is reported in warnings instead of being applied.
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=edit_line" \
  -d "id=4521" \
  -d "package=2" \
  -d "reseller_notes=Extended for another month"
POST extend_line Extend Line

Extend an existing line by re-applying a package. The package's official duration is added to the line's current expiry (or from now if already expired) and the package credits are charged to your account. Username, password and bouquets are preserved. (Equivalent to edit_line with only a package — provided as a dedicated action for clarity.)

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
  • exp_date_before, exp_date_after and exp_date_added report exactly what the extend did.
ParameterTypeRequiredDescription
idintRequiredLine ID to extend
packageintRequiredPackage ID whose official duration is added to the line
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=extend_line" \
  -d "id=4521" \
  -d "package=2"
Response
{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 4521,
    "exp_date": 1786708220
  }
}
GET get_line Get Single Line

Retrieve details of a single M3U line by its ID.

ParameterTypeRequiredDescription
idintRequiredLine ID
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_line&id=4521"
Response
{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 4521,
    "username": "customer1",
    "password": "securepass",
    "package_id": 1,
    "exp_date": 1740000000,
    "is_trial": 0,
    "is_isplock": 0,
    "status": 1,
    "allowed_ips": [],
    "reseller_notes": "Premium customer",
    "bouquets": [1, 4, 7],
    "created_at": "2025-01-15 14:30:00"
  }
}
GET get_lines List All Lines

Retrieve a paginated list of all M3U lines. Supports search and status filtering.

New in this version
  • Works on POST as well as GET. It previously answered the literal null for any POSTed request.
  • Response gains total, start, limit, returned and has_more beside recordsTotal.
  • search= (no DataTables nesting), order_by=<column name>, order_dir=asc|desc.
  • Filters: enabled, admin_enabled, is_trial, package_id, member_id, exp_before, exp_after, since, until.
ParameterTypeRequiredDescription
startintOptionalPagination offset (default: 0)
limitintOptionalRecords per page (default: 50)
search[value]stringOptionalSearch by username or other fields
filterintOptional1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_lines&start=0&limit=25&filter=1"
POST delete_line Delete Line

Permanently delete a line. This action cannot be undone.

New in this version
  • ids=1,2,3 — delete up to 100 in one call; the reply lists each id with its own status plus succeeded / failed.
  • Honours the group's Delete Users permission (it was ignored before).
  • Honours the group's User Delete Refund permission: the pro-rata refund is credited and reported as refunded.
ParameterTypeRequiredDescription
idintRequiredLine ID to delete
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=delete_line" \
  -d "id=4521"
POST disable_line Disable Line

Temporarily disable a line. The line can be re-enabled later.

New in this version
  • ids=1,2,3 — up to 100 in one call, with a per-id result list.
ParameterTypeRequiredDescription
idintRequiredLine ID to disable
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=disable_line" \
  -d "id=4521"
POST enable_line Enable Line

Re-enable a previously disabled line.

New in this version
  • ids=1,2,3 — up to 100 in one call, with a per-id result list.
ParameterTypeRequiredDescription
idintRequiredLine ID to enable
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=enable_line" \
  -d "id=4521"

MAG Device Management

Create and manage MAG device subscriptions.

POST create_mag Create MAG Device

Register a new MAG device with its MAC address and assign a package.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
ParameterTypeRequiredDescription
macstringRequiredMAC address (format: 00:1A:79:XX:XX:XX)
packageintRequiredPackage ID
trialintRequired0 = regular, 1 = trial
is_isplockintOptional0 = disabled, 1 = enable ISP lock
reseller_notesstringOptionalInternal notes
bouquets_selected[]arrayOptionalArray of bouquet IDs
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=create_mag" \
  -d "mac=00:1A:79:AB:CD:EF" \
  -d "package=1" \
  -d "trial=0"
POST edit_mag Edit / Extend MAG

Edit a MAG device's properties or extend its subscription.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
ParameterTypeRequiredDescription
idintRequiredMAG device ID
macstringOptionalNew MAC address
packageintOptionalPackage ID for extension
is_isplockintOptional0 = disabled, 1 = enabled
reseller_notesstringOptionalInternal notes
bouquets_selected[]arrayOptionalArray of bouquet IDs
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=edit_mag" \
  -d "id=301" \
  -d "package=2"
GET get_mag Get Single MAG

Retrieve details of a single MAG device.

ParameterTypeRequiredDescription
idintRequiredMAG device ID
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_mag&id=301"
GET get_mags List MAG Devices

Retrieve a paginated list of all MAG devices. Supports search and status filtering.

New in this version
  • Works on POST as well as GET. It previously answered the literal null for any POSTed request.
  • Response gains total, start, limit, returned and has_more beside recordsTotal.
  • search= (no DataTables nesting), order_by=<column name>, order_dir=asc|desc.
  • Filters: enabled, admin_enabled, is_trial, package_id, member_id, exp_before, exp_after, since, until.
  • mac= filters by MAC address.
ParameterTypeRequiredDescription
startintOptionalPagination offset (default: 0)
limitintOptionalRecords per page (default: 50)
search[value]stringOptionalSearch term
filterintOptional1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_mags&start=0&limit=25"
POST delete_mag Delete MAG

Permanently delete a MAG device entry.

New in this version
  • ids=1,2,3 — delete up to 100 in one call; the reply lists each id with its own status plus succeeded / failed.
  • Honours the group's Delete Users permission (it was ignored before).
  • Honours the group's User Delete Refund permission: the pro-rata refund is credited and reported as refunded.
  • The line behind the device is refunded only when this was the LAST device on it.
ParameterTypeRequiredDescription
idintRequiredMAG device ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=delete_mag" \
  -d "id=301"
POST disable_mag Disable MAG

Temporarily disable a MAG device.

New in this version
  • ids=1,2,3 — up to 100 in one call, with a per-id result list.
ParameterTypeRequiredDescription
idintRequiredMAG device ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=disable_mag" \
  -d "id=301"
POST enable_mag Enable MAG

Re-enable a previously disabled MAG device.

New in this version
  • ids=1,2,3 — up to 100 in one call, with a per-id result list.
ParameterTypeRequiredDescription
idintRequiredMAG device ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=enable_mag" \
  -d "id=301"
POST convert_mag Convert MAG to M3U

Convert a MAG device subscription to an M3U line. Returns the newly created line with username and password.

ParameterTypeRequiredDescription
idintRequiredMAG device ID to convert
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=convert_mag" \
  -d "id=301"

Enigma2 Device Management

Create and manage Enigma2 device subscriptions.

POST create_enigma Create Enigma2 Device

Register a new Enigma2 device with its MAC address and assign a package.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
ParameterTypeRequiredDescription
macstringRequiredDevice MAC address
packageintRequiredPackage ID
trialintRequired0 = regular, 1 = trial
is_isplockintOptional0 = disabled, 1 = enable ISP lock
reseller_notesstringOptionalInternal notes
bouquets_selected[]arrayOptionalArray of bouquet IDs
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=create_enigma" \
  -d "mac=AA:BB:CC:DD:EE:FF" \
  -d "package=1" \
  -d "trial=0"
POST edit_enigma Edit / Extend Enigma2

Edit an Enigma2 device's properties or extend its subscription.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
ParameterTypeRequiredDescription
idintRequiredEnigma2 device ID
macstringOptionalNew MAC address
packageintOptionalPackage ID for extension
is_isplockintOptional0 = disabled, 1 = enabled
reseller_notesstringOptionalInternal notes
bouquets_selected[]arrayOptionalArray of bouquet IDs
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=edit_enigma" \
  -d "id=501" \
  -d "package=2"
GET get_enigma Get Single Enigma2

Retrieve details of a single Enigma2 device.

ParameterTypeRequiredDescription
idintRequiredEnigma2 device ID
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_enigma&id=501"
GET get_enigmas List Enigma2 Devices

Retrieve a paginated list of all Enigma2 devices.

New in this version
  • Works on POST as well as GET. It previously answered the literal null for any POSTed request.
  • Response gains total, start, limit, returned and has_more beside recordsTotal.
  • search= (no DataTables nesting), order_by=<column name>, order_dir=asc|desc.
  • Filters: enabled, admin_enabled, is_trial, package_id, member_id, exp_before, exp_after, since, until.
  • mac= filters by MAC address.
ParameterTypeRequiredDescription
startintOptionalPagination offset (default: 0)
limitintOptionalRecords per page (default: 50)
search[value]stringOptionalSearch term
filterintOptional1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_enigmas&start=0&limit=25"
POST delete_enigma Delete Enigma2

Permanently delete an Enigma2 device entry.

New in this version
  • ids=1,2,3 — delete up to 100 in one call; the reply lists each id with its own status plus succeeded / failed.
  • Honours the group's Delete Users permission (it was ignored before).
  • Honours the group's User Delete Refund permission: the pro-rata refund is credited and reported as refunded.
  • The line behind the device is refunded only when this was the LAST device on it.
ParameterTypeRequiredDescription
idintRequiredEnigma2 device ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=delete_enigma" \
  -d "id=501"
POST disable_enigma Disable Enigma2

Temporarily disable an Enigma2 device.

New in this version
  • ids=1,2,3 — up to 100 in one call, with a per-id result list.
ParameterTypeRequiredDescription
idintRequiredEnigma2 device ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=disable_enigma" \
  -d "id=501"
POST enable_enigma Enable Enigma2

Re-enable a previously disabled Enigma2 device.

New in this version
  • ids=1,2,3 — up to 100 in one call, with a per-id result list.
ParameterTypeRequiredDescription
idintRequiredEnigma2 device ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=enable_enigma" \
  -d "id=501"
POST convert_enigma Convert Enigma2 to M3U

Convert an Enigma2 device subscription to an M3U line. Returns the newly created line with username and password.

ParameterTypeRequiredDescription
idintRequiredEnigma2 device ID to convert
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=convert_enigma" \
  -d "id=501"

Activecode Management

Create and manage activation codes for device registration.

POST create_activecode Create Activecode

Create a new activation code. If no custom code is provided, one will be auto-generated based on group settings.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
  • count=N — generate a batch of codes in one call.
  • A code you choose that is already taken is now refused with STATUS_EXISTS_USERNAME. It used to succeed and hand back a different, randomly generated code.
ParameterTypeRequiredDescription
packageintRequiredPackage ID
trialintRequired0 = regular, 1 = trial
codestringOptionalCustom activation code (auto-generated if empty)
is_isplockintOptional0 = disabled, 1 = enable ISP lock
reseller_notesstringOptionalInternal notes
bouquets_selected[]arrayOptionalArray of bouquet IDs
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=create_activecode" \
  -d "package=1" \
  -d "trial=0" \
  -d "code=PROMO-2025-ABCD"
Response
{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 892,
    "code": "PROMO-2025-ABCD",
    "package_id": 1,
    "is_trial": 0,
    "status": 1,
    "reseller_notes": "",
    "created_at": "2025-01-15 14:30:00"
  }
}
POST edit_activecode Edit / Extend Activecode

Edit an activecode's properties or extend it with a new package.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
ParameterTypeRequiredDescription
idintRequiredActivecode ID
packageintOptionalPackage ID for extension
codestringOptionalChange the activation code
reseller_notesstringOptionalInternal notes
bouquets_selected[]arrayOptionalArray of bouquet IDs
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=edit_activecode" \
  -d "id=892" \
  -d "package=2"
GET get_activecode Get Single Activecode

Retrieve details of a single activation code.

ParameterTypeRequiredDescription
idintRequiredActivecode ID
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_activecode&id=892"
GET get_activecodes List All Activecodes

Retrieve a paginated list of all activation codes.

New in this version
  • Works on POST as well as GET. It previously answered the literal null for any POSTed request.
  • Response gains total, start, limit, returned and has_more beside recordsTotal.
  • search= (no DataTables nesting), order_by=<column name>, order_dir=asc|desc.
  • Filters: enabled, admin_enabled, is_trial, package_id, member_id, exp_before, exp_after, since, until.
  • code= filters by exact code.
ParameterTypeRequiredDescription
startintOptionalPagination offset (default: 0)
limitintOptionalRecords per page (default: 50)
search[value]stringOptionalSearch term
filterintOptional1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_activecodes&start=0&limit=25"
POST delete_activecode Delete Activecode

Permanently delete an activation code.

New in this version
  • ids=1,2,3 — delete up to 100 in one call; the reply lists each id with its own status plus succeeded / failed.
  • Honours the group's Delete Users permission (it was ignored before).
  • Honours the group's User Delete Refund permission: the pro-rata refund is credited and reported as refunded.
ParameterTypeRequiredDescription
idintRequiredActivecode ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=delete_activecode" \
  -d "id=892"
POST disable_activecode Disable Activecode

Temporarily disable an activation code.

New in this version
  • ids=1,2,3 — up to 100 in one call, with a per-id result list.
ParameterTypeRequiredDescription
idintRequiredActivecode ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=disable_activecode" \
  -d "id=892"
POST enable_activecode Enable Activecode

Re-enable a previously disabled activation code.

New in this version
  • ids=1,2,3 — up to 100 in one call, with a per-id result list.
ParameterTypeRequiredDescription
idintRequiredActivecode ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=enable_activecode" \
  -d "id=892"
POST reset_activecode Reset Activecode

Generate a new random activation code, replacing the existing one. Returns both the old and new codes.

New in this version
  • The replacement code is checked for collisions properly. It used to be able to write a code another record already held.
ParameterTypeRequiredDescription
idintRequiredActivecode ID to reset
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=reset_activecode" \
  -d "id=892"
Response
{
  "status": "STATUS_SUCCESS",
  "data": {
    "old_code": "PROMO-2025-ABCD",
    "new_code": "XM-8F3K-Q9W2-LP7N"
  }
}

Sub-Reseller Management

Create and manage sub-reseller accounts and their credits.

POST create_user Create Sub-Reseller

Create a new sub-reseller account under your reseller.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
  • A duplicate sub-reseller username is now refused. It used to succeed, leaving two accounts with the same login.
  • The min_credits_create configured in Sub-Reseller Setup is now honoured: the new account opens with that many credits and the creator is charged the fee plus the transfer.
  • The password hash is no longer returned in data.
ParameterTypeRequiredDescription
usernamestringOptionalUsername (auto-generated if empty)
passwordstringOptionalPassword (auto-generated if empty)
emailstringOptionalEmail address
member_group_idintOptionalMember group ID to assign
reseller_dnsstringOptionalCustom DNS for the sub-reseller
notesstringOptionalInternal notes
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=create_user" \
  -d "username=subreseller1" \
  -d "password=strongpass123" \
  -d "email=sub@example.com" \
  -d "member_group_id=3"
POST edit_user Edit Sub-Reseller

Edit a sub-reseller account's properties.

New in this version
  • dry_run=1 — validate and price the request, write nothing. The reply is the record that WOULD be written, plus cost, credits_before and credits_after.
  • request_id=<uuid> — a retry within 24 h returns the record the first call created, with "replayed": true, and charges nothing.
  • An edit that changes only notes, email or reseller_dns now works. It used to return STATUS_FAILURE and change nothing unless username was re-sent.
  • The password hash is no longer returned in data.
ParameterTypeRequiredDescription
idintRequiredSub-reseller ID
usernamestringOptionalNew username
passwordstringOptionalNew password
emailstringOptionalNew email
member_group_idintOptionalNew member group ID
reseller_dnsstringOptionalCustom DNS
notesstringOptionalInternal notes
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=edit_user" \
  -d "id=88" \
  -d "email=newemail@example.com"
GET get_user Get Single Sub-Reseller

Retrieve details of a single sub-reseller account.

ParameterTypeRequiredDescription
idintRequiredSub-reseller ID
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_user&id=88"
GET get_users List Sub-Resellers

Retrieve a paginated list of all sub-reseller accounts.

New in this version
  • Works on POST as well as GET. It previously answered the literal null for any POSTed request.
  • Response gains total, start, limit, returned and has_more beside recordsTotal.
  • search= (no DataTables nesting), order_by=<column name>, order_dir=asc|desc.
  • Filters: status, owner_id, member_group_id, username, since, until.
ParameterTypeRequiredDescription
startintOptionalPagination offset (default: 0)
limitintOptionalRecords per page (default: 50)
search[value]stringOptionalSearch term
filterintOptional1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_users&start=0&limit=25"
POST delete_user Delete Sub-Reseller

Permanently delete a sub-reseller account.

New in this version
  • What the deleted account owned is re-homed to you instead of being orphaned. Its lines used to get member_id = NULL — still streaming, owned by nobody, invisible to every listing and unreachable through every endpoint.
  • reassign_to=<reg_user id> — hand them to another account inside your own sub-tree instead.
  • delete_lines=1 — delete the lines rather than re-homing them.
  • delete_subs=1 — delete the sub-resellers below it too.
  • Honours the group's Delete Users permission (it was ignored before).
ParameterTypeRequiredDescription
idintRequiredSub-reseller ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=delete_user" \
  -d "id=88"
POST disable_user Disable Sub-Reseller

Temporarily disable a sub-reseller account.

ParameterTypeRequiredDescription
idintRequiredSub-reseller ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=disable_user" \
  -d "id=88"
POST enable_user Enable Sub-Reseller

Re-enable a previously disabled sub-reseller account.

ParameterTypeRequiredDescription
idintRequiredSub-reseller ID
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=enable_user" \
  -d "id=88"
POST adjust_credits Transfer Credits

Add or subtract credits from a sub-reseller account. Use positive values to add credits and negative values to subtract.

New in this version
  • Both balances are written additively, so two adjustments in flight against the same account cannot lose one.
  • Two xm_credits_logs rows are written, one per side — the transfer used to leave no trace at all.
  • Reports credits_moved, credits_after and target_credits_after.
ParameterTypeRequiredDescription
idintRequiredSub-reseller ID
creditsintRequiredCredits to transfer (positive = add, negative = subtract)
notestringOptionalNote for the transaction log
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=adjust_credits" \
  -d "id=88" \
  -d "credits=50" \
  -d "note=Monthly top-up"
Response
{
  "status": "STATUS_SUCCESS",
  "data": {
    "id": 88,
    "credits_before": 100.00,
    "credits_after": 150.00,
    "adjusted": 50,
    "note": "Monthly top-up"
  }
}

Logs & Monitoring

Monitor activity, connections, and credit transactions.

GET activity_logs Activity Logs

Retrieve user activity logs with pagination support.

New in this version
  • This endpoint returned the literal null on every call. The query behind it referenced a column the database does not have, so it failed and answered with an empty body. It returns rows now.
  • Works on POST as well as GET.
  • Response gains total, start, limit, returned and has_more beside recordsTotal.
  • search=, order_by=<column name>, order_dir=asc|desc.
  • Filters: line (one line id), user (owning reseller), stream, server_id, country, container, since, until, and the range=YYYY-MM-DD - YYYY-MM-DD form the panel uses.
  • Newest first by default, rather than alphabetically by username.
  • Requires the group’s Client Connection Logs permission; without it you now get a named refusal instead of an empty list that reads as “no activity”.
ParameterTypeRequiredDescription
startintOptionalPagination offset (default: 0)
limitintOptionalRecords per page (default: 50)
search[value]stringOptionalSearch term
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=activity_logs&start=0&limit=50"
GET live_connections Live Connections

View currently active/live connections across your lines and devices.

New in this version
  • This endpoint returned the literal null on every call. The query behind it referenced a column the database does not have, so it failed and answered with an empty body. It returns rows now.
  • Works on POST as well as GET.
  • Response gains total, start, limit, returned and has_more beside recordsTotal.
  • search=, order_by=<column name>, order_dir=asc|desc.
  • Each row also carries active_time (seconds since the session started), identifier and server_name.
  • Same filters as activity_logs.
  • Requires the group’s Client Connection Logs permission.
ParameterTypeRequiredDescription
startintOptionalPagination offset (default: 0)
limitintOptionalRecords per page (default: 50)
search[value]stringOptionalSearch term
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=live_connections&start=0&limit=50"
GET user_logs Credit / Action Logs

Retrieve credit transactions and action logs.

New in this version
  • Works on POST as well as GET.
  • Response gains total, start, limit, returned and has_more beside recordsTotal.
  • search=, order_by=<column name>, order_dir=asc|desc.
  • Filters: since, until, range, reseller.
  • The username scope is bound rather than pasted into the SQL, so an account whose name contains a quote no longer breaks its own log.
ParameterTypeRequiredDescription
startintOptionalPagination offset (default: 0)
limitintOptionalRecords per page (default: 50)
search[value]stringOptionalSearch term
Request
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=user_logs&start=0&limit=50"
GET credits_log Credit Ledger NEW

Every charge and refund on your account, newest first. The ledger has always been written; nothing could read it back until now.

ParameterTypeRequiredDescription
startintOptionalPagination offset (default: 0)
limitintOptionalRows to return (default: 50)
sinceintOptionalUnix time — only entries at or after it
untilintOptionalUnix time — only entries at or before it
include_reportsintOptional1 = include the ledgers of the accounts below you as well
Request
curl -X POST "http://dns:port/access_code/reseller/index.php" \
  -d "api_key=YOUR_KEY" \
  -d "action=credits_log" \
  -d "limit=2"
Response
{
  "status": "STATUS_SUCCESS",
  "data": [
    { "id": "405", "user": "reseller", "charge": "+3.90", "left": "12726.9",
      "details": "Delete M3U User: rf1 - LineID: 41402", "time": "1786443217" },
    { "id": "404", "user": "reseller", "charge": "-3", "left": "12723",
      "details": "extend ID: 41402 Pack: 3", "time": "1786443217" }
  ],
  "total": 346,
  "start": 0,
  "limit": 2,
  "has_more": true
}

Response Format

All API responses follow a consistent JSON structure.

Standard Response
{
  "status": "STATUS_SUCCESS" | "STATUS_FAILURE",
  "data": { // Object or Array depending on the endpoint }
}
Important Always check the status field before processing the data field. On failure, the data field may contain an error message or be empty.

Status Codes

All possible status values returned by the API.

STATUS_SUCCESS
STATUS_FAILURE
STATUS_INSUFFICIENT_CREDITS
STATUS_INVALID_PACKAGE
STATUS_INVALID_MAC
STATUS_EXISTS_MAC
STATUS_EXISTS_USERNAME
STATUS_INVALID_USERNAME
STATUS_INVALID_PASSWORD
STATUS_NO_TRIALS
STATUS_INVALID_TYPE
STATUS_NO_PERMISSIONS
STATUS_INVALID_DATA