# API reference

> The provider cabinet API for panels and bots: keys, domains, limited links, messages, devices and statistics, with curl examples.

For panels and bots: everything the cabinet has, except API keys and deleting the cabinet.

- Address: `https://api.flowlock.cc`.
- **API key**: cabinet → “API keys” → “Create a key”. The key (`flp_…`) is shown once; keep it like a password. You
  can create up to 10 keys and revoke any of them.
- The key goes **only in the header**: `Authorization: Bearer flp_…`. A key in the URL is not accepted.
- Bodies and responses are JSON, dates are RFC 3339 UTC. An error is an HTTP status plus
  `{"error": "…", "code": "…"}`. A `429` comes with a `Retry-After` header (seconds).
- Lists come in pages: `?cursor=` from the previous response's `next_cursor`.
- At most 120 requests per minute.

In the examples the key is in an environment variable:

```sh
export FLOWLOCK_KEY='flp_…'
```

## Provider

```sh
curl -s https://api.flowlock.cc/v1/provider -H "Authorization: Bearer $FLOWLOCK_KEY"
```

```json
{
	"id": "k7m2x9q4bd",
	"name": "My VPN",
	"support_url": "https://t.me/myvpn_support",
	"email": "help@myvpn.example",
	"telegram": "myvpn_bot",
	"status": "active",
	"created_at": "2026-10-28T12:00:00Z",
	"limits": { "domains": 50, "…": 0 }
}
```

Change the contacts (an empty string removes a field, except `name`):

```sh
curl -s -X PATCH https://api.flowlock.cc/v1/provider -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' -d '{"support_url": "https://t.me/myvpn_support", "telegram": "@myvpn_bot"}'
```

Fields: `name`: 1 to 40 characters; `support_url`: `https://…` or `tg://…`, up to 512; `email`; `telegram`: `@name`,
a `t.me` link or a name (5 to 32 characters of `A–Z a–z 0–9 _`).

## Domains

```sh
# list
curl -s https://api.flowlock.cc/v1/provider/domains -H "Authorization: Bearer $FLOWLOCK_KEY"
# add: the response holds the record and the file for verification
curl -s -X POST https://api.flowlock.cc/v1/provider/domains -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' -d '{"domain": "sub.example.com"}'
```

```json
{
	"id": "d1f2a3b4c5d6e7f8",
	"domain": "sub.example.com",
	"hash": "…64 hex…",
	"status": "pending",
	"created_at": "…",
	"dns": {
		"name": "_flowlock.sub.example.com",
		"type": "TXT",
		"value": "flowlock-provider=k7m2x9q4bd"
	},
	"file": {
		"url": "https://sub.example.com/.well-known/flowlock-provider",
		"body": "flowlock-provider=k7m2x9q4bd"
	}
}
```

```sh
# check now (up to 20 times an hour)
curl -s -X POST https://api.flowlock.cc/v1/provider/domains/d1f2a3b4c5d6e7f8/verify -H "Authorization: Bearer $FLOWLOCK_KEY"
# delete
curl -s -X DELETE https://api.flowlock.cc/v1/provider/domains/d1f2a3b4c5d6e7f8 -H "Authorization: Bearer $FLOWLOCK_KEY"
```

Domain fields: `status` (`pending` | `verified` | `failing` | `lost`), `method` (`dns` | `file`: how it was
verified), `verified_at`, `checked_at`, `error`: the reason for the last failure: `no_record` (no record and no
file), `wrong_id` (the record exists, but with another ID), `dns_error`, `http_status`, `fetch_error`, `not_public`
(the domain points to a private address).

## Links

```sh
# create a limited link for 3 devices
curl -s -X POST https://api.flowlock.cc/v1/provider/installs -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://sub.example.com/abc123", "limit": 3, "note": "user 1042"}'
```

```json
{
	"id": "i8c7b6a5d4e3f2a1",
	"code": "a7k2m9x4q8b3n6p5",
	"link": "flowlock://install/a7k2m9x4q8b3n6p5",
	"web_link": "https://flowlock.cc/i/a7k2m9x4q8b3n6p5",
	"url": "https://sub.example.com/abc123",
	"domain": "sub.example.com",
	"limit": 3,
	"count": 0,
	"hwid": "",
	"note": "user 1042",
	"status": "active",
	"created_at": "…",
	"expires_at": null
}
```

