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.
api_access_code.
Base URL
All requests are made to the following base URL format:
Replace {server-dns}, {stream-port}, and {api-access-code} with your server details.
Quick Start
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.
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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| api_key | string | Required | Your unique API key for authentication |
| action | string | Required | The API action to perform |
| start | int | Optional | Pagination offset for list endpoints (default: 0) |
| limit | int | Optional | Number of records to return (default: 50) |
| show_columns | string | Optional | Comma-separated list of column names to include in the response |
| dry_run | int | Optional | New. 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_id | string | Optional | New. 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. |
| count | int | Optional | New. 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. |
| ids | string | Optional | New. 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. |
| search | string | Optional | New. The search term, without the DataTables search[value] nesting. Both forms work. |
| order_by | string | Optional | New. 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_dir | string | Optional | New. asc or desc. |
| since | int | Optional | New. 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. |
| until | int | Optional | New. Unix time — only records created at or before it. |
| add_special_bouquets | int | Optional | New. See create_line. |
| hide_columns | string | Optional | Comma-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.
{
"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"
}
]
}
| code | What it means |
|---|---|
| connections_not_allowed | The package sells one screen; max_connections was ignored. |
| connections_capped | You asked for more screens than the package allows. |
| connection_pricing_missing | The package has no multi-connection price list, so the charge was the base price × screens. |
| adult_not_enabled | Your group may not sell adult content. |
| adult_not_configured | The server’s adult bouquet set is empty or switched off. |
| forced_country_not_allowed | Your group may not force a country. |
| forced_country_from_package | The package forces its own country, which wins. |
| date_on_usage_unavailable | Start-on-first-use is not available on this server. |
| custom_pack_not_found | No saved arrangement with that id belongs to you. |
| custom_pack_needs_upgrade | That arrangement is in the older format; open it once in the panel to convert it. |
| reset_password_unnecessary | Your group may set passwords directly — send password. |
| mass_generation_disabled | Your 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.
- Dry runs.
dry_run=1on 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=Non creates,ids=1,2,3on 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, plussearch,order_by,order_dirand column filters. - Delta sync.
since/untilon 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,fieldand a one-sentencehint. Thestatusstring is unchanged. - Permission refusals name the setting. A refusal caused by a group setting carries
settingwith 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
warningsarray 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_idand the adult switch were all panel-only;created_byis now recorded, where every line the API created used to be attributed to nobody. - Credit ledger. The new
credits_logaction reads back the charges and refunds on your account. - Adult bouquets.
add_special_bouquets=1on a line adds them and setsis_adult,0removes them. There was no way to do either over the API before. - Expiry deltas. An extend reports
exp_date_before,exp_date_afterandexp_date_added.
activity_logsandlive_connectionsnever worked at all. Both queried a column the database does not have, so both returned the literalnullon GET and on POST alike. They return rows now.- List endpoints answered
nullon POST.get_lines,get_mags,get_enigmas,get_users,get_activecodes,activity_logs,live_connectionsanduser_logsreturned the literal four bytesnullfor a POSTed request — which is what every example in this document showed. They now work on POST and on GET. show_columns/hide_columnsdeleted rows frompackages.show_columns=id,package_namereturned one package with every column;hide_columns=groupsdropped 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 UsersandUser Delete Refundgroup 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_userfailed unlessusernamewas re-sent, so changing only a note or an email did nothing and returnedSTATUS_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_userandedit_userreturned 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=5granted five screens and charged the one-screen price; the package’s ownmulticonx_pricingladder 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.
Returns your reseller account details including credits, status, and account settings.
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=user_info"
{
"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"]
}
}
Returns the list of packages available to your reseller account with pricing and duration details.
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=packages"
{
"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.
Creates a new M3U line. Username and password are auto-generated if not provided.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_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_connectionsis honoured up to the package'smultiple_connectionscap.
| Parameter | Type | Required | Description |
|---|---|---|---|
| package | int | Required | Package ID from the packages endpoint |
| trial | int | Required | 0 = regular, 1 = trial |
| username | string | Optional | Custom username (auto-generated if empty) |
| password | string | Optional | Custom password (auto-generated if empty) |
| is_isplock | int | Optional | 0 = disabled, 1 = enable ISP lock |
| allowed_ips[] | array | Optional | Array of allowed IP addresses |
| reseller_notes | string | Optional | Internal notes for the reseller |
| bouquets_selected[] | array | Optional | Array of bouquet IDs to assign |
| max_connections | int | Optional | Priced. 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_bouquets | int | Optional | New. 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_cdn | int | Optional | New. 1 routes the line through the private CDN, 0 off. Omit to leave it as it is. |
| allow_epg | int | Optional | New. 1 includes the line in XMLTV EPG output, 0 excludes it (lighter on low-power devices). Omit to leave it as it is. |
| forced_country | string | Optional | New. 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_password | int | Optional | New. 1 issues a fresh password. For groups that may not choose passwords — a group that may should send password. |
| custom_playlist_id | int | Optional | New. 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. |
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"
{
"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"
}
}
Edit an existing line's properties or extend it with a new package. Provide the package parameter to extend the line's expiry.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_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_afterandexp_date_addedare reported when the expiry moves.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Line ID to edit |
| package | int | Optional | Package ID to extend with |
| username | string | Optional | New username |
| password | string | Optional | New password |
| is_isplock | int | Optional | 0 = disabled, 1 = enable ISP lock |
| allowed_ips[] | array | Optional | Array of allowed IP addresses |
| reseller_notes | string | Optional | Internal notes |
| bouquets_selected[] | array | Optional | Array of bouquet IDs |
| max_connections | int | Optional | Priced. 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_bouquets | int | Optional | New. 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_cdn | int | Optional | New. 1 routes the line through the private CDN, 0 off. Omit to leave it as it is. |
| allow_epg | int | Optional | New. 1 includes the line in XMLTV EPG output, 0 excludes it (lighter on low-power devices). Omit to leave it as it is. |
| forced_country | string | Optional | New. 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_password | int | Optional | New. 1 issues a fresh password. For groups that may not choose passwords — a group that may should send password. |
| custom_playlist_id | int | Optional | New. 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. |
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"
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.)
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_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_afterandexp_date_addedreport exactly what the extend did.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Line ID to extend |
| package | int | Required | Package ID whose official duration is added to the line |
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"
{
"status": "STATUS_SUCCESS",
"data": {
"id": 4521,
"exp_date": 1786708220
}
}
Retrieve details of a single M3U line by its ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Line ID |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_line&id=4521"
{
"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"
}
}
Retrieve a paginated list of all M3U lines. Supports search and status filtering.
- Works on POST as well as GET. It previously answered the literal
nullfor any POSTed request. - Response gains
total,start,limit,returnedandhas_morebesiderecordsTotal. 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| start | int | Optional | Pagination offset (default: 0) |
| limit | int | Optional | Records per page (default: 50) |
| search[value] | string | Optional | Search by username or other fields |
| filter | int | Optional | 1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_lines&start=0&limit=25&filter=1"
Permanently delete a line. This action cannot be undone.
ids=1,2,3— delete up to 100 in one call; the reply lists each id with its own status plussucceeded/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Line ID to delete |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=delete_line" \ -d "id=4521"
Temporarily disable a line. The line can be re-enabled later.
ids=1,2,3— up to 100 in one call, with a per-id result list.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Line ID to disable |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=disable_line" \ -d "id=4521"
Re-enable a previously disabled line.
ids=1,2,3— up to 100 in one call, with a per-id result list.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Line ID to enable |
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.
Register a new MAG device with its MAC address and assign a package.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_after.request_id=<uuid>— a retry within 24 h returns the record the first call created, with"replayed": true, and charges nothing.
| Parameter | Type | Required | Description |
|---|---|---|---|
| mac | string | Required | MAC address (format: 00:1A:79:XX:XX:XX) |
| package | int | Required | Package ID |
| trial | int | Required | 0 = regular, 1 = trial |
| is_isplock | int | Optional | 0 = disabled, 1 = enable ISP lock |
| reseller_notes | string | Optional | Internal notes |
| bouquets_selected[] | array | Optional | Array of bouquet IDs |
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"
Edit a MAG device's properties or extend its subscription.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_after.request_id=<uuid>— a retry within 24 h returns the record the first call created, with"replayed": true, and charges nothing.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | MAG device ID |
| mac | string | Optional | New MAC address |
| package | int | Optional | Package ID for extension |
| is_isplock | int | Optional | 0 = disabled, 1 = enabled |
| reseller_notes | string | Optional | Internal notes |
| bouquets_selected[] | array | Optional | Array of bouquet IDs |
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"
Retrieve details of a single MAG device.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | MAG device ID |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_mag&id=301"
Retrieve a paginated list of all MAG devices. Supports search and status filtering.
- Works on POST as well as GET. It previously answered the literal
nullfor any POSTed request. - Response gains
total,start,limit,returnedandhas_morebesiderecordsTotal. 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| start | int | Optional | Pagination offset (default: 0) |
| limit | int | Optional | Records per page (default: 50) |
| search[value] | string | Optional | Search term |
| filter | int | Optional | 1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_mags&start=0&limit=25"
Permanently delete a MAG device entry.
ids=1,2,3— delete up to 100 in one call; the reply lists each id with its own status plussucceeded/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | MAG device ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=delete_mag" \ -d "id=301"
Temporarily disable a MAG device.
ids=1,2,3— up to 100 in one call, with a per-id result list.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | MAG device ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=disable_mag" \ -d "id=301"
Re-enable a previously disabled MAG device.
ids=1,2,3— up to 100 in one call, with a per-id result list.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | MAG device ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=enable_mag" \ -d "id=301"
Convert a MAG device subscription to an M3U line. Returns the newly created line with username and password.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | MAG device ID to convert |
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.
Register a new Enigma2 device with its MAC address and assign a package.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_after.request_id=<uuid>— a retry within 24 h returns the record the first call created, with"replayed": true, and charges nothing.
| Parameter | Type | Required | Description |
|---|---|---|---|
| mac | string | Required | Device MAC address |
| package | int | Required | Package ID |
| trial | int | Required | 0 = regular, 1 = trial |
| is_isplock | int | Optional | 0 = disabled, 1 = enable ISP lock |
| reseller_notes | string | Optional | Internal notes |
| bouquets_selected[] | array | Optional | Array of bouquet IDs |
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"
Edit an Enigma2 device's properties or extend its subscription.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_after.request_id=<uuid>— a retry within 24 h returns the record the first call created, with"replayed": true, and charges nothing.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Enigma2 device ID |
| mac | string | Optional | New MAC address |
| package | int | Optional | Package ID for extension |
| is_isplock | int | Optional | 0 = disabled, 1 = enabled |
| reseller_notes | string | Optional | Internal notes |
| bouquets_selected[] | array | Optional | Array of bouquet IDs |
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"
Retrieve details of a single Enigma2 device.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Enigma2 device ID |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_enigma&id=501"
Retrieve a paginated list of all Enigma2 devices.
- Works on POST as well as GET. It previously answered the literal
nullfor any POSTed request. - Response gains
total,start,limit,returnedandhas_morebesiderecordsTotal. 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| start | int | Optional | Pagination offset (default: 0) |
| limit | int | Optional | Records per page (default: 50) |
| search[value] | string | Optional | Search term |
| filter | int | Optional | 1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_enigmas&start=0&limit=25"
Permanently delete an Enigma2 device entry.
ids=1,2,3— delete up to 100 in one call; the reply lists each id with its own status plussucceeded/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Enigma2 device ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=delete_enigma" \ -d "id=501"
Temporarily disable an Enigma2 device.
ids=1,2,3— up to 100 in one call, with a per-id result list.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Enigma2 device ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=disable_enigma" \ -d "id=501"
Re-enable a previously disabled Enigma2 device.
ids=1,2,3— up to 100 in one call, with a per-id result list.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Enigma2 device ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=enable_enigma" \ -d "id=501"
Convert an Enigma2 device subscription to an M3U line. Returns the newly created line with username and password.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Enigma2 device ID to convert |
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.
Create a new activation code. If no custom code is provided, one will be auto-generated based on group settings.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_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
codeyou choose that is already taken is now refused withSTATUS_EXISTS_USERNAME. It used to succeed and hand back a different, randomly generated code.
| Parameter | Type | Required | Description |
|---|---|---|---|
| package | int | Required | Package ID |
| trial | int | Required | 0 = regular, 1 = trial |
| code | string | Optional | Custom activation code (auto-generated if empty) |
| is_isplock | int | Optional | 0 = disabled, 1 = enable ISP lock |
| reseller_notes | string | Optional | Internal notes |
| bouquets_selected[] | array | Optional | Array of bouquet IDs |
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"
{
"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"
}
}
Edit an activecode's properties or extend it with a new package.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_after.request_id=<uuid>— a retry within 24 h returns the record the first call created, with"replayed": true, and charges nothing.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Activecode ID |
| package | int | Optional | Package ID for extension |
| code | string | Optional | Change the activation code |
| reseller_notes | string | Optional | Internal notes |
| bouquets_selected[] | array | Optional | Array of bouquet IDs |
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"
Retrieve details of a single activation code.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Activecode ID |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_activecode&id=892"
Retrieve a paginated list of all activation codes.
- Works on POST as well as GET. It previously answered the literal
nullfor any POSTed request. - Response gains
total,start,limit,returnedandhas_morebesiderecordsTotal. 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| start | int | Optional | Pagination offset (default: 0) |
| limit | int | Optional | Records per page (default: 50) |
| search[value] | string | Optional | Search term |
| filter | int | Optional | 1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_activecodes&start=0&limit=25"
Permanently delete an activation code.
ids=1,2,3— delete up to 100 in one call; the reply lists each id with its own status plussucceeded/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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Activecode ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=delete_activecode" \ -d "id=892"
Temporarily disable an activation code.
ids=1,2,3— up to 100 in one call, with a per-id result list.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Activecode ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=disable_activecode" \ -d "id=892"
Re-enable a previously disabled activation code.
ids=1,2,3— up to 100 in one call, with a per-id result list.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Activecode ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=enable_activecode" \ -d "id=892"
Generate a new random activation code, replacing the existing one. Returns both the old and new codes.
- The replacement code is checked for collisions properly. It used to be able to write a code another record already held.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Activecode ID to reset |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=reset_activecode" \ -d "id=892"
{
"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.
Create a new sub-reseller account under your reseller.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_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_createconfigured 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| username | string | Optional | Username (auto-generated if empty) |
| password | string | Optional | Password (auto-generated if empty) |
| string | Optional | Email address | |
| member_group_id | int | Optional | Member group ID to assign |
| reseller_dns | string | Optional | Custom DNS for the sub-reseller |
| notes | string | Optional | Internal notes |
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"
Edit a sub-reseller account's properties.
dry_run=1— validate and price the request, write nothing. The reply is the record that WOULD be written, pluscost,credits_beforeandcredits_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,emailorreseller_dnsnow works. It used to returnSTATUS_FAILUREand change nothing unlessusernamewas re-sent. - The password hash is no longer returned in
data.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Sub-reseller ID |
| username | string | Optional | New username |
| password | string | Optional | New password |
| string | Optional | New email | |
| member_group_id | int | Optional | New member group ID |
| reseller_dns | string | Optional | Custom DNS |
| notes | string | Optional | Internal notes |
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"
Retrieve details of a single sub-reseller account.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Sub-reseller ID |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_user&id=88"
Retrieve a paginated list of all sub-reseller accounts.
- Works on POST as well as GET. It previously answered the literal
nullfor any POSTed request. - Response gains
total,start,limit,returnedandhas_morebesiderecordsTotal. search=(no DataTables nesting),order_by=<column name>,order_dir=asc|desc.- Filters:
status,owner_id,member_group_id,username,since,until.
| Parameter | Type | Required | Description |
|---|---|---|---|
| start | int | Optional | Pagination offset (default: 0) |
| limit | int | Optional | Records per page (default: 50) |
| search[value] | string | Optional | Search term |
| filter | int | Optional | 1=Active, 2=Disabled, 3=Banned, 4=Expired, 5=Trial |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=get_users&start=0&limit=25"
Permanently delete a sub-reseller account.
- 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).
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Sub-reseller ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=delete_user" \ -d "id=88"
Temporarily disable a sub-reseller account.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Sub-reseller ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=disable_user" \ -d "id=88"
Re-enable a previously disabled sub-reseller account.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Sub-reseller ID |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=enable_user" \ -d "id=88"
Add or subtract credits from a sub-reseller account. Use positive values to add credits and negative values to subtract.
- Both balances are written additively, so two adjustments in flight against the same account cannot lose one.
- Two
xm_credits_logsrows are written, one per side — the transfer used to leave no trace at all. - Reports
credits_moved,credits_afterandtarget_credits_after.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | int | Required | Sub-reseller ID |
| credits | int | Required | Credits to transfer (positive = add, negative = subtract) |
| note | string | Optional | Note for the transaction log |
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"
{
"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.
Retrieve user activity logs with pagination support.
- This endpoint returned the literal
nullon 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,returnedandhas_morebesiderecordsTotal. 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 therange=YYYY-MM-DD - YYYY-MM-DDform 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”.
| Parameter | Type | Required | Description |
|---|---|---|---|
| start | int | Optional | Pagination offset (default: 0) |
| limit | int | Optional | Records per page (default: 50) |
| search[value] | string | Optional | Search term |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=activity_logs&start=0&limit=50"
View currently active/live connections across your lines and devices.
- This endpoint returned the literal
nullon 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,returnedandhas_morebesiderecordsTotal. search=,order_by=<column name>,order_dir=asc|desc.- Each row also carries
active_time(seconds since the session started),identifierandserver_name. - Same filters as
activity_logs. - Requires the group’s Client Connection Logs permission.
| Parameter | Type | Required | Description |
|---|---|---|---|
| start | int | Optional | Pagination offset (default: 0) |
| limit | int | Optional | Records per page (default: 50) |
| search[value] | string | Optional | Search term |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=live_connections&start=0&limit=50"
Retrieve credit transactions and action logs.
- Works on POST as well as GET.
- Response gains
total,start,limit,returnedandhas_morebesiderecordsTotal. 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| start | int | Optional | Pagination offset (default: 0) |
| limit | int | Optional | Records per page (default: 50) |
| search[value] | string | Optional | Search term |
curl -X GET "http://dns:port/access_code/reseller/index.php?api_key=YOUR_KEY&action=user_logs&start=0&limit=50"
Every charge and refund on your account, newest first. The ledger has always been written; nothing could read it back until now.
| Parameter | Type | Required | Description |
|---|---|---|---|
| start | int | Optional | Pagination offset (default: 0) |
| limit | int | Optional | Rows to return (default: 50) |
| since | int | Optional | Unix time — only entries at or after it |
| until | int | Optional | Unix time — only entries at or before it |
| include_reports | int | Optional | 1 = include the ledgers of the accounts below you as well |
curl -X POST "http://dns:port/access_code/reseller/index.php" \ -d "api_key=YOUR_KEY" \ -d "action=credits_log" \ -d "limit=2"
{
"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.
{
"status": "STATUS_SUCCESS" | "STATUS_FAILURE",
"data": { // Object or Array depending on the endpoint }
}
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_SUCCESSSTATUS_FAILURESTATUS_INSUFFICIENT_CREDITSSTATUS_INVALID_PACKAGESTATUS_INVALID_MACSTATUS_EXISTS_MACSTATUS_EXISTS_USERNAMESTATUS_INVALID_USERNAMESTATUS_INVALID_PASSWORDSTATUS_NO_TRIALSSTATUS_INVALID_TYPESTATUS_NO_PERMISSIONSSTATUS_INVALID_DATA