freeQoS API guide
Everything an integrator needs to drive freeQoS from a billing system, a CRM or a script: add routers, push client plans, force limits, read consumption and quality measurements.
In the examples, replace <your-freeqos-url> with the
address of your freeQoS server, and <key> with an API key.
Overview
freeQoS exposes three APIs. They share the same keys and the same server.
| API | Path | Use it to |
|---|---|---|
| Operating API | /api/v1 |
Everything the interface does: routers, plans, limits, measurements, traffic, diagnostics. Rates in Mbit/s. |
| Model API | /model/v1 |
Push your commercial inventory (customers, packages, sites, sectors, sold lines). Same contract as the Preseem model API: an existing Preseem integration works by changing only the URL and the key. Rates in kbit/s. |
| Usage API | /usage/v1 |
Read the bytes consumed by each sold line, per hour, day or month (for billing or a customer portal). |
Authentication
Create a key
In the interface, open the API tab, give the key a name (for example Billing) and a scope, then Create. The secret is shown once: copy it right away. A key can be disabled or revoked from the same tab.
| Scope | Allows |
|---|---|
read | Every GET: inventory, plans,
measurements, traffic, consumption. |
read + write | Also POST,
PUT, PATCH, DELETE: add routers, push
plans, force limits, push the model inventory. |
For safety, a key can never manage keys, user accounts or passwords: those stay in the interface, behind a person's login.
Send the key
Three equivalent forms are accepted on every API. Use the one your tool supports.
# 1. Bearer token (recommended)
curl -H 'Authorization: Bearer <key>' <your-freeqos-url>/api/v1/plans
# 2. X-API-Key header
curl -H 'X-API-Key: <key>' <your-freeqos-url>/api/v1/plans
# 3. HTTP Basic, the key as user name and an empty password (Preseem form)
curl -u '<key>:' <your-freeqos-url>/model/v1
Check a key in one call: GET /model/v1 returns the collections, the key
prefix and its scopes.
Conventions
- JSON in and out. Send
Content-Type: application/jsonwith a body. - Units.
/api/v1takes rates in Mbit/s (down_mbps,up_mbps); some endpoints also accept_kbpsand_gbpsvariants./model/v1takes kbit/s (down_speed,up_speed), like Preseem./usage/v1returns bytes. Down is toward the client, up from the client. - Times are ISO 8601 in UTC (
2026-10-07T08:00:00Z). - Identifiers. A PPPoE client is identified by its login; a static-IP client by the reference you gave it; a model record by the id you choose in the URL.
- Idempotence.
PUTcreates or replaces: replaying your whole inventory is harmless. The id in the URL is authoritative. - Immediate effect. Plan and limit changes are written to the router in the same call; the response says what was actually written (see enforcement report).
Quick start
From nothing to a throttled client in five calls. Replace <key>
with a read + write key.
- Check the key.
curl -H 'Authorization: Bearer <key>' <your-freeqos-url>/model/v1 - Add a router. freeQoS connects to it and sets it up by itself.
curl -X POST <your-freeqos-url>/api/v1/pops/routers \ -H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' \ -d '{"name":"nas-north","host":"10.0.0.2","username":"freeqos", "password":"<router password>","role":"pop","pop_name":"North"}' - Wait for the setup (usually under a minute):
finished_atis set when done.curl -H 'Authorization: Bearer <key>' <your-freeqos-url>/api/v1/pops/provisioning/nas-north - Find the clients freeQoS discovered on it (PPPoE sessions, VLAN clients):
curl -H 'Authorization: Bearer <key>' <your-freeqos-url>/api/v1/plans - Push a plan. The queue is written to the router at once.
curl -X PUT <your-freeqos-url>/api/v1/plans/john.doe \ -H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' \ -d '{"down_mbps":100,"up_mbps":20}'
Add a router
POST/api/v1/pops/routers
Saves a MikroTik router (RouterOS API, port 8728, or 8729 with TLS). Right after, freeQoS runs its automatic setup: CAKE queue types, NetFlow export toward freeQoS, discovery of its clients and links. Nothing else has to be configured by hand.
| Field | Required | Description |
|---|---|---|
name | yes | Unique name of the router
(nas-north). Used in every other call. |
host | yes | Management IP or host name. |
password | yes | Password of the RouterOS account. Stored encrypted, never returned. |
username | no | RouterOS account
(default qos-ro). To write queues, its group needs the policies
read,write,api,test. |
role | no | pop (default, a router
serving clients), core or gateway (internet exit:
traffic is measured there). |
pop_name | no | Site name shown in the interface (default: the router name). |
port, use_ssl | no | 8728 by default;
use_ssl: true with port 8729 for API-SSL. |
loopback | no | Loopback address, detected automatically when omitted. |
enabled | no | false keeps the router
in the inventory without polling it. |
write_enabled | no | Allow freeQoS to write on this
router: queues, CAKE types, NetFlow export, restrictions (default true,
as before). false keeps it read-only: it is read and measured,
its plan is computed and shown, nothing is sent to it. The interface sends
false unless the operator ticks the box. |
reason | no | Why writing is allowed or not, recorded with its author in the command log. |
Writing reaches a router only when the global switch is on
(GET /api/v1/shaping/enforcement), its write_enabled is
true, and its RouterOS account has the write policy.
Related calls
| POST | /api/v1/pops/routers/test |
Test the connection and the account rights without saving (same body). |
| GET | /api/v1/pops/provisioning/{router_name} |
Progress of the automatic setup: state, steps,
finished_at. |
| POST | /api/v1/pops/provisioning/{router_name} |
Run the setup again (after fixing a password, for example). |
| GET | /api/v1/pops/routers |
The router inventory, with each router's id. |
| PATCH | /api/v1/pops/routers/{router_id} |
Edit a router: send only the fields to change. To allow writing on a read-only
router: {"write_enabled": true, "confirm": true, "reason": "pilot"};
{"write_enabled": false} makes it read-only at once. |
| PUT | /api/v1/pops/routers/file/{name}/write |
The same switch for a router of the routers file:
{"enabled": false}, or {"enabled": true, "confirm": true}
when the file allows it. |
| DELETE | /api/v1/pops/routers/{router_id} |
Remove a router from the inventory. |
| GET | /api/v1/pops/health |
CPU, memory, uptime and version of every router. |
Client plans
The plan is the rate sold to a client. It is the normal way to throttle: freeQoS writes a CAKE queue for the client on the router that carries it.
GET/api/v1/plans
Every known client (detected on a router or declared), its plan and where the plan
comes from (api, ui, default). Use it to find
the logins to push plans for. pending lists the plans received for logins
never seen connected yet.
PUT/api/v1/plans/{login}
curl -X PUT <your-freeqos-url>/api/v1/plans/john.doe \
-H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' \
-d '{"down_mbps":100,"up_mbps":20}'
| Field | Description |
|---|---|
down_mbps, up_mbps | Rates in Mbit/s. At least one rate, or a package, is required. |
package_id | Instead of rates: the id of a package pushed
to /model/v1/packages; its rates are used. |
- The client does not need to have connected yet. Your management interface
(the one that feeds your RADIUS) can push the plan as soon as it creates the
subscriber, and an initial sync can push every subscriber, online or not. The plan of
a login never seen connected is kept, answered with
"pending": trueand the stateen-attente-de-connexion, and applied by itself at the subscriber's first PPPoE session (at the latest at the next reconciliation pass, 2 minutes by default). Use the PPPoE login, which is the RADIUS username. A login with leading or trailing spaces or control characters is refused (422). When plans are read from RADIUS (PLAN_PROVIDER=freeradius_sql), a new subscriber's plan comes from RADIUS at its first connection: a pending plan is then refused (409) rather than silently ignored. - Setting a plan lifts a forced limit on that client: the plan becomes the rule.
Enforcement report. The response returns the client record and an
enforcement object telling what really happened on the router:
state | Meaning |
|---|---|
file-posee | Queue written (or already correct) on the router. |
file-retiree | Queue removed (plan deleted). |
file-a-poser | Would be written: simulation only. |
ecarte | Not written, and reason says why
(client offline, address shared, infrastructure address…). |
conflit | Another queue already targets this client on the router; it must be cleaned by hand. |
sans-routeur | No router carries this client yet. |
en-attente-de-connexion | Plan kept for a login never seen connected: applied at its first connection. |
erreur | The router refused or could not be reached:
reason gives the RouterOS message. |
DELETE/api/v1/plans/{login}
Removes the plan: the client's queue is removed and it is no longer
throttled (it stays observed). For a login still waiting for its first connection,
the pending plan is simply forgotten. A login with neither a session nor a plan returns
404.
POST/api/v1/plans/refresh
Re-reads every plan and re-writes the queues right away, instead of waiting for the next cycle.
Forced limits and boosts
Force a limit
PUT/api/v1/shaping/policies
A forced limit takes precedence over the plan: suspension for unpaid bills, fair-use cap, or a cap on a link (a radio backhaul, a whole sector).
curl -X PUT <your-freeqos-url>/api/v1/shaping/policies \
-H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' \
-d '{"scope":"subscriber","target_key":"john.doe","max_down_mbps":5,"max_up_mbps":1,
"note":"unpaid invoice"}'
| Field | Description |
|---|---|
scope | subscriber (a client) or
link (a link of the network tree). |
target_key | Client login (or static-client reference), or link key. |
max_down_mbps, max_up_mbps | The cap, in
Mbit/s (_kbps and _gbps variants accepted). |
enabled | false disables shaping for this
target entirely. |
note | Free text, kept in the history. |
Lift it with DELETE /api/v1/shaping/policies/{scope}/{target_key}; the
client goes back to its plan. GET /api/v1/shaping/policies lists the
limits in force.
Temporary boost
POST/api/v1/shaping/boosts
Raises a client's rate for a limited time; it expires by itself.
curl -X POST <your-freeqos-url>/api/v1/shaping/boosts \
-H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' \
-d '{"login":"john.doe","duration_minutes":60,"multiplier":2,"reason":"speed test"}'
Give either multiplier or absolute rates (down_mbps,
up_mbps). GET /api/v1/shaping/boosts lists the running
boosts; DELETE /api/v1/shaping/boosts/{login} ends one early.
Static-IP clients
A client without a PPPoE session (business line, VLAN, fixed IP or subnet) is declared with its address. freeQoS finds the router that serves the address and shapes it like any other client.
POST/api/v1/static-clients
curl -X POST <your-freeqos-url>/api/v1/static-clients \
-H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' \
-d '{"reference":"acme-hq","pop_name":"North","address":"203.0.113.16/29",
"label":"ACME head office","vlan":2060,"plan_down_mbps":50,"plan_up_mbps":50}'
| Field | Description |
|---|---|
reference | Your stable identifier for the client; it is its identifier everywhere else (plans, limits). |
pop_name | Site of the router that serves it. |
address | Single IP (10.0.0.5) or subnet
(10.0.0.0/29). |
plan_down_mbps, plan_up_mbps | Its plan
(optional; can be pushed later with PUT /api/v1/plans/{reference}). |
label, vlan, cpe_mac,
note | Optional details. |
Edit with PATCH /api/v1/static-clients/{client_id}, remove with
DELETE. GET /api/v1/static-clients/enforcement tells, for
each declared client, whether its queue is in place and what is missing.
Billing integration (Preseem-compatible)
The model API receives your commercial inventory. It follows the Preseem model API: a billing system that already pushes to Preseem (Splynx, UISP/UCRM, Powercode, in-house scripts) only changes the base URL and the key.
| Call | Effect |
|---|---|
GET /model/v1/{collection} |
List, as {"data": [...], "paginator": {...}}. Paginate with
?page=1&limit=500. |
GET /model/v1/{collection}/{id} |
One record. |
PUT /model/v1/{collection}/{id} |
Create or replace (idempotent). |
DELETE /model/v1/{collection}/{id} |
Remove. Removing a service removes its queue. |
| Collection | Record | Main fields |
|---|---|---|
accounts | Customer | name |
packages | Commercial plan | name, down_speed, up_speed (kbit/s) |
sites | Tower or PoP | name |
access_points | Radio sector | name, site, ip_address |
services | Sold line (what is shaped) | account, package, down_speed,
up_speed, parent_device_id,
attachments |
Push a service
curl -u '<key>:' -X PUT <your-freeqos-url>/model/v1/services/svc-42 \
-H 'Content-Type: application/json' \
-d '{"id":"svc-42","account":"cust-7","package":"fibre-100",
"down_speed":100000,"up_speed":20000,
"attachments":[{"cpe_mac":"00:10:0b:6e:4c:ff","network_prefixes":["10.20.0.10"]}]}'
- Rates of the service, in kbit/s, take precedence over those of its package. Without rates, the package's apply.
- Address.
network_prefixesplaces the service on the router that serves that address. A service with only acpe_macis accepted and kept aside; it becomes a client as soon as the MAC shows up on a router. - Rules. A body whose
iddiffers from the URL returns400; a negative speed returns400; a service id already used by a client entered by hand returns409(the API never takes over a human decision). Invalid JSON returns400, as with Preseem.
Consumption (usage)
Bytes consumed by each service, measured from NetFlow (counted once, at a single point of the network). Answers "how much did this line use last month?".
| GET | /usage/v1/services |
Every service over the period. |
| GET | /usage/v1/services/{service_id} |
One service, split by day by default. |
| Parameter | Description |
|---|---|
start, end | Period, ISO 8601 UTC. |
days | Window when start is omitted
(default 30). |
bucket | total, hour,
day or month. |
curl -u '<key>:' '<your-freeqos-url>/usage/v1/services/svc-42?bucket=month&days=90'
Reading measurements
All read calls work with a read key.
| GET | /api/v1/overview |
Headline figures: clients, throughput, sites. |
| GET | /api/v1/subscribers/latest |
Each client with its last sample: throughput down/up, latency, plan, limit
in force. Filters: pop_id, search, kind
(pppoe / static), limit. |
| GET | /api/v1/subscribers/{subscriber_id}/metrics |
Throughput series of a client (minutes,
bucket_seconds, or start/end). |
| GET | /api/v1/latency/clients |
Latency of every client over the period: median, p95, jitter, loss, latency under load and experience score. |
| GET | /api/v1/bufferbloat |
Bufferbloat grade (A+ to F) per client. |
| GET | /api/v1/netflow/top |
Who consumes the most over the period. |
| GET | /api/v1/netflow/applications |
Traffic by usage (video, social, gaming…), for the network or one client
(subscriber_id). |
| GET | /api/v1/network/tree |
The network tree with capacity and load of every node. |
| GET | /api/v1/capacity/hotspots |
Saturation points: headroom left on every link. |
| GET | /api/v1/search |
Find a client, an IP, a MAC or a device (?q=). |
A client's subscriber_id is returned by
/api/v1/subscribers/latest and /api/v1/search.
Operations and diagnostics
| GET | /api/v1/status |
State of the controller and of its cycles. |
| POST | /api/v1/jobs/{job_name}/run |
Run a cycle now instead of waiting: collect_subscribers,
refresh_plans, reconcile_shaping,
probe_rtt, discover_topology,
collect_links. |
| GET | /api/v1/shaping/enforcement |
Whether freeQoS is allowed to write to the routers. |
| GET | /api/v1/shaping/limits |
Are the caps really held on the routers? (FastTrack, masked queues…) |
| GET | /api/v1/shaping/audit |
Log of every command sent to the routers. |
| GET | /api/v1/rtt/diagnose |
Run the latency probe by hand and get the cause of a missing latency
(?router=&address=, or nothing for the last silent client). |
Errors
Errors return a JSON body {"detail": "..."} with a readable message.
| Code | Meaning |
|---|---|
400 | Invalid request (model and usage APIs: invalid JSON or field). |
401 | Missing, unknown, disabled or expired key. |
403 | The key lacks the write scope, or the
call is reserved to a person (keys, accounts). |
404 | Unknown client, router or record. |
409 | Conflict: writing is disabled, or the record belongs to an entry made by hand. |
422 | Invalid field on the operating API; the
detail says which. |
503 | The database is not ready yet (server starting). |
Every endpoint
Generated live from this server, so it always matches the version installed. Click an endpoint to see its parameters and body.
Loading…