# Mikrodius Public API > Cloud RADIUS for ISPs, RT/RW nets and hotspot operators, driven from your own billing app. Mikrodius authenticates PPPoE and hotspot logins for ISPs, RT/RW nets and hotspot operators. Your billing app keeps the customers and payments and calls this API to make the network enforce them: one subscriber per customer, a renew per payment, and webhooks for what happened on the network. - Version: 1.0.0-draft.2 - Base URL: https://mikrodius.com/api/v1 - Authentication: apiKey — An API key sent as `Authorization: Bearer mk_live_...`. ## Start here - [Guide and full reference](https://mikrodius.com/llms-full.txt): read this before writing code. The rules below, a step-by-step guide to building a billing app on the API (setup, activation, payments, webhooks, errors, with code), then every operation and schema. - [Agent skill](https://mikrodius.com/skill.zip): the same guide as a skill folder (`mikrodius/SKILL.md` and `reference.md`) for Claude Code and other agents that load skills. Single-file version for rules files and system prompts: [skill.md](https://mikrodius.com/skill.md). - Claude Code plugin: the skill plus the MCP server. Install with `/plugin marketplace add https://mikrodius.com/plugins/marketplace.json`, then `/plugin install mikrodius@mikrodius`. - [Pricing](https://mikrodius.com/pricing.md): what an account pays, in credits: the access rates, free use, NAS fees, the welcome credit and the dedicated plan, from the live rates. ## Rules for building on this API Follow these whenever you write code that calls Mikrodius. Each one prevents a real bug. 1. **Keep the API key on the server.** Read it from an environment variable (`MIKRODIUS_API_KEY`) and call the API only from backend code. Never put it in browser, mobile or client-side code, and never commit it. 2. **Link customers with `external_id`.** Create every subscriber with your own immutable customer ID as `external_id`, then address them as `external_id:` (URL-encoded) in every path. You do not need to store Mikrodius `sub_` IDs. 3. **Payment received means `renewSubscriber`, always with a `reference`.** Pass your payment or invoice ID as `reference`, so a retried call can never extend twice. Do not compute `expires_at` yourself and `PATCH` it for a payment. 4. **Let expiry do the cutting off.** A subscriber whose `expires_at` has passed is refused (or put on the isolation plan) automatically, and live sessions are ended. You do not need a cron job that suspends unpaid customers. Use `suspendSubscriber` only for reasons other than time running out (abuse, a manual hold). 5. **Units:** speed in **kbps** (20 Mbps = `20000`), data in **bytes**, time in **seconds**. For every limit, `null` means unlimited and `0` never does. 6. **Every write is safe to retry.** When a retried create returns `409` (`username_taken`, `external_id_taken`), fetch the subscriber by `external_id:`: if it is there, the earlier attempt worked, so carry on. Never invent a new username to get past a `409`. 7. **Switch on the error `code`**, never on `title` or `detail`, and log `request_id`. On `429 rate_limited` wait `Retry-After` seconds; retry `5xx` with backoff. 8. **Verify every webhook** (`Mikrodius-Signature`, HMAC-SHA256 over `.`), answer `2xx` within 10 seconds, do the work afterwards, de-duplicate on `Mikrodius-Event-Id`, and ignore event types you do not know. 9. **Use webhooks instead of polling** for expiry, quota, first login and NAS status. 10. **Tolerate additions:** ignore unknown response fields and unknown enum values; they ship without a version change. 11. **Never ask for or store NAS credentials.** Mikrodius generates the tunnel keys and the RADIUS secret. Onboard a router by creating the NAS and showing the operator its `install_link.command` to paste into the RouterOS terminal. 12. **Only call operations that exist in this reference.** There are no `metadata` fields and no `Idempotency-Key` header; keep your own data in your database and link it with `external_id`. ## Reference - [Full reference](https://mikrodius.com/llms-full.txt): every operation with its parameters, request bodies, responses and webhook events. - [OpenAPI document](https://mikrodius.com/api/v1/openapi.yaml): the source of truth these files are generated from. - [MCP server](https://mikrodius.com/api/v1/mcp): the same operations as Model Context Protocol tools, authenticated with the API key above. - [OpenAI tools](https://mikrodius.com/api/v1/tools/openai.json) and [Claude tools](https://mikrodius.com/api/v1/tools/claude.json): function-calling schemas for the same operations. ## Account Information about the key making the request. - [getMe](https://mikrodius.com/api/v1/me) `GET /api/v1/me`: Get the current key and account ## Workspaces A workspace is an isolated RADIUS namespace with its own NAS, plans, subscribers and sessions. Two workspaces may each have a subscriber called `user01`. There are no per-workspace ports or servers to configure. Each workspace runs on one node (see Nodes): the Mikrodius edge that holds its RADIUS data and terminates its routers' tunnels. Choose one with `node_id` when you create the workspace, or leave it to Mikrodius. Routers reach the workspace by its own hostname, which follows the workspace to its node. Mikrodius is pay as you go, in platform credits, counted in flat 30-day periods. Workspaces have no monthly fee. Your account balance is charged once for each access window a subscriber uses, once cumulative usage reaches 2 minutes or 5 MB. The price depends only on how long the access lasts (the shorter of the plan's validity, its time limit and the subscriber's `expires_at`, at most 30 days). Subscribers who never log in cost nothing. Login and usage below the free threshold do not require credits. At the threshold, the current price is checked against the account balance and workspace spend cap. If it cannot be paid, the subscriber is disconnected and further logins are rejected with `balance_exhausted` until billing succeeds. Paid windows remain usable. Each workspace can have a `spend_cap`: the most credits it may use in 30 days (access windows and NAS fees together). At the free threshold, access that would go over it is disconnected with `spend_cap_reached`; reconnects are rejected until billing succeeds. `POST /workspaces/{workspace_id}/reset-spend` starts the count again from zero. - [listWorkspaces](https://mikrodius.com/api/v1/workspaces) `GET /api/v1/workspaces`: List workspaces - [createWorkspace](https://mikrodius.com/api/v1/workspaces) `POST /api/v1/workspaces`: Create a workspace - [getWorkspace](https://mikrodius.com/api/v1/workspaces/{workspace_id}) `GET /api/v1/workspaces/{workspace_id}`: Get a workspace - [updateWorkspace](https://mikrodius.com/api/v1/workspaces/{workspace_id}) `PATCH /api/v1/workspaces/{workspace_id}`: Update a workspace - [deleteWorkspace](https://mikrodius.com/api/v1/workspaces/{workspace_id}) `DELETE /api/v1/workspaces/{workspace_id}`: Delete a workspace - [moveWorkspace](https://mikrodius.com/api/v1/workspaces/{workspace_id}/move) `POST /api/v1/workspaces/{workspace_id}/move`: Move to another node - [resetWorkspaceSpend](https://mikrodius.com/api/v1/workspaces/{workspace_id}/reset-spend) `POST /api/v1/workspaces/{workspace_id}/reset-spend`: Reset the spend count - [getWorkspaceStats](https://mikrodius.com/api/v1/workspaces/{workspace_id}/stats) `GET /api/v1/workspaces/{workspace_id}/stats`: Get workspace statistics ## Nodes The Mikrodius edges a workspace can run on, with where they are. Use `hostname` to measure your own latency to a node (`ping `) before choosing one. - [listNodes](https://mikrodius.com/api/v1/nodes) `GET /api/v1/nodes`: List nodes ## NAS A NAS (network access server) is any device that sends RADIUS requests to a workspace: a MikroTik router, an access point, a BNG or a hotspot controller. Add a NAS here first, then set up the device, either with the one-line install command (MikroTik) or by hand using `GET /nas/{nas_id}/config`. Devices never register themselves. Mikrodius generates every credential the device needs (tunnel keys and the RADIUS shared secret); you never create or store them. The `connection` is chosen when the NAS is added. The install command, `config` and credential rotation only ever produce that connection. A RouterOS v6 NAS can switch between `l2tp` and `sstp` at any time (same tunnel IP, login and fee; run a new install link on the router afterwards). A `wireguard` NAS stays `wireguard`: to move a router between WireGuard and v6, add a new NAS for it and remove the old one. A workspace has `included_nas` free NAS (25). A NAS added while all of them are taken is billed per day, prepaid in 30-day periods: 30 credits a day (900 per 30 days) with `wireguard`, 125 a day (3,750 per 30 days) with `l2tp` or `sstp`. The fee is charged when the NAS is added and then every 30 days, each NAS on its own cycle. A NAS stays free or billed; the order in which NAS were added does not matter. A billed NAS stops being billed when it is removed, or when a free NAS is removed and it takes that free place (right away; an `unpaid` NAS first, then the one with the most paid time left). The unused whole days of its period are then refunded; a day that has started counts as used; a NAS that has never connected gets its whole period refunded. Credit comes back as it was paid: paid credit as paid credit, promo credit to its grant with the grant's original expiry, unless that has passed. When a renewal cannot be charged, the NAS's `billing.status` becomes `unpaid` and logins through it are rejected with `nas_unpaid` until the fee is paid, which is retried automatically after a top up. - [listNas](https://mikrodius.com/api/v1/workspaces/{workspace_id}/nas) `GET /api/v1/workspaces/{workspace_id}/nas`: List NAS - [createNas](https://mikrodius.com/api/v1/workspaces/{workspace_id}/nas) `POST /api/v1/workspaces/{workspace_id}/nas`: Add a NAS - [getNas](https://mikrodius.com/api/v1/workspaces/{workspace_id}/nas/{nas_id}) `GET /api/v1/workspaces/{workspace_id}/nas/{nas_id}`: Get a NAS - [updateNas](https://mikrodius.com/api/v1/workspaces/{workspace_id}/nas/{nas_id}) `PATCH /api/v1/workspaces/{workspace_id}/nas/{nas_id}`: Update a NAS - [deleteNas](https://mikrodius.com/api/v1/workspaces/{workspace_id}/nas/{nas_id}) `DELETE /api/v1/workspaces/{workspace_id}/nas/{nas_id}`: Remove a NAS - [getNasConfig](https://mikrodius.com/api/v1/workspaces/{workspace_id}/nas/{nas_id}/config) `GET /api/v1/workspaces/{workspace_id}/nas/{nas_id}/config`: Get manual configuration - [createNasInstallLink](https://mikrodius.com/api/v1/workspaces/{workspace_id}/nas/{nas_id}/install-link) `POST /api/v1/workspaces/{workspace_id}/nas/{nas_id}/install-link`: Create an install link - [rotateNasCredentials](https://mikrodius.com/api/v1/workspaces/{workspace_id}/nas/{nas_id}/rotate-credentials) `POST /api/v1/workspaces/{workspace_id}/nas/{nas_id}/rotate-credentials`: Rotate credentials ## NAS install The endpoint the router calls when you paste the one-line install command. It is served from `https://mikrodius.com`, takes no API key (the install token in the path is the credential) and returns a RouterOS script, not JSON. API clients never call it. - [getInstallScript](https://mikrodius.com/i/{install_token}) `GET /i/{install_token}`: Download the install script ## Plans A plan is what you sell ("Home 20 Mbps", "Hotspot 1 Day"): speed, address pool, timeouts, device limit and optional data, time or validity limits. Each subscriber is on one plan. For a custom deal, create a plan for it. - [listPlans](https://mikrodius.com/api/v1/workspaces/{workspace_id}/plans) `GET /api/v1/workspaces/{workspace_id}/plans`: List plans - [createPlan](https://mikrodius.com/api/v1/workspaces/{workspace_id}/plans) `POST /api/v1/workspaces/{workspace_id}/plans`: Create a plan - [getPlan](https://mikrodius.com/api/v1/workspaces/{workspace_id}/plans/{plan_id}) `GET /api/v1/workspaces/{workspace_id}/plans/{plan_id}`: Get a plan - [updatePlan](https://mikrodius.com/api/v1/workspaces/{workspace_id}/plans/{plan_id}) `PATCH /api/v1/workspaces/{workspace_id}/plans/{plan_id}`: Update a plan - [deletePlan](https://mikrodius.com/api/v1/workspaces/{workspace_id}/plans/{plan_id}) `DELETE /api/v1/workspaces/{workspace_id}/plans/{plan_id}`: Delete a plan ## Subscribers The accounts that log in over PPPoE or hotspot: credentials, a plan, a status and an optional expiry, plus your own `external_id` for linking back to your billing records. - [listSubscribers](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers) `GET /api/v1/workspaces/{workspace_id}/subscribers`: List subscribers - [createSubscriber](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers) `POST /api/v1/workspaces/{workspace_id}/subscribers`: Create a subscriber - [getSubscriber](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers/{subscriber}) `GET /api/v1/workspaces/{workspace_id}/subscribers/{subscriber}`: Get a subscriber - [updateSubscriber](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers/{subscriber}) `PATCH /api/v1/workspaces/{workspace_id}/subscribers/{subscriber}`: Update a subscriber - [deleteSubscriber](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers/{subscriber}) `DELETE /api/v1/workspaces/{workspace_id}/subscribers/{subscriber}`: Delete a subscriber - [getSubscriberUsage](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/usage) `GET /api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/usage`: Get usage ## Subscriber actions The calls your billing app makes when something happens in billing: payment received (`renew`), payment overdue (`suspend`), new quota cycle (`reset-usage`), and so on. - [activateSubscriber](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/activate) `POST /api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/activate`: Reactivate a subscriber - [disconnectSubscriber](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/disconnect) `POST /api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/disconnect`: Disconnect a subscriber - [renewSubscriber](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/renew) `POST /api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/renew`: Renew a subscriber - [resetSubscriberUsage](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/reset-usage) `POST /api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/reset-usage`: Reset usage counters - [suspendSubscriber](https://mikrodius.com/api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/suspend) `POST /api/v1/workspaces/{workspace_id}/subscribers/{subscriber}/suspend`: Suspend a subscriber ## Vouchers Bulk-generate subscribers with random codes, for printing or selling as vouchers. - [generateVouchers](https://mikrodius.com/api/v1/workspaces/{workspace_id}/vouchers) `POST /api/v1/workspaces/{workspace_id}/vouchers`: Generate vouchers ## Sales An estimate of what you sold, from the plans' `price`. Mikrodius never takes payments, so a sale here means an access window that was used, not money received. A sale is recorded when Mikrodius charges an access window (see Workspaces), and at the first login for plans whose limits are too small to ever be charged. Each sale keeps the price and currency it had at that moment. - [listSales](https://mikrodius.com/api/v1/workspaces/{workspace_id}/sales) `GET /api/v1/workspaces/{workspace_id}/sales`: List sales - [getSalesSummary](https://mikrodius.com/api/v1/workspaces/{workspace_id}/sales/summary) `GET /api/v1/workspaces/{workspace_id}/sales/summary`: Get sales totals ## Sessions RADIUS accounting sessions, live and historical. A session starts with Accounting-Start and ends with Accounting-Stop, or is closed as `stale` when the NAS stops sending interim updates for 5 minutes. - [listSessions](https://mikrodius.com/api/v1/workspaces/{workspace_id}/sessions) `GET /api/v1/workspaces/{workspace_id}/sessions`: List sessions - [streamSessions](https://mikrodius.com/api/v1/workspaces/{workspace_id}/sessions/events) `GET /api/v1/workspaces/{workspace_id}/sessions/events`: Stream active sessions - [getSession](https://mikrodius.com/api/v1/workspaces/{workspace_id}/sessions/{session_id}) `GET /api/v1/workspaces/{workspace_id}/sessions/{session_id}`: Get a session - [disconnectSession](https://mikrodius.com/api/v1/workspaces/{workspace_id}/sessions/{session_id}/disconnect) `POST /api/v1/workspaces/{workspace_id}/sessions/{session_id}/disconnect`: Disconnect a session ## Webhook endpoints Register HTTPS endpoints that receive signed event notifications. - [listWebhookEndpoints](https://mikrodius.com/api/v1/webhook-endpoints) `GET /api/v1/webhook-endpoints`: List webhook endpoints - [createWebhookEndpoint](https://mikrodius.com/api/v1/webhook-endpoints) `POST /api/v1/webhook-endpoints`: Create a webhook endpoint - [getWebhookEndpoint](https://mikrodius.com/api/v1/webhook-endpoints/{endpoint_id}) `GET /api/v1/webhook-endpoints/{endpoint_id}`: Get a webhook endpoint - [updateWebhookEndpoint](https://mikrodius.com/api/v1/webhook-endpoints/{endpoint_id}) `PATCH /api/v1/webhook-endpoints/{endpoint_id}`: Update a webhook endpoint - [deleteWebhookEndpoint](https://mikrodius.com/api/v1/webhook-endpoints/{endpoint_id}) `DELETE /api/v1/webhook-endpoints/{endpoint_id}`: Delete a webhook endpoint - [rotateWebhookSecret](https://mikrodius.com/api/v1/webhook-endpoints/{endpoint_id}/rotate-secret) `POST /api/v1/webhook-endpoints/{endpoint_id}/rotate-secret`: Rotate the signing secret - [testWebhookEndpoint](https://mikrodius.com/api/v1/webhook-endpoints/{endpoint_id}/test) `POST /api/v1/webhook-endpoints/{endpoint_id}/test`: Send a test event ## Events Webhook events Mikrodius sends to your endpoints. Each delivery is a `POST` with a JSON `Event` body and `Mikrodius-Signature`, `Mikrodius-Event-Id` and `Mikrodius-Event-Type` headers. - `onNasOffline` (webhook `nas.offline`): NAS offline - `onNasOnline` (webhook `nas.online`): NAS online - `onSessionStarted` (webhook `session.started`): Session started - `onSessionStopped` (webhook `session.stopped`): Session stopped - `onSubscriberExpired` (webhook `subscriber.expired`): Subscriber expired - `onSubscriberFirstLogin` (webhook `subscriber.first_login`): First login - `onSubscriberFupReached` (webhook `subscriber.fup_reached`): FUP step reached - `onSubscriberLimitReached` (webhook `subscriber.limit_reached`): Limit reached - `onSubscriberLoginRejected` (webhook `subscriber.login_rejected`): Login rejected - `onSubscriberUpdated` (webhook `subscriber.updated`): Subscriber changed - `onWorkspaceMoved` (webhook `workspace.moved`): Workspace moved