```sh
# HWID link: limit 1 and the device's HWID
curl -s -X POST https://api.flowlock.cc/v1/provider/installs -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://sub.example.com/abc123", "limit": 1, "hwid": "5f0c…"}'
# list; one link; change the limit, note, status (active | disabled), address, expiry
curl -s https://api.flowlock.cc/v1/provider/installs -H "Authorization: Bearer $FLOWLOCK_KEY"
curl -s https://api.flowlock.cc/v1/provider/installs/i8c7b6a5d4e3f2a1 -H "Authorization: Bearer $FLOWLOCK_KEY"
curl -s -X PATCH https://api.flowlock.cc/v1/provider/installs/i8c7b6a5d4e3f2a1 -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' -d '{"limit": 5}'
# the link's devices, and unlinking one
curl -s https://api.flowlock.cc/v1/provider/installs/i8c7b6a5d4e3f2a1/devices -H "Authorization: Bearer $FLOWLOCK_KEY"
curl -s -X DELETE https://api.flowlock.cc/v1/provider/installs/i8c7b6a5d4e3f2a1/devices/5f0c… \
  -H "Authorization: Bearer $FLOWLOCK_KEY"
# delete the link
curl -s -X DELETE https://api.flowlock.cc/v1/provider/installs/i8c7b6a5d4e3f2a1 -H "Authorization: Bearer $FLOWLOCK_KEY"
```

`expires_at`: RFC 3339 in the future, or `null` (no expiry). Errors: the address isn't on a verified domain:
`422 domain_unverified`; the limit is below the number of devices: `422 limit_below_count`; with `hwid` the limit can
only be 1: `400 bad_request`.

## Messages

```sh
# notification to everyone
curl -s -X POST https://api.flowlock.cc/v1/provider/messages -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' -d '{
  "kind": "notification", "mode": "optional",
  "title": "Maintenance", "body": "Tonight 23:00 to 00:00 UTC service may be interrupted.",
  "link": "https://myvpn.example/status",
  "locales": {"ru": {"title": "Плановые работы", "body": "Сегодня с 02:00 до 03:00 МСК возможны перебои."}},
  "target": {"all": true}, "expires_in": 86400}'
```

```json
{ "id": "m3k9q2x7a1b4c5d6", "kind": "notification", "status": "active", "…": "…" }
```

```sh
# command: refresh the subscription on Windows and Linux on a schedule
curl -s -X POST https://api.flowlock.cc/v1/provider/messages -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' -d '{
  "kind": "command", "action": "update-subscription",
  "target": {"os": ["windows", "linux"]}, "send_at": "2026-11-01T09:00:00Z"}'
# command: move to a new domain (it must be verified)
curl -s -X POST https://api.flowlock.cc/v1/provider/messages -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' -d '{
  "kind": "command", "action": "sub-change", "change_type": "domain", "value": "sub2.example.com",
  "target": {"all": true}}'
# command: new settings for two devices
curl -s -X POST https://api.flowlock.cc/v1/provider/messages -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' -d '{
  "kind": "command", "action": "set-settings",
  "settings": {"ping-type": "tcp", "fragmentation-enable": true},
  "target": {"devices": ["5f0c…", "9b1e…"]}}'
# command: offer an import (the user confirms)
curl -s -X POST https://api.flowlock.cc/v1/provider/messages -H "Authorization: Bearer $FLOWLOCK_KEY" \
  -H 'Content-Type: application/json' -d '{
  "kind": "command", "action": "import-data", "data": "https://sub.example.com/abc123",
  "target": {"devices": ["5f0c…"]}}'
# list (with delivery results), one message, cancel
curl -s https://api.flowlock.cc/v1/provider/messages -H "Authorization: Bearer $FLOWLOCK_KEY"
curl -s https://api.flowlock.cc/v1/provider/messages/m3k9q2x7a1b4c5d6 -H "Authorization: Bearer $FLOWLOCK_KEY"
curl -s -X DELETE https://api.flowlock.cc/v1/provider/messages/m3k9q2x7a1b4c5d6 -H "Authorization: Bearer $FLOWLOCK_KEY"
```

Message fields:

