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.

APIPathUse 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).
How shaping works. When a router is added, freeQoS sets it up by itself (CAKE queue types, NetFlow export) and discovers its clients. A client is throttled only once a plan is pushed for it (by the API or the Plans page); until then it is observed, never capped at a rate nobody sold. The plan is written to the router immediately as a CAKE queue, and kept in place by a reconciliation every two minutes.

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.

ScopeAllows
readEvery GET: inventory, plans, measurements, traffic, consumption.
read + writeAlso 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/json with a body.
  • Units. /api/v1 takes rates in Mbit/s (down_mbps, up_mbps); some endpoints also accept _kbps and _gbps variants. /model/v1 takes kbit/s (down_speed, up_speed), like Preseem. /usage/v1 returns 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. PUT creates 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.

  1. Check the key.
    curl -H 'Authorization: Bearer <key>' <your-freeqos-url>/model/v1
  2. 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"}'
  3. Wait for the setup (usually under a minute): finished_at is set when done.
    curl -H 'Authorization: Bearer <key>' <your-freeqos-url>/api/v1/pops/provisioning/nas-north
  4. Find the clients freeQoS discovered on it (PPPoE sessions, VLAN clients):
    curl -H 'Authorization: Bearer <key>' <your-freeqos-url>/api/v1/plans
  5. 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.

FieldRequiredDescription
nameyesUnique name of the router (nas-north). Used in every other call.
hostyesManagement IP or host name.
passwordyesPassword of the RouterOS account. Stored encrypted, never returned.
usernamenoRouterOS account (default qos-ro). To write queues, its group needs the policies read,write,api,test.
rolenopop (default, a router serving clients), core or gateway (internet exit: traffic is measured there).
pop_namenoSite name shown in the interface (default: the router name).
port, use_sslno8728 by default; use_ssl: true with port 8729 for API-SSL.
loopbacknoLoopback address, detected automatically when omitted.
enablednofalse keeps the router in the inventory without polling it.
write_enablednoAllow 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.
reasonnoWhy 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}'
FieldDescription
down_mbps, up_mbpsRates in Mbit/s. At least one rate, or a package, is required.
package_idInstead 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": true and the state en-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:

stateMeaning
file-poseeQueue written (or already correct) on the router.
file-retireeQueue removed (plan deleted).
file-a-poserWould be written: simulation only.
ecarteNot written, and reason says why (client offline, address shared, infrastructure address…).
conflitAnother queue already targets this client on the router; it must be cleaned by hand.
sans-routeurNo router carries this client yet.
en-attente-de-connexionPlan kept for a login never seen connected: applied at its first connection.
erreurThe 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"}'
FieldDescription
scopesubscriber (a client) or link (a link of the network tree).
target_keyClient login (or static-client reference), or link key.
max_down_mbps, max_up_mbpsThe cap, in Mbit/s (_kbps and _gbps variants accepted).
enabledfalse disables shaping for this target entirely.
noteFree 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}'
FieldDescription
referenceYour stable identifier for the client; it is its identifier everywhere else (plans, limits).
pop_nameSite of the router that serves it.
addressSingle IP (10.0.0.5) or subnet (10.0.0.0/29).
plan_down_mbps, plan_up_mbpsIts plan (optional; can be pushed later with PUT /api/v1/plans/{reference}).
label, vlan, cpe_mac, noteOptional 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.

CallEffect
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.
CollectionRecordMain fields
accountsCustomername
packagesCommercial plan name, down_speed, up_speed (kbit/s)
sitesTower or PoPname
access_pointsRadio sector name, site, ip_address
servicesSold 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_prefixes places the service on the router that serves that address. A service with only a cpe_mac is accepted and kept aside; it becomes a client as soon as the MAC shows up on a router.
  • Rules. A body whose id differs from the URL returns 400; a negative speed returns 400; a service id already used by a client entered by hand returns 409 (the API never takes over a human decision). Invalid JSON returns 400, 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.
ParameterDescription
start, endPeriod, ISO 8601 UTC.
daysWindow when start is omitted (default 30).
buckettotal, 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.

CodeMeaning
400Invalid request (model and usage APIs: invalid JSON or field).
401Missing, unknown, disabled or expired key.
403The key lacks the write scope, or the call is reserved to a person (keys, accounts).
404Unknown client, router or record.
409Conflict: writing is disabled, or the record belongs to an entry made by hand.
422Invalid field on the operating API; the detail says which.
503The 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…