- `kind`: `notification` | `command`;
- notification: `title`, `body`, `link`, `locales` (`ru`/`en`), `mode` (`optional` | `force`);
- command: `action` and its fields: `change_type` + `value` (`sub-change`), `data` (`import-data`, up to 16 KB),
  `settings` (`set-settings`, keys from [“App settings”](https://flowlock.cc/en/docs/app-management.md#app-settings));
- `target`: `{"all": true}`, `{"os": […]}` or `{"devices": […]}`;
- `send_at`: optional; `expires_in`: seconds from `send_at` (3600 to 2592000, 604800 by default).

In the list: `status` (`scheduled` | `active` | `expired` | `cancelled`) and `counts`: `received`, `shown`,
`opened`, `done`, `declined`, `failed`.

Errors: `400 bad_request` (the field in `field`), `422 domain_unverified`, `422 bad_setting` (the key in `key`),
`429 rate_limited` (anti-spam, `retry_after`), `403 suspended`.

## Devices and statistics

```sh
# devices (for choosing recipients): filter by system and active in the last 30 days
curl -s 'https://api.flowlock.cc/v1/provider/devices?os=windows&active=30d' -H "Authorization: Bearer $FLOWLOCK_KEY"
```

```json
{
	"devices": [
		{
			"device": "5f0c…",
			"device_kind": "hwid",
			"os": "windows",
			"os_version": "10.0.26200",
			"app": "0.4.0",
			"first_seen": "…",
			"last_seen": "…"
		}
	],
	"next_cursor": "…"
}
```

`device_kind`: `hwid`: the device's HWID (the same one your panel gets in `x-hwid`); `alias`: an alias, if the user
doesn't send the HWID. More on the [“Statistics”](https://flowlock.cc/en/docs/stats.md) page.

```sh
curl -s https://api.flowlock.cc/v1/provider/stats -H "Authorization: Bearer $FLOWLOCK_KEY"
```

```json
{
	"devices": {
		"total": 1520,
		"active_30d": 1204,
		"by_os": {
			"windows": { "total": 900, "active_30d": 700 },
			"macos": { "total": 420, "active_30d": 350 },
			"linux": { "total": 200, "active_30d": 154 }
		}
	},
	"months": [
		{
			"month": "2026-10",
			"new": { "windows": 120, "macos": 40 },
			"active": { "windows": 700, "macos": 350 }
		}
	]
}
```

## Reference data for forms

`GET /v1/provider/settings-keys`: the “App settings” keys with their types (`bool`, `int`, `string`, `enum` +
`values`), scope (`app`: the whole app, `subscription`: this subscription) and platforms.

## Error codes

| code                  | HTTP | what it means                                                                                |
| --------------------- | ---- | -------------------------------------------------------------------------------------------- |
| `unauthorized`        | 401  | no key, or the key is wrong or revoked                                                       |
| `not_provider`        | 404  | the account has no cabinet turned on                                                         |
| `feature_disabled`    | 403  | provider cabinets are temporarily switched off on the server                                 |
| `suspended`           | 403  | the cabinet is suspended: messages and new links are not allowed                             |
| `bad_request`         | 400  | the body is malformed (`field` says which field)                                             |
| `bad_domain`          | 400  | not a domain (an IP, no dot, invalid characters)                                             |
| `conflict`            | 409  | already exists                                                                               |
| `not_found`           | 404  | no such thing                                                                                |
| `domain_unverified`   | 422  | the address isn't on your verified domain                                                    |
| `bad_setting`         | 422  | the key isn't in the settings list or the value doesn't fit (`key`)                          |
| `limit_below_count`   | 422  | the link's limit is below the number of its devices                                          |
| `limit`               | 422  | a ceiling is reached: domains, links, keys, messages (`upgrade: true`: “Unlimited” lifts it) |
| `billing_off`         | 503  | payments for “Unlimited” are off on the server                                               |
| `prepaid_max`         | 409  | “Unlimited” is already paid 12 months ahead                                                  |
| `payment_unavailable` | 502  | the payment service did not answer, retry later                                              |
| `rate_limited`        | 429  | too often, retry after `retry_after`                                                         |

## Limits

| What                             | How many                           |
| -------------------------------- | ---------------------------------- |
| API keys                         | 10                                 |
| domains                          | 50                                 |
| limited links                    | 5000                               |
| limit of one link                | 1000 devices                       |
| devices in statistics            | 500,000                            |
| messages to everyone / by system | 2 a day, at most once in 5 minutes |
| messages to selected devices     | 200 an hour, up to 20 devices each |
| messages waiting for delivery    | 500                                |
| API requests                     | 120 a minute                       |
| manual domain checks             | 20 an hour                         |

Need more? [“Unlimited”](https://flowlock.cc/en/docs/limits.md) or FlowLock support.
