Administration.
These calls need an administrator and a key with the admin scope. Several of them also need another license feature, named on the call. Reading the license does not.
See the API reference for the key, errors, and the licence spelling of the license routes.
License
GET /api/v1/license
What this installation is licensed to do: plan, speed, features, users and jobs. Answers in Free mode too.
Who. Any user with an API key. The key does not need a particular scope. This is the one API-key call that still answers when the license does not include the REST API, so a script can see why the others return 402. The same call is also at GET /api/v1/licence.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/license
Load a license. Checked before it replaces the old one.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. None.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | not a valid license, or not for this install |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "not a valid license, or not for this install"
}
}
DELETE /api/v1/admin/license
Remove the license. The server drops to Free mode at once; the file is set aside.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/admin/license/free-mode
Who stays active and which jobs keep running in Free mode.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/license/free-mode
Pick the active users and the running jobs.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. The raw bytes of the piece. Not JSON.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | more than the limit was picked |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "more than the limit was picked"
}
}
POST /api/v1/admin/license/request/preview
The license request email, with its request code, before it is sent.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. None.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/license/request/send
Send the license request to [email protected] through this server's email settings, or return it to send by hand.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. None.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/license/trial/start
Ask license.farwing.io for a 30-day trial. The only call this server makes to farwing.io.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. None.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/license/trial/verify
Enter the code from the trial email. The trial license is loaded at once.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. None.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
Audit
GET /api/v1/admin/audit
The audit trail. Read-only, including for administrators.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
action | query | string | optional | Exact action, or a prefix ending in . such as auth.. |
actorKind | query | string | optional | user, api_key, system or recipient. |
from | query | string | optional | RFC 3339. Inclusive. |
to | query | string | optional | RFC 3339. Exclusive, so paging by at cannot repeat a row. |
before | query | integer | optional | Page backwards: only rows with a smaller id than this one. |
limit | query | integer | optional | Up to 500. Clamped, not refused. |
q | query | string | optional | Matched against the action, the target and the detail. |
actorId | query | string | optional |
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
items | array of objects | required | The records in this reply. | ||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||
nextBefore | integer | optional | Pass as before for the next page. Null when there is no next page. | ||||||||||||||||||||||||||||||||||||||||
actions | array of string | required | Every action present in the table, so the filter can offer a list rather than ask an administrator to remember the naming scheme. | ||||||||||||||||||||||||||||||||||||||||
{
"items": [
{
"id": 1,
"at": "2026-10-05T18:00:00Z",
"actorKind": "example",
"action": "release"
}
],
"nextBefore": 1,
"actions": [
"release"
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | an unknown actorKind |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "an unknown actorKind"
}
}
GET /api/v1/admin/audit.csv
The same rows as a CSV file, with the same filters. No paging: the file is the whole window.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
action | query | string | optional | Exact action, or a prefix ending in . such as auth.. |
actorId | query | string | optional | |
actorKind | query | string | optional | |
from | query | string | optional | RFC 3339. Inclusive. |
to | query | string | optional | RFC 3339. Exclusive, so paging by at cannot repeat a row. |
before | query | integer | optional | Page backwards: only rows with a smaller id than this one. |
limit | query | integer | optional | |
q | query | string | optional | Matched against the action, the target and the detail. |
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
Settings
GET /api/v1/admin/transfer-settings
How long a transfer link stays valid before its transfer starts: {linkMinutes, defaultMinutes, minMinutes, maxMinutes, resumeHours}.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
linkMinutes | integer | required | |
defaultMinutes | integer | required | |
minMinutes | integer | required | |
maxMinutes | integer | required | |
resumeHours | integer | required | How long after its first use a broken transfer can be continued with the same ticket. Fixed; shown so the screen can say it. |
{
"linkMinutes": 1,
"defaultMinutes": 1,
"minMinutes": 1,
"maxMinutes": 1,
"resumeHours": 24
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/transfer-settings
Change it with {linkMinutes}. A value outside its range is refused, not clamped. Applies to links made from then on.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
linkMinutes | integer | required |
{
"linkMinutes": 1
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
linkMinutes | integer | required | |
defaultMinutes | integer | required | |
minMinutes | integer | required | |
maxMinutes | integer | required | |
resumeHours | integer | required | How long after its first use a broken transfer can be continued with the same ticket. Fixed; shown so the screen can say it. |
{
"linkMinutes": 1,
"defaultMinutes": 1,
"minMinutes": 1,
"maxMinutes": 1,
"resumeHours": 24
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a value outside its allowed range |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "a value outside its allowed range"
}
}
GET /api/v1/admin/sign-in-policy
Password rules, lockout and session timeout.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
passwordMinLength | integer | required | |
passwordRequireMixedCase | boolean | required | |
passwordRequireDigit | boolean | required | |
passwordRequireSymbol | boolean | required | |
requireTotp | boolean | required | Whether every account must carry an authenticator code, rather than each person deciding for themselves. |
lockoutAttempts | integer | required | |
lockoutMinutes | integer | required | |
sessionIdleMinutes | integer | required |
{
"passwordMinLength": 1,
"passwordRequireMixedCase": true,
"passwordRequireDigit": true,
"passwordRequireSymbol": true,
"requireTotp": true,
"lockoutAttempts": 1,
"lockoutMinutes": 1,
"sessionIdleMinutes": 1
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/sign-in-policy
Change them. A value outside its range is refused, not clamped.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
passwordMinLength | integer | required | |
passwordRequireMixedCase | boolean | required | |
passwordRequireDigit | boolean | required | |
passwordRequireSymbol | boolean | required | |
requireTotp | boolean | required | |
lockoutAttempts | integer | required | |
lockoutMinutes | integer | required | |
sessionIdleMinutes | integer | required |
{
"passwordMinLength": 1,
"passwordRequireMixedCase": true,
"passwordRequireDigit": true,
"passwordRequireSymbol": true,
"requireTotp": true,
"lockoutAttempts": 1,
"lockoutMinutes": 1,
"sessionIdleMinutes": 1
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
passwordMinLength | integer | required | |
passwordRequireMixedCase | boolean | required | |
passwordRequireDigit | boolean | required | |
passwordRequireSymbol | boolean | required | |
requireTotp | boolean | required | Whether every account must carry an authenticator code, rather than each person deciding for themselves. |
lockoutAttempts | integer | required | |
lockoutMinutes | integer | required | |
sessionIdleMinutes | integer | required |
{
"passwordMinLength": 1,
"passwordRequireMixedCase": true,
"passwordRequireDigit": true,
"passwordRequireSymbol": true,
"requireTotp": true,
"lockoutAttempts": 1,
"lockoutMinutes": 1,
"sessionIdleMinutes": 1
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a value outside its allowed range |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "a value outside its allowed range"
}
}
GET /api/v1/admin/appearance
Company name, logos, accent color, sign-in page text, globe route, footer links and notice, and what the accent measures against white.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
appearance | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
accentContrast | number | required | The measured contrast of white text on the accent. Returned so the screen can show how a color is doing while it is being chosen, rather than only when saving it fails. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
included | boolean | required | Whether the license includes custom branding. When it does not, recipients see the product's own look and saving is refused; what is stored is kept for when a license that includes it is loaded. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"appearance": {
"companyName": "example",
"accent": "example",
"logo": "example",
"logoDark": "example",
"eyebrow": "example",
"headline": "example",
"subtext": "example",
"linkLabel": "example",
"linkUrl": "example",
"cardTitle": "example",
"cardSubtitle": "example",
"globe": true,
"routeFrom": "example",
"routeTo": "example",
"footerLinks": [
{
"label": "example",
"url": "example"
}
],
"notice": "example"
},
"accentContrast": 1,
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/appearance
Change them.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include custom branding.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
companyName | string | required | |||||||||||||
accent | string | required | |||||||||||||
logo | string | required | Empty removes the logo, which is the only way to go back to the product's own mark once one has been uploaded. | ||||||||||||
logoDark | string | optional | |||||||||||||
eyebrow | string | optional | |||||||||||||
headline | string | optional | |||||||||||||
subtext | string | optional | |||||||||||||
linkLabel | string | optional | |||||||||||||
linkUrl | string | optional | |||||||||||||
cardTitle | string | optional | |||||||||||||
cardSubtitle | string | optional | |||||||||||||
globe | boolean | optional | |||||||||||||
routeFrom | string | optional | |||||||||||||
routeTo | string | optional | |||||||||||||
footerLinks | array of objects | optional | |||||||||||||
Each item is an object:
| |||||||||||||||
notice | string | optional | |||||||||||||
{
"companyName": "example",
"accent": "example",
"logo": "example"
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
appearance | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
accentContrast | number | required | The measured contrast of white text on the accent. Returned so the screen can show how a color is doing while it is being chosen, rather than only when saving it fails. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
included | boolean | required | Whether the license includes custom branding. When it does not, recipients see the product's own look and saving is refused; what is stored is kept for when a license that includes it is loaded. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"appearance": {
"companyName": "example",
"accent": "example",
"logo": "example",
"logoDark": "example",
"eyebrow": "example",
"headline": "example",
"subtext": "example",
"linkLabel": "example",
"linkUrl": "example",
"cardTitle": "example",
"cardSubtitle": "example",
"globe": true,
"routeFrom": "example",
"routeTo": "example",
"footerLinks": [
{
"label": "example",
"url": "example"
}
],
"notice": "example"
},
"accentContrast": 1,
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a color white text cannot be read on, a logo that is not a plain PNG or SVG, text over its length, or a link that is not https or mailto |
| 402 | licence_required | this server's license does not include custom branding |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "a color white text cannot be read on, a logo that is not a plain PNG or SVG, text over its length, or a link that is not https or mailto"
}
}
DELETE /api/v1/admin/appearance
Return to the Farwing defaults: every logo, color, text and link is removed.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
appearance | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
accentContrast | number | required | The measured contrast of white text on the accent. Returned so the screen can show how a color is doing while it is being chosen, rather than only when saving it fails. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
included | boolean | required | Whether the license includes custom branding. When it does not, recipients see the product's own look and saving is refused; what is stored is kept for when a license that includes it is loaded. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"appearance": {
"companyName": "example",
"accent": "example",
"logo": "example",
"logoDark": "example",
"eyebrow": "example",
"headline": "example",
"subtext": "example",
"linkLabel": "example",
"linkUrl": "example",
"cardTitle": "example",
"cardSubtitle": "example",
"globe": true,
"routeFrom": "example",
"routeTo": "example",
"footerLinks": [
{
"label": "example",
"url": "example"
}
],
"notice": "example"
},
"accentContrast": 1,
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
API keys
GET /api/v1/admin/api-keys
Every key on the server. Never a secret: a key is shown once, when it is made.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Each key has clients: the last version of the farwing command line and of Farwing Desktop it was used from, newest first. Empty for a key only used by scripts.
Success. 200.
The body is an array.
| Field | Type | Meaning | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | string | required | The id. | ||||||||||||||||
name | string | required | The name. | ||||||||||||||||
ownerId | string | required | The id. | ||||||||||||||||
ownerEmail | string | required | The address, because an id tells whoever has to judge the key nothing. Kept rather than deleted accounts are what make this resolvable. | ||||||||||||||||
scopes | array of string | required | |||||||||||||||||
createdAt | string | required | A time, as RFC 3339. | ||||||||||||||||
expiresAt | string | optional, left out when empty | A time, as RFC 3339. | ||||||||||||||||
lastUsedAt | string | optional, left out when empty | Absent on a key that has never been used, which is itself the answer to "can we take this one away". | ||||||||||||||||
revoked | boolean | required | The plain answer, so the list can be read without working out what a null timestamp means. | ||||||||||||||||
revokedAt | string | optional, left out when empty | A time, as RFC 3339. | ||||||||||||||||
clients | array of ClientSeen | optional, left out when empty | The Farwing clients the key was last used from. In the list of keys only. | ||||||||||||||||
Each item is an object:
| |||||||||||||||||||
[
{
"id": "k7Qm2sLp9vX4aB1c",
"name": "Rush delivery",
"ownerId": "k7Qm2sLp9vX4aB1c",
"ownerEmail": "[email protected]",
"scopes": [
"read"
],
"createdAt": "2026-10-05T18:00:00Z",
"revoked": true
}
]
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/admin/api-keys/policy
How long a new key may live, and whether one may never expire.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
maxDays | integer | required | The longest life anyone may ask for, in days. |
allowNever | boolean | required | Whether a key may be made with no expiry at all. |
{
"maxDays": 1,
"allowNever": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/api-keys/policy
Change that. Keys already issued keep the expiry they were given.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
maxDays | integer | required | Both fields are required. A defaulted field would mean saving one setting silently reset the other, and an administrator who narrowed the maximum life would find they had also allowed keys that never expire. |
allowNever | boolean | required |
{
"maxDays": 1,
"allowNever": true
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
maxDays | integer | required | The longest life anyone may ask for, in days. |
allowNever | boolean | required | Whether a key may be made with no expiry at all. |
{
"maxDays": 1,
"allowNever": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a value outside its allowed range |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "a value outside its allowed range"
}
}
DELETE /api/v1/admin/api-keys/{id}
Revoke somebody else's key.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Success. 200.
| Field | Type | Meaning | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | string | required | The id. | ||||||||||||||||
name | string | required | The name. | ||||||||||||||||
ownerId | string | required | The id. | ||||||||||||||||
ownerEmail | string | required | The address, because an id tells whoever has to judge the key nothing. Kept rather than deleted accounts are what make this resolvable. | ||||||||||||||||
scopes | array of string | required | |||||||||||||||||
createdAt | string | required | A time, as RFC 3339. | ||||||||||||||||
expiresAt | string | optional, left out when empty | A time, as RFC 3339. | ||||||||||||||||
lastUsedAt | string | optional, left out when empty | Absent on a key that has never been used, which is itself the answer to "can we take this one away". | ||||||||||||||||
revoked | boolean | required | The plain answer, so the list can be read without working out what a null timestamp means. | ||||||||||||||||
revokedAt | string | optional, left out when empty | A time, as RFC 3339. | ||||||||||||||||
clients | array of ClientSeen | optional, left out when empty | The Farwing clients the key was last used from. In the list of keys only. | ||||||||||||||||
Each item is an object:
| |||||||||||||||||||
{
"id": "k7Qm2sLp9vX4aB1c",
"name": "Rush delivery",
"ownerId": "k7Qm2sLp9vX4aB1c",
"ownerEmail": "[email protected]",
"scopes": [
"read"
],
"createdAt": "2026-10-05T18:00:00Z",
"revoked": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
Settings
GET /api/v1/admin/system
Version, uptime, disk, health checks and backups. A figure this platform cannot measure is absent rather than guessed.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
version | string | required | |||||||||||||||||||||
build | string | required | The version and the commit it was built from, such as 0.4.0 (abc1234). | ||||||||||||||||||||
installId | string | required | The id. | ||||||||||||||||||||
startedAt | string | required | When this process started, not when the installation was made. | ||||||||||||||||||||
uptimeSeconds | integer | required | Counted from [started_at], so it is the age of the process. See the note there for why it can read low on a server nobody has looked at. | ||||||||||||||||||||
database | object | required | |||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||
disk | array of objects | required | |||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
checks | array of objects | required | |||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
backups | array of objects | required | |||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
{
"version": "example",
"build": "example",
"installId": "k7Qm2sLp9vX4aB1c",
"startedAt": "2026-10-05T18:00:00Z",
"uptimeSeconds": 1,
"database": {
"path": "projects/rush",
"bytes": 1048576,
"schemaVersion": 1
},
"disk": [
{
"name": "Rush delivery",
"path": "projects/rush",
"bytesFree": 1,
"bytesTotal": 1
}
],
"checks": [
{
"name": "Rush delivery",
"ok": true,
"detail": "example"
}
],
"backups": [
{
"name": "Rush delivery",
"bytes": 1048576,
"modifiedAt": "2026-10-05T18:00:00Z"
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/admin/system/logs
The audit trail as a text file. The process log goes to standard output, where the container runtime keeps it.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
Speed check
GET /api/v1/speedcheck
Past speed checks, and the weekly schedule.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. A speed check measures what limits transfers on this server: the network, the processor, the staging disk, each storage location and the license's speed. It works on every plan, Free included. The reply is one page of checks (runs), how many match in all (total), the schedule, how many transfers are running now (transfersRunning), and how long a check usually takes in seconds (estimatedSeconds). from and to are inclusive. Without limit the page holds 50.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
from | query | string | optional | RFC 3339. Inclusive. Absent means no lower bound. |
to | query | string | optional | RFC 3339. Inclusive. Absent means no upper bound. |
limit | query | integer | optional | How many checks to return, from 1 to 200. Defaults to 50. A larger number is brought down to 200. |
offset | query | integer | optional | How many matching checks to skip. Defaults to 0. |
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
runs | array of objects | required | |||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||
total | integer | required | How many checks match from and to, including ones not on this page. | ||||||||||||||||||||||||||||||||
schedule | object | required | |||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||
transfersRunning | integer | required | |||||||||||||||||||||||||||||||||
estimatedSeconds | integer | required | |||||||||||||||||||||||||||||||||
places | array of objects | required | Every place a check can time, so the screen can say what this run will cover before it starts. | ||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||
savedBucketIds | array of string | required | Buckets included last time. A new bucket is not in this list. | ||||||||||||||||||||||||||||||||
{
"runs": [
{
"id": "k7Qm2sLp9vX4aB1c",
"state": "active",
"verdict": "example",
"limiter": {},
"receiveBps": 1,
"sendBps": 1,
"startedAt": "2026-10-05T18:00:00Z"
}
],
"total": 1,
"schedule": {
"enabled": true,
"weekday": 1,
"minute": 1,
"addresses": [
"example"
],
"lastRunAt": "2026-10-05T18:00:00Z",
"included": true
},
"transfersRunning": 1,
"estimatedSeconds": 1,
"places": [
{
"id": "k7Qm2sLp9vX4aB1c",
"title": "example",
"path": "projects/rush",
"role": "example",
"region": "example"
}
],
"savedBucketIds": [
"k7Qm2sLp9vX4aB1c"
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a time that is not RFC 3339, a page size below 1, or a page that starts before 0 |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "a time that is not RFC 3339, a page size below 1, or a page that starts before 0"
}
}
POST /api/v1/speedcheck
Start a speed check.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. Answers at once with the check in state running. It takes about two minutes, so read it again by its id to follow the progress and get the result. It writes test data to each location it times and slows live transfers while it runs. When transfers are running and confirmBusy is not true, nothing starts and the answer is 409; ask again with it set to go ahead. A check that is already running is returned instead of starting a second one.
Request body. JSON.
The body may be left out.
| Field | Type | Meaning | |
|---|---|---|---|
confirmBusy | boolean | optional | |
testSizeMb | integer | optional | Megabytes written to each location. Moved into the allowed range; see [store::MIN_TEST_MB]. |
bucketIds | array of string | optional | Buckets to include. Absent means leave every bucket out and do not change the saved choice. An empty list means include none, and is remembered. |
{
"confirmBusy": true,
"testSizeMb": 1,
"bucketIds": [
"k7Qm2sLp9vX4aB1c"
]
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
outcome | object | required | |||||||||||||||||||||||||||||
id | string | required | The id. | ||||||||||||||||||||||||||||
state | string | required | Where this record is in its life. | ||||||||||||||||||||||||||||
trigger | string | required | |||||||||||||||||||||||||||||
progressPercent | integer | required | |||||||||||||||||||||||||||||
progressStep | string | optional | |||||||||||||||||||||||||||||
error | string | optional | |||||||||||||||||||||||||||||
serverVersion | string | required | |||||||||||||||||||||||||||||
licensePlan | string | required | |||||||||||||||||||||||||||||
startedAt | string | required | A time, as RFC 3339. | ||||||||||||||||||||||||||||
finishedAt | string | optional | A time, as RFC 3339. | ||||||||||||||||||||||||||||
currentUdpAdvice | Advice | optional | The host's UDP buffer limit read for this request, labelled as the current setting. Kept apart from network and advice, which are what the check measured when it ran. | ||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||
{
"outcome": {},
"id": "k7Qm2sLp9vX4aB1c",
"state": "active",
"trigger": "example",
"progressPercent": 1,
"serverVersion": "example",
"licensePlan": "example",
"startedAt": "2026-10-05T18:00:00Z"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 409 | conflict | transfers are running and `confirmBusy` was not set |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "conflict",
"message": "transfers are running and `confirmBusy` was not set"
}
}
PUT /api/v1/speedcheck/schedule
Turn the weekly speed check on or off, or change when it runs.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when turning it on, when this server's license does not include automation.
Administrators only. Send only what changes. Turning the schedule on needs the automation feature. Turning it off, or changing the day, time or addresses of one that is already on, does not, so a license that lapses never traps an administrator with a schedule they cannot edit. addresses are who the result is emailed to, up to 10.
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | optional | |
weekday | integer | optional | Read as a number of any size so that 9 is refused in words rather than by the parser. |
minute | integer | optional | |
addresses | array of string | optional |
{
"enabled": true,
"weekday": 1,
"minute": 1,
"addresses": [
"example"
]
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required | |
weekday | integer | required | |
minute | integer | required | |
addresses | array of string | required | |
lastRunAt | string | optional | A time, as RFC 3339. |
included | boolean | required | Whether the license includes automation, which scheduled checks need and the check itself does not. |
{
"enabled": true,
"weekday": 1,
"minute": 1,
"addresses": [
"example"
],
"lastRunAt": "2026-10-05T18:00:00Z",
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a day outside 0 to 6, a time outside 0 to 1439, an address that is not one, or more than 10 addresses |
| 402 | licence_required | turning it on, when this server's license does not include automation |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "a day outside 0 to 6, a time outside 0 to 1439, an address that is not one, or more than 10 addresses"
}
}
GET /api/v1/speedcheck/{id}
One speed check, with its progress while it runs.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. While the check runs, progressPercent and progressStep say how far it has got. When it finishes, verdict says in one sentence what limits transfers on this server, and advice says what to do about it.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
outcome | object | required | |||||||||||||||||||||||||||||
id | string | required | The id. | ||||||||||||||||||||||||||||
state | string | required | Where this record is in its life. | ||||||||||||||||||||||||||||
trigger | string | required | |||||||||||||||||||||||||||||
progressPercent | integer | required | |||||||||||||||||||||||||||||
progressStep | string | optional | |||||||||||||||||||||||||||||
error | string | optional | |||||||||||||||||||||||||||||
serverVersion | string | required | |||||||||||||||||||||||||||||
licensePlan | string | required | |||||||||||||||||||||||||||||
startedAt | string | required | A time, as RFC 3339. | ||||||||||||||||||||||||||||
finishedAt | string | optional | A time, as RFC 3339. | ||||||||||||||||||||||||||||
currentUdpAdvice | Advice | optional | The host's UDP buffer limit read for this request, labelled as the current setting. Kept apart from network and advice, which are what the check measured when it ran. | ||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||
{
"outcome": {},
"id": "k7Qm2sLp9vX4aB1c",
"state": "active",
"trigger": "example",
"progressPercent": 1,
"serverVersion": "example",
"licensePlan": "example",
"startedAt": "2026-10-05T18:00:00Z"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 404 | not_found | no such check |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "not_found",
"message": "not found"
}
}
DELETE /api/v1/speedcheck/{id}
Delete a finished speed check.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. The row is removed. A check that is still running is refused, because it is still using the disks and it is what stops a second check from starting.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Success. 200.
The reply has no body.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 404 | not_found | no such check |
| 409 | conflict | the check is still running |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "not_found",
"message": "not found"
}
}
GET /api/v1/speedcheck/{id}/report.txt
A finished speed check as a text file.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. The same words the screen shows, to paste into a support email, sent as an attachment named for the day the check started. A check that failed is reported with the reason.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Success. 200.
The body is plain text, not JSON.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 404 | not_found | no such check |
| 409 | conflict | the check is still running; ask again when it has finished |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "not_found",
"message": "not found"
}
}
GET /api/v1/admin/email/templates
Every email this server sends: its group, whether it is a security email, whether it is switched on, and which languages it has been written in.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only, on every plan. Reading works without a license so the editor can show what a license would add; included says whether this server may change anything, and freeMode says whether custom wording is saved but not being sent.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
included | boolean | required | The licence includes branding, so writes are accepted. | ||||||||||||||||||||||||||||||||||||||||||||
freeMode | boolean | required | Something custom is stored and is switched off for want of a licence. | ||||||||||||||||||||||||||||||||||||||||||||
serverLanguage | string | required | |||||||||||||||||||||||||||||||||||||||||||||
languages | array of string | required | |||||||||||||||||||||||||||||||||||||||||||||
layout | object | required | |||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||
kinds | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||
{
"included": true,
"freeMode": true,
"serverLanguage": "example",
"languages": [
"example"
],
"layout": {
"custom": true,
"version": 1,
"hasDraft": true
},
"kinds": [
{
"id": "k7Qm2sLp9vX4aB1c",
"group": "example",
"title": "example",
"about": "example",
"security": true,
"canDisable": true,
"enabled": true,
"custom": true,
"hasDraft": true,
"languages": [
"example"
]
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/admin/email/templates/{kind}
One email in one language: its variables, its locked blocks, the built-in wording, the published version, the draft and the version history.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only, on every plan. lang picks the language and defaults to this server's. fallsBackTo names the language that is sent instead while this one has nothing of its own.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
lang | query | string | optional | The language, such as en or pt-BR. This server's language when it is left out. |
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
kind | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lang | string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fallsBackTo | string | optional | The language that is sent instead while this one has nothing of its own, or None when it is the built-in wording that is sent. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variables | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
requiredLocks | array of string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lockWording | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | Published | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"kind": {
"id": "k7Qm2sLp9vX4aB1c",
"group": "example",
"title": "example",
"about": "example",
"security": true,
"canDisable": true,
"enabled": true,
"custom": true,
"hasDraft": true,
"languages": [
"example"
]
},
"lang": "example",
"variables": [
{
"group": "example",
"vars": [
{
"id": "k7Qm2sLp9vX4aB1c",
"label": "example",
"about": "example",
"sample": "example",
"kind": {}
}
]
}
],
"requiredLocks": [
"example"
],
"lockWording": {},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 404 | not_found | no such email type |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "not_found",
"message": "not found"
}
}
PUT /api/v1/admin/email/templates/{kind}
Save the draft. Accepts wording with problems in it and hands the problems back, because the editor saves as you type.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Administrators only, and needs a license that includes branding. Publishing checks again and refuses; saving does not, so an administrator is never stopped mid-sentence.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
lang | query | string | optional | The language, such as en or pt-BR. This server's language when it is left out. |
Request body. JSON.
| Field | Type | Meaning | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
subject | array of Inline | required | |||||||||
previewText | array of Inline | required | |||||||||
plainText | string | optional | The administrator's own plain-text version, or empty to have one made from the document. | ||||||||
doc | object | required | |||||||||
It is an object:
| |||||||||||
{
"subject": [
{}
],
"previewText": [
{}
],
"plainText": "example",
"doc": {
"blocks": [
{}
]
}
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
draft | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
problems | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"draft": {
"draft": {
"body": {
"subject": [],
"previewText": [],
"plainText": "example",
"doc": {}
},
"updatedAt": "2026-10-05T18:00:00Z"
},
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
},
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the document is not a shape this server understands |
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such email type |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "the document is not a shape this server understands"
}
}
DELETE /api/v1/admin/email/templates/{kind}/draft
Throw the draft away and go back to the published wording.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
lang | query | string | optional | The language, such as en or pt-BR. This server's language when it is left out. |
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
kind | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lang | string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fallsBackTo | string | optional | The language that is sent instead while this one has nothing of its own, or None when it is the built-in wording that is sent. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variables | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
requiredLocks | array of string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lockWording | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | Published | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"kind": {
"id": "k7Qm2sLp9vX4aB1c",
"group": "example",
"title": "example",
"about": "example",
"security": true,
"canDisable": true,
"enabled": true,
"custom": true,
"hasDraft": true,
"languages": [
"example"
]
},
"lang": "example",
"variables": [
{
"group": "example",
"vars": [
{
"id": "k7Qm2sLp9vX4aB1c",
"label": "example",
"about": "example",
"sample": "example",
"kind": {}
}
]
}
],
"requiredLocks": [
"example"
],
"lockWording": {},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such email type |
| 409 | conflict | there is no draft |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "This server's license does not include email templates"
}
}
POST /api/v1/admin/email/templates/{kind}/publish
Make the draft live, as a new numbered version with an optional note.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Administrators only, and needs a license that includes branding. Checked again here: a security email that has lost one of its locked blocks, an unknown variable, or a password link or code in a subject line is refused, however the request was made.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
lang | query | string | optional | The language, such as en or pt-BR. This server's language when it is left out. |
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
note | string | optional |
{
"note": "The cut is in the folder."
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
kind | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lang | string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fallsBackTo | string | optional | The language that is sent instead while this one has nothing of its own, or None when it is the built-in wording that is sent. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variables | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
requiredLocks | array of string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lockWording | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | Published | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"kind": {
"id": "k7Qm2sLp9vX4aB1c",
"group": "example",
"title": "example",
"about": "example",
"security": true,
"canDisable": true,
"enabled": true,
"custom": true,
"hasDraft": true,
"languages": [
"example"
]
},
"lang": "example",
"variables": [
{
"group": "example",
"vars": [
{
"id": "k7Qm2sLp9vX4aB1c",
"label": "example",
"about": "example",
"sample": "example",
"kind": {}
}
]
}
],
"requiredLocks": [
"example"
],
"lockWording": {},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the wording has a problem that must be fixed first |
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such email type |
| 409 | conflict | there is no draft |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "the wording has a problem that must be fixed first"
}
}
POST /api/v1/admin/email/templates/{kind}/reset
Make a draft from the built-in wording. Nothing is lost until it is published.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
lang | query | string | optional | The language, such as en or pt-BR. This server's language when it is left out. |
Request body. None.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
kind | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lang | string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fallsBackTo | string | optional | The language that is sent instead while this one has nothing of its own, or None when it is the built-in wording that is sent. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variables | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
requiredLocks | array of string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lockWording | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | Published | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"kind": {
"id": "k7Qm2sLp9vX4aB1c",
"group": "example",
"title": "example",
"about": "example",
"security": true,
"canDisable": true,
"enabled": true,
"custom": true,
"hasDraft": true,
"languages": [
"example"
]
},
"lang": "example",
"variables": [
{
"group": "example",
"vars": [
{
"id": "k7Qm2sLp9vX4aB1c",
"label": "example",
"about": "example",
"sample": "example",
"kind": {}
}
]
}
],
"requiredLocks": [
"example"
],
"lockWording": {},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such email type |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "This server's license does not include email templates"
}
}
POST /api/v1/admin/email/templates/{kind}/restore
Make a draft from an earlier version. Body: {"version": 3}.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
lang | query | string | optional | The language, such as en or pt-BR. This server's language when it is left out. |
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
version | integer | required |
{
"version": 1
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
kind | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lang | string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fallsBackTo | string | optional | The language that is sent instead while this one has nothing of its own, or None when it is the built-in wording that is sent. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variables | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
requiredLocks | array of string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lockWording | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | Published | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"kind": {
"id": "k7Qm2sLp9vX4aB1c",
"group": "example",
"title": "example",
"about": "example",
"security": true,
"canDisable": true,
"enabled": true,
"custom": true,
"hasDraft": true,
"languages": [
"example"
]
},
"lang": "example",
"variables": [
{
"group": "example",
"vars": [
{
"id": "k7Qm2sLp9vX4aB1c",
"label": "example",
"about": "example",
"sample": "example",
"kind": {}
}
]
}
],
"requiredLocks": [
"example"
],
"lockWording": {},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such email type, or no such version |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "This server's license does not include email templates"
}
}
POST /api/v1/admin/email/templates/{kind}/languages
Add a language to this email, copying the wording of another to translate. Body: {"lang": "pt-BR", "copyFrom": "en"}.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
lang | string | required | |
copyFrom | string | optional | The language to copy from. Empty copies the built-in wording. |
{
"lang": "example",
"copyFrom": "example"
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
kind | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lang | string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fallsBackTo | string | optional | The language that is sent instead while this one has nothing of its own, or None when it is the built-in wording that is sent. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
variables | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
requiredLocks | array of string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lockWording | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | Published | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"kind": {
"id": "k7Qm2sLp9vX4aB1c",
"group": "example",
"title": "example",
"about": "example",
"security": true,
"canDisable": true,
"enabled": true,
"custom": true,
"hasDraft": true,
"languages": [
"example"
]
},
"lang": "example",
"variables": [
{
"group": "example",
"vars": [
{
"id": "k7Qm2sLp9vX4aB1c",
"label": "example",
"about": "example",
"sample": "example",
"kind": {}
}
]
}
],
"requiredLocks": [
"example"
],
"lockWording": {},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | not a language code such as en or pt-BR |
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such email type |
| 409 | conflict | that language is already there |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "not a language code such as en or pt-BR"
}
}
DELETE /api/v1/admin/email/templates/{kind}/languages/{lang}
Remove a language. The email then falls back to this server's language.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
lang | path | string | required | The language, such as pt-BR. |
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
id | string | required | The id. |
group | string | required | |
title | string | required | |
about | string | required | |
security | boolean | required | |
canDisable | boolean | required | |
enabled | boolean | required | |
custom | boolean | required | |
hasDraft | boolean | required | |
languages | array of string | required |
{
"id": "k7Qm2sLp9vX4aB1c",
"group": "example",
"title": "example",
"about": "example",
"security": true,
"canDisable": true,
"enabled": true,
"custom": true,
"hasDraft": true,
"languages": [
"example"
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such email type, or no such language |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "This server's license does not include email templates"
}
}
POST /api/v1/admin/email/templates/{kind}/enabled
Turn one email on or off. Body: {"enabled": false}.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Administrators only, and needs a license that includes branding. Security emails cannot be switched off; the notice about a new sign-in is the one exception, and it starts off.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required |
{
"enabled": true
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
id | string | required | The id. |
group | string | required | |
title | string | required | |
about | string | required | |
security | boolean | required | |
canDisable | boolean | required | |
enabled | boolean | required | |
custom | boolean | required | |
hasDraft | boolean | required | |
languages | array of string | required |
{
"id": "k7Qm2sLp9vX4aB1c",
"group": "example",
"title": "example",
"about": "example",
"security": true,
"canDisable": true,
"enabled": true,
"custom": true,
"hasDraft": true,
"languages": [
"example"
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | this email cannot be switched off |
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such email type |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "this email cannot be switched off"
}
}
POST /api/v1/admin/email/templates/{kind}/preview
Draw wording that has not been saved, with sample data, and get back the HTML, the plain text, the subject line and the preview line.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only, on every plan. data chooses the sample set: sample, long (long names and messages), empty (every optional field blank) or real (a recent package or user, named by realId). A preview never fails on a bad variable: it is drawn as the text that was typed and listed under problems.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
lang | query | string | optional | The language, such as en or pt-BR. This server's language when it is left out. |
Request body. JSON.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
body | object | required | |||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||
data | string | optional | sample, long, empty or real. | ||||||||||||||||||||||||||||||||
realId | string | optional | The package or user to fill real from. | ||||||||||||||||||||||||||||||||
{
"body": {
"subject": [
{}
],
"previewText": [
{}
],
"plainText": "example",
"doc": {
"blocks": [
{}
]
}
},
"data": "example",
"realId": "k7Qm2sLp9vX4aB1c"
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
subject | string | required | |||||||||||||||||||||
previewText | string | required | |||||||||||||||||||||
html | string | required | |||||||||||||||||||||
text | string | required | |||||||||||||||||||||
problems | array of objects | required | |||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
{
"subject": "Files for Monday",
"previewText": "example",
"html": "example",
"text": "example",
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the document is not a shape this server understands |
| 404 | not_found | no such email type |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the document is not a shape this server understands"
}
}
POST /api/v1/admin/email/templates/{kind}/test
Send this email to the signed-in administrator's own address, with both HTML and plain text.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Administrators only, and needs a license that includes branding. The address comes from the account, never from the request, so the button cannot be aimed at somebody else. A relay that refuses it is reported in its own words.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
kind | path | string | required | The email type, such as package.received. |
lang | query | string | optional | The language, such as en or pt-BR. This server's language when it is left out. |
Request body. JSON.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
body | object | required | |||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||
data | string | optional | sample, long, empty or real. | ||||||||||||||||||||||||||||||||
realId | string | optional | The package or user to fill real from. | ||||||||||||||||||||||||||||||||
{
"body": {
"subject": [
{}
],
"previewText": [
{}
],
"plainText": "example",
"doc": {
"blocks": [
{}
]
}
},
"data": "example",
"realId": "k7Qm2sLp9vX4aB1c"
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
sent | boolean | required | |
to | string | required |
{
"sent": true,
"to": "projects/rush"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the wording has a problem, or the mail relay refused it |
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such email type |
| 503 | unavailable | this server is not set up to send email |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "the wording has a problem, or the mail relay refused it"
}
}
GET /api/v1/admin/email/samples
A short list of recent packages and users, to preview an email against something that really happened.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
packages | array of objects | required | |||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||
users | array of objects | required | |||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||
{
"packages": [
{
"id": "k7Qm2sLp9vX4aB1c",
"label": "example",
"subject": "Files for Monday",
"sender": "example",
"fileCount": 1,
"totalSize": 1,
"createdAt": "2026-10-05T18:00:00Z"
}
],
"users": [
{
"id": "k7Qm2sLp9vX4aB1c",
"label": "example",
"name": "Rush delivery",
"email": "[email protected]"
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/admin/email/layout
The frame every email is drawn in: logo, brand and button colours, header and footer, and whether the server's web address is shown.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
included | boolean | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | PublishedLayout | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftLayoutView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
contrast | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"included": true,
"default": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"published": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
},
"draft": {
"draft": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [],
"footerText": [],
"showServerUrl": true
},
"updatedAt": "2026-10-05T18:00:00Z"
},
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
],
"contrast": {
"button": 1
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/email/layout
Save the layout draft.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
logo | string | required | Empty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout]. |
logoAlt | string | required | |
brandColour | string | required | #rrggbb. Used for links and for the server's name when there is no logo. |
buttonColour | string | required | #rrggbb. The button's background. |
headerText | array of Inline | required | |
footerText | array of Inline | required | |
showServerUrl | boolean | required |
{
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
draft | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
problems | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"draft": {
"draft": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [],
"footerText": [],
"showServerUrl": true
},
"updatedAt": "2026-10-05T18:00:00Z"
},
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
},
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a colour that is not #rrggbb, a logo that is not a PNG or an SVG, or one over 256 KiB |
| 402 | licence_required | this server's license does not include email templates |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "a colour that is not #rrggbb, a logo that is not a PNG or an SVG, or one over 256 KiB"
}
}
DELETE /api/v1/admin/email/layout/draft
Throw the layout draft away.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
included | boolean | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | PublishedLayout | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftLayoutView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
contrast | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"included": true,
"default": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"published": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
},
"draft": {
"draft": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [],
"footerText": [],
"showServerUrl": true
},
"updatedAt": "2026-10-05T18:00:00Z"
},
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
],
"contrast": {
"button": 1
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | this server's license does not include email templates |
| 409 | conflict | there is no draft |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "This server's license does not include email templates"
}
}
POST /api/v1/admin/email/layout/publish
Make the layout draft live, as a new numbered version.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
note | string | optional |
{
"note": "The cut is in the folder."
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
included | boolean | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | PublishedLayout | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftLayoutView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
contrast | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"included": true,
"default": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"published": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
},
"draft": {
"draft": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [],
"footerText": [],
"showServerUrl": true
},
"updatedAt": "2026-10-05T18:00:00Z"
},
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
],
"contrast": {
"button": 1
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the layout has a problem that must be fixed first |
| 402 | licence_required | this server's license does not include email templates |
| 409 | conflict | there is no draft |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "the layout has a problem that must be fixed first"
}
}
POST /api/v1/admin/email/layout/reset
Make a layout draft from the built-in frame.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Request body. None.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
included | boolean | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | PublishedLayout | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftLayoutView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
contrast | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"included": true,
"default": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"published": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
},
"draft": {
"draft": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [],
"footerText": [],
"showServerUrl": true
},
"updatedAt": "2026-10-05T18:00:00Z"
},
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
],
"contrast": {
"button": 1
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | this server's license does not include email templates |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "This server's license does not include email templates"
}
}
POST /api/v1/admin/email/layout/restore
Make a layout draft from an earlier version. Body: {"version": 2}.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
version | integer | required |
{
"version": 1
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
included | boolean | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
default | object | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
published | PublishedLayout | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
draft | DraftLayoutView | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
versions | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
contrast | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"included": true,
"default": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"published": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
},
"draft": {
"draft": {
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [],
"footerText": [],
"showServerUrl": true
},
"updatedAt": "2026-10-05T18:00:00Z"
},
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
},
"versions": [
{
"version": 1,
"at": "2026-10-05T18:00:00Z",
"by": "example",
"note": "The cut is in the folder."
}
],
"contrast": {
"button": 1
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | this server's license does not include email templates |
| 404 | not_found | no such version |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "This server's license does not include email templates"
}
}
POST /api/v1/admin/email/layout/preview
Draw a layout that has not been saved around a sample email. Body: the layout, and kind for which email to show inside it.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
layout | object | required | |||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||
kind | string | optional | The email to draw it around. package.received when left out. | ||||||||||||||||||||||||||||||||
data | string | optional | |||||||||||||||||||||||||||||||||
{
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"kind": "shared",
"data": "example"
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
subject | string | required | |||||||||||||||||||||
previewText | string | required | |||||||||||||||||||||
html | string | required | |||||||||||||||||||||
text | string | required | |||||||||||||||||||||
problems | array of objects | required | |||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
{
"subject": "Files for Monday",
"previewText": "example",
"html": "example",
"text": "example",
"problems": [
{
"where": {},
"blockId": "k7Qm2sLp9vX4aB1c",
"message": "The cut is in the folder.",
"suggestion": "example"
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the layout is not a shape this server understands |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the layout is not a shape this server understands"
}
}
GET /api/v1/admin/email/export
Every custom email and the layout as one JSON file, to copy to another server or to a standby.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
version | integer | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||
templates | array of objects | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||
layout | Layout | optional | None when the layout is the built-in one. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||
enabled | object | optional | The switches an administrator has set, by email id. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"version": 1,
"templates": [
{
"kind": {},
"lang": "example",
"body": {
"subject": [
{}
],
"previewText": [
{}
],
"plainText": "example",
"doc": {
"blocks": []
}
}
}
],
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"enabled": {}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/email/import
Take a file made by the export above. Wording that does not pass the checks is refused and nothing is changed.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include email templates.
Request body. JSON.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
version | integer | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||
templates | array of objects | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||
layout | Layout | optional | None when the layout is the built-in one. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||
enabled | object | optional | The switches an administrator has set, by email id. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"version": 1,
"templates": [
{
"kind": {},
"lang": "example",
"body": {
"subject": [
{}
],
"previewText": [
{}
],
"plainText": "example",
"doc": {
"blocks": []
}
}
}
],
"layout": {
"logo": "example",
"logoAlt": "example",
"brandColour": "example",
"buttonColour": "example",
"headerText": [
{}
],
"footerText": [
{}
],
"showServerUrl": true
},
"enabled": {}
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
templates | array of string | required | kind/lang of every template that is now a new version. |
removed | array of string | required | kind/lang of every template that was here and is not in the file, and so was removed. |
unchanged | integer | required | Templates in the file that were already live, word for word. |
layout | string | required | replaced, removed or unchanged. |
switchesChanged | integer | required | How many on/off switches are different now. |
{
"templates": [
"example"
],
"removed": [
"example"
],
"unchanged": 1,
"layout": "example",
"switchesChanged": 1
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the file is not one of ours, or something in it would not publish |
| 402 | licence_required | this server's license does not include email templates |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "the file is not one of ours, or something in it would not publish"
}
}
The server
GET /api/v1/admin/network
The address and ports in use, the ones the next start will use, which are fixed by the environment, and the firewall rules that matter.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
hostname | string | required | What the next start will use: the config file, then the environment. | ||||||||||||||||||||
dataPort | integer | required | |||||||||||||||||||||
webPort | integer | required | |||||||||||||||||||||
running | object | required | What this process is using now. | ||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||
pending | array of string | required | What a restart would change, one line each. | ||||||||||||||||||||
pinned | object | required | The environment variable that fixes each setting, where one does. The portal shows those read-only, since a value saved here would be overridden at the next start. | ||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||
lowPorts | object | required | Whether this server may open ports below 1024. | ||||||||||||||||||||
problems | array of objects | required | What would go wrong opening the saved ports, found by trying them. | ||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
notes | array of string | required | What start-up had to work around, such as a port it could not open. | ||||||||||||||||||||
rules | array of objects | required | What has to be open, and what people cannot do if it is not. For the saved ports, since those are the ones to open before a restart. | ||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
dataServiceRunning | boolean | required | Whether the data service is actually listening. | ||||||||||||||||||||
checkedFromOutside | boolean | required | Always false here: reachability from the internet is only known by asking something outside, which is [probe_network]. | ||||||||||||||||||||
{
"hostname": "example",
"dataPort": 443,
"webPort": 443,
"running": {
"hostname": "example",
"webPort": 443,
"dataPort": 443
},
"pending": [
"example"
],
"pinned": {
"hostname": "example",
"webPort": "example",
"dataPort": "example"
},
"lowPorts": {},
"problems": [
{
"blocking": true,
"text": "example"
}
],
"notes": [
"The cut is in the folder."
],
"rules": [
{
"port": 443,
"protocol": "example",
"what": "2026-10-05T18:00:00Z",
"costIfClosed": "example"
}
],
"dataServiceRunning": true,
"checkedFromOutside": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/network
Set the address, the portal port (TCP) and the transfer port (UDP, with TCP on the same number as a fallback). Takes effect on the next restart.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
hostname | string | optional | The name people will reach this server by. Empty clears it; absent leaves it as it is. |
webPort | integer | optional | TCP, for the portal and the API. Read as a wide number so a value out of range is answered in a sentence rather than a parse error. |
dataPort | integer | optional | UDP for transfers, and TCP on the same number for the fallback. |
{
"hostname": "example",
"webPort": 443,
"dataPort": 443
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
hostname | string | required | What the next start will use: the config file, then the environment. | ||||||||||||||||||||
dataPort | integer | required | |||||||||||||||||||||
webPort | integer | required | |||||||||||||||||||||
running | object | required | What this process is using now. | ||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||
pending | array of string | required | What a restart would change, one line each. | ||||||||||||||||||||
pinned | object | required | The environment variable that fixes each setting, where one does. The portal shows those read-only, since a value saved here would be overridden at the next start. | ||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||
lowPorts | object | required | Whether this server may open ports below 1024. | ||||||||||||||||||||
problems | array of objects | required | What would go wrong opening the saved ports, found by trying them. | ||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
notes | array of string | required | What start-up had to work around, such as a port it could not open. | ||||||||||||||||||||
rules | array of objects | required | What has to be open, and what people cannot do if it is not. For the saved ports, since those are the ones to open before a restart. | ||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
dataServiceRunning | boolean | required | Whether the data service is actually listening. | ||||||||||||||||||||
checkedFromOutside | boolean | required | Always false here: reachability from the internet is only known by asking something outside, which is [probe_network]. | ||||||||||||||||||||
{
"hostname": "example",
"dataPort": 443,
"webPort": 443,
"running": {
"hostname": "example",
"webPort": 443,
"dataPort": 443
},
"pending": [
"example"
],
"pinned": {
"hostname": "example",
"webPort": "example",
"dataPort": "example"
},
"lowPorts": {},
"problems": [
{
"blocking": true,
"text": "example"
}
],
"notes": [
"The cut is in the folder."
],
"rules": [
{
"port": 443,
"protocol": "example",
"what": "2026-10-05T18:00:00Z",
"costIfClosed": "example"
}
],
"dataServiceRunning": true,
"checkedFromOutside": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | not a host name, a port out of range, a port fixed by the environment, or a port this server cannot open |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "not a host name, a port out of range, a port fixed by the environment, or a port this server cannot open"
}
}
POST /api/v1/admin/network/probe
Ask farwing.io to try this server's ports from the internet.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Sends the address, the port numbers and a one-time ticket for a 256 KiB file of random bytes; farwing.io connects only to the address the request comes from. TCP is tried with a connection. UDP is tried by downloading the test file over UDP only, without the TCP fallback, and reports open, timeout (nothing arrived), oneway (it arrived but the replies did not get back) or untested.
Request body. None.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
checkedFromOutside | boolean | required | Whether farwing.io actually ran the check. | ||||||||||||||||||||||||||||||||
skippedReason | string | optional, left out when empty | |||||||||||||||||||||||||||||||||
address | string | optional, left out when empty | The address farwing.io connected to: this server's outgoing address. | ||||||||||||||||||||||||||||||||
host | string | required | |||||||||||||||||||||||||||||||||
hostMatches | boolean | optional, left out when empty | Whether the host name points at the address that was tried. None when the check did not run. | ||||||||||||||||||||||||||||||||
hostAddresses | array of string | optional | |||||||||||||||||||||||||||||||||
results | array of objects | required | |||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||
{
"checkedFromOutside": true,
"skippedReason": "example",
"address": "example",
"host": "files.example.com",
"hostMatches": true,
"hostAddresses": [
"example"
],
"results": [
{
"port": 443,
"protocol": "example",
"open": true,
"state": "active",
"detail": "example",
"bytes": 1048576,
"ms": 1
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a check ran a moment ago |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "a check ran a moment ago"
}
}
POST /api/v1/admin/network/udp-test
Make a one-time UDP test to run from any computer: a ticket for a 256 KiB file of random bytes that works once, for one minute, over UDP only.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Returns the ticket and, when the server has a hostname, the farwing cp command that fetches it.
Request body. None.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
id | string | required | The id. |
ticket | string | required | For farwing cp --ticket. Names no TCP port, and works once. |
command | string | optional, left out when empty | The whole command, when the server knows the name it is reached by. Without one the client needs --ticket-host as well. |
fileName | string | required | The file's name. |
bytes | integer | required | Size in bytes. |
port | integer | required | |
expiresAt | string | required | A time, as RFC 3339. |
{
"id": "k7Qm2sLp9vX4aB1c",
"ticket": "packed-ticket-string",
"command": "example",
"fileName": "example",
"bytes": 1048576,
"port": 443,
"expiresAt": "2026-10-05T18:00:00Z"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 409 | conflict | four tests are already waiting |
| 503 | unavailable | the transfer port is not open |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "conflict",
"message": "four tests are already waiting"
}
}
GET /api/v1/admin/network/udp-test/{id}
What a UDP test has come to: waiting, receiving, passed (with bytes, seconds and the address it came from), failed or expired.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
id | string | required | The id. |
state | string | required | waiting, receiving, passed, failed or expired. |
bytes | integer | optional, left out when empty | Size in bytes. |
seconds | number | optional, left out when empty | |
from | string | optional, left out when empty | The address the download came from. |
detail | string | optional, left out when empty | |
expiresAt | string | required | A time, as RFC 3339. |
{
"id": "k7Qm2sLp9vX4aB1c",
"state": "active",
"bytes": 1048576,
"seconds": 1,
"from": "projects/rush",
"detail": "example",
"expiresAt": "2026-10-05T18:00:00Z"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 404 | not_found | no such test, or it is more than ten minutes old |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "not_found",
"message": "not found"
}
}
GET /api/v1/admin/certificate
The portal's certificate: where it came from (self-signed, lets-encrypt, uploaded or config-file), issuer, names, validity and days left, whether it covers the configured address, automatic renewal and its next attempt, any warning, and the progress of a Let's Encrypt request.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Never the private key.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
source | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
trusted | boolean | required | Issued by somebody browsers already trust. | ||||||||||||||||||||||||||||||||||||||||
certificate | Details | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
daysLeft | integer | optional | |||||||||||||||||||||||||||||||||||||||||
lifetimeDays | integer | optional | |||||||||||||||||||||||||||||||||||||||||
hostname | string | required | The address the next start uses. | ||||||||||||||||||||||||||||||||||||||||
coversHostname | boolean | required | |||||||||||||||||||||||||||||||||||||||||
autoRenew | boolean | required | |||||||||||||||||||||||||||||||||||||||||
renewal | Renewal | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
warning | Warning | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
progress | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
acme | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
webPort | integer | required | |||||||||||||||||||||||||||||||||||||||||
notes | array of string | required | |||||||||||||||||||||||||||||||||||||||||
bootFingerprint | string | required | SHA-256 of the certificate this process loaded when it started, or of the latest renewal. After a new certificate is installed it differs from certificate.fingerprintSha256 until the process starts again. A renewal does not make it differ. | ||||||||||||||||||||||||||||||||||||||||
{
"source": {
"root": "k7Qm2sLp9vX4aB1c",
"path": "projects/rush",
"rel": {}
},
"trusted": true,
"hostname": "example",
"coversHostname": true,
"autoRenew": true,
"progress": {
"stage": {},
"renewal": true,
"error": {
"message": "The cut is in the folder.",
"detail": "example"
},
"at": "2026-10-05T18:00:00Z"
},
"acme": {
"email": "[email protected]",
"termsUrl": "example",
"listener": {},
"plan": [
{}
]
},
"webPort": 443,
"notes": [
"The cut is in the folder."
],
"bootFingerprint": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
DELETE /api/v1/admin/certificate
Go back to the certificate this server makes for itself.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Browsers then warn before opening the portal. Uploaded and Let's Encrypt pairs are deleted; the Let's Encrypt account is kept. Takes effect on the next connection.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
source | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
trusted | boolean | required | Issued by somebody browsers already trust. | ||||||||||||||||||||||||||||||||||||||||
certificate | Details | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
daysLeft | integer | optional | |||||||||||||||||||||||||||||||||||||||||
lifetimeDays | integer | optional | |||||||||||||||||||||||||||||||||||||||||
hostname | string | required | The address the next start uses. | ||||||||||||||||||||||||||||||||||||||||
coversHostname | boolean | required | |||||||||||||||||||||||||||||||||||||||||
autoRenew | boolean | required | |||||||||||||||||||||||||||||||||||||||||
renewal | Renewal | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
warning | Warning | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
progress | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
acme | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
webPort | integer | required | |||||||||||||||||||||||||||||||||||||||||
notes | array of string | required | |||||||||||||||||||||||||||||||||||||||||
bootFingerprint | string | required | SHA-256 of the certificate this process loaded when it started, or of the latest renewal. After a new certificate is installed it differs from certificate.fingerprintSha256 until the process starts again. A renewal does not make it differ. | ||||||||||||||||||||||||||||||||||||||||
{
"source": {
"root": "k7Qm2sLp9vX4aB1c",
"path": "projects/rush",
"rel": {}
},
"trusted": true,
"hostname": "example",
"coversHostname": true,
"autoRenew": true,
"progress": {
"stage": {},
"renewal": true,
"error": {
"message": "The cut is in the folder.",
"detail": "example"
},
"at": "2026-10-05T18:00:00Z"
},
"acme": {
"email": "[email protected]",
"termsUrl": "example",
"listener": {},
"plan": [
{}
]
},
"webPort": 443,
"notes": [
"The cut is in the folder."
],
"bootFingerprint": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 409 | conflict | a certificate is being requested; wait for it to finish |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "conflict",
"message": "a certificate is being requested; wait for it to finish"
}
}
POST /api/v1/admin/certificate/acme/eligibility
Whether Let's Encrypt can issue a certificate for this server: the address is a public name, it resolves (looked up from here) to the address farwing.io sees this server at, and port 443 or port 80 answers from the internet.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Opens port 80 if it can. Returns a checklist with a fix for each item that fails, and which check Let's Encrypt would use.
Request body. None.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
eligible | boolean | required | Nothing on the list is red. | ||||||||||||||||||||
hostname | string | required | |||||||||||||||||||||
publicAddress | string | optional | The address this server reaches the internet from, as farwing.io saw it. | ||||||||||||||||||||
dnsAddresses | array of string | required | |||||||||||||||||||||
challenge | Challenge | optional | How Let's Encrypt will check, if it can. | ||||||||||||||||||||
items | array of objects | required | The records in this reply. | ||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
checkedFromOutside | boolean | required | |||||||||||||||||||||
{
"eligible": true,
"hostname": "example",
"publicAddress": "example",
"dnsAddresses": [
"example"
],
"challenge": {},
"items": [
{
"id": "k7Qm2sLp9vX4aB1c",
"state": {},
"title": "example",
"detail": "example"
}
],
"checkedFromOutside": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a check ran a moment ago |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "a check ran a moment ago"
}
}
POST /api/v1/admin/certificate/acme/issue
Ask Let's Encrypt for a certificate for the configured address, in the background; follow it with GET /api/v1/admin/certificate.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Uses TLS on port 443 when the portal is on 443, otherwise HTTP on port 80. Saved at once; connections already open keep the previous certificate until the server restarts. Answers 202. Body: {"email": "", "agreeTerms": true}.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
email | string | optional | Where Let's Encrypt sends notices. Empty for none. |
agreeTerms | boolean | required |
{
"email": "[email protected]",
"agreeTerms": true
}
Success. 202.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
source | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
trusted | boolean | required | Issued by somebody browsers already trust. | ||||||||||||||||||||||||||||||||||||||||
certificate | Details | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
daysLeft | integer | optional | |||||||||||||||||||||||||||||||||||||||||
lifetimeDays | integer | optional | |||||||||||||||||||||||||||||||||||||||||
hostname | string | required | The address the next start uses. | ||||||||||||||||||||||||||||||||||||||||
coversHostname | boolean | required | |||||||||||||||||||||||||||||||||||||||||
autoRenew | boolean | required | |||||||||||||||||||||||||||||||||||||||||
renewal | Renewal | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
warning | Warning | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
progress | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
acme | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
webPort | integer | required | |||||||||||||||||||||||||||||||||||||||||
notes | array of string | required | |||||||||||||||||||||||||||||||||||||||||
bootFingerprint | string | required | SHA-256 of the certificate this process loaded when it started, or of the latest renewal. After a new certificate is installed it differs from certificate.fingerprintSha256 until the process starts again. A renewal does not make it differ. | ||||||||||||||||||||||||||||||||||||||||
{
"source": {
"root": "k7Qm2sLp9vX4aB1c",
"path": "projects/rush",
"rel": {}
},
"trusted": true,
"hostname": "example",
"coversHostname": true,
"autoRenew": true,
"progress": {
"stage": {},
"renewal": true,
"error": {
"message": "The cut is in the folder.",
"detail": "example"
},
"at": "2026-10-05T18:00:00Z"
},
"acme": {
"email": "[email protected]",
"termsUrl": "example",
"listener": {},
"plan": [
{}
]
},
"webPort": 443,
"notes": [
"The cut is in the folder."
],
"bootFingerprint": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the terms were not accepted, the email is not an address, or the address is not a public name |
| 409 | conflict | a request is already running |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the terms were not accepted, the email is not an address, or the address is not a public name"
}
}
POST /api/v1/admin/certificate/renew
Renew the Let's Encrypt certificate now, in the background. Answers 202.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. None.
Success. 202.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
source | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
trusted | boolean | required | Issued by somebody browsers already trust. | ||||||||||||||||||||||||||||||||||||||||
certificate | Details | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
daysLeft | integer | optional | |||||||||||||||||||||||||||||||||||||||||
lifetimeDays | integer | optional | |||||||||||||||||||||||||||||||||||||||||
hostname | string | required | The address the next start uses. | ||||||||||||||||||||||||||||||||||||||||
coversHostname | boolean | required | |||||||||||||||||||||||||||||||||||||||||
autoRenew | boolean | required | |||||||||||||||||||||||||||||||||||||||||
renewal | Renewal | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
warning | Warning | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
progress | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
acme | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
webPort | integer | required | |||||||||||||||||||||||||||||||||||||||||
notes | array of string | required | |||||||||||||||||||||||||||||||||||||||||
bootFingerprint | string | required | SHA-256 of the certificate this process loaded when it started, or of the latest renewal. After a new certificate is installed it differs from certificate.fingerprintSha256 until the process starts again. A renewal does not make it differ. | ||||||||||||||||||||||||||||||||||||||||
{
"source": {
"root": "k7Qm2sLp9vX4aB1c",
"path": "projects/rush",
"rel": {}
},
"trusted": true,
"hostname": "example",
"coversHostname": true,
"autoRenew": true,
"progress": {
"stage": {},
"renewal": true,
"error": {
"message": "The cut is in the folder.",
"detail": "example"
},
"at": "2026-10-05T18:00:00Z"
},
"acme": {
"email": "[email protected]",
"termsUrl": "example",
"listener": {},
"plan": [
{}
]
},
"webPort": 443,
"notes": [
"The cut is in the folder."
],
"bootFingerprint": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the certificate in use is not from Let's Encrypt |
| 409 | conflict | a request is already running |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the certificate in use is not from Let's Encrypt"
}
}
PUT /api/v1/admin/certificate/auto-renew
Turn automatic renewal on or off. When on, the certificate is renewed when a third of its lifetime is left (at most 30 days before it expires), retrying with backoff after a failure. Body: {"enabled": true}.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required |
{
"enabled": true
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
source | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
trusted | boolean | required | Issued by somebody browsers already trust. | ||||||||||||||||||||||||||||||||||||||||
certificate | Details | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
daysLeft | integer | optional | |||||||||||||||||||||||||||||||||||||||||
lifetimeDays | integer | optional | |||||||||||||||||||||||||||||||||||||||||
hostname | string | required | The address the next start uses. | ||||||||||||||||||||||||||||||||||||||||
coversHostname | boolean | required | |||||||||||||||||||||||||||||||||||||||||
autoRenew | boolean | required | |||||||||||||||||||||||||||||||||||||||||
renewal | Renewal | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
warning | Warning | optional | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
progress | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
acme | object | required | |||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||
webPort | integer | required | |||||||||||||||||||||||||||||||||||||||||
notes | array of string | required | |||||||||||||||||||||||||||||||||||||||||
bootFingerprint | string | required | SHA-256 of the certificate this process loaded when it started, or of the latest renewal. After a new certificate is installed it differs from certificate.fingerprintSha256 until the process starts again. A renewal does not make it differ. | ||||||||||||||||||||||||||||||||||||||||
{
"source": {
"root": "k7Qm2sLp9vX4aB1c",
"path": "projects/rush",
"rel": {}
},
"trusted": true,
"hostname": "example",
"coversHostname": true,
"autoRenew": true,
"progress": {
"stage": {},
"renewal": true,
"error": {
"message": "The cut is in the folder.",
"detail": "example"
},
"at": "2026-10-05T18:00:00Z"
},
"acme": {
"email": "[email protected]",
"termsUrl": "example",
"listener": {},
"plan": [
{}
]
},
"webPort": 443,
"notes": [
"The cut is in the folder."
],
"bootFingerprint": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/certificate/upload
Check, and unless dryRun install, a certificate chain and private key in PEM form (RSA, ECDSA or Ed25519).
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Refused if the key does not match, the chain is out of order, or the certificate has expired or is not yet valid; a name that does not cover the configured address is a warning. The certificate in use is kept when the new one is refused. Answers {installed, inspection: {errors, warnings, details}, status}. Body: {"certificate": "(PEM certificate chain)", "privateKey": "(PEM private key)", "dryRun": false}.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
certificate | string | required | PEM: the server's certificate, then any intermediates. Any order. |
privateKey | string | required | PEM, without a password. |
dryRun | boolean | optional | Check only; install nothing. |
{
"certificate": "example",
"privateKey": "example",
"dryRun": false
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
installed | boolean | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
inspection | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
status | Status | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"installed": true,
"inspection": {
"details": {
"subject": "Files for Monday",
"names": [
"Rush delivery"
],
"issuer": "example",
"notBefore": "example",
"notAfter": "example",
"selfSigned": true,
"chainLength": 1,
"keyType": "example",
"fingerprintSha256": "example"
},
"errors": [
"example"
],
"warnings": [
"example"
]
},
"status": {
"source": {
"root": "k7Qm2sLp9vX4aB1c",
"path": "projects/rush",
"rel": {}
},
"trusted": true,
"hostname": "example",
"coversHostname": true,
"autoRenew": true,
"progress": {
"stage": {},
"renewal": true,
"error": {
"message": "The cut is in the folder.",
"detail": "example"
},
"at": "2026-10-05T18:00:00Z"
},
"acme": {
"email": "[email protected]",
"termsUrl": "example",
"listener": {},
"plan": [
{}
]
},
"webPort": 443,
"notes": [
"The cut is in the folder."
],
"bootFingerprint": "example"
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the request is not valid |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the request is not valid"
}
}
GET /api/v1/admin/server
Restart and maintenance status: how the process comes back, what a restart would change, and anything that would stop it coming back.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
bootId | string | required | The id. | ||||||||||||
restartMode | object | required | How the process comes back: exit for a supervisor to restart it, exec to replace itself in place. | ||||||||||||
restarting | boolean | required | |||||||||||||
maintenance | Maintenance | optional | |||||||||||||
It is an object:
| |||||||||||||||
runningTransfers | integer | required | |||||||||||||
pending | array of string | required | What a restart would change, one line each. | ||||||||||||
problems | array of objects | required | What would go wrong on the way back up, found by trying. | ||||||||||||
Each item is an object:
| |||||||||||||||
notes | array of string | required | What this start had to work around. | ||||||||||||
{
"bootId": "k7Qm2sLp9vX4aB1c",
"restartMode": {},
"restarting": true,
"maintenance": {
"since": "example",
"message": "The cut is in the folder."
},
"runningTransfers": 1,
"pending": [
"example"
],
"problems": [
{
"blocking": true,
"text": "example"
}
],
"notes": [
"The cut is in the folder."
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/server/restart
Restart the server. Running transfers are paused and kept; the answer comes before the process stops. Body: {"force": false}.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
force | boolean | optional | Restart even though a check found something that would stop part of the server coming back. The portal offers this only after showing what the check found. |
{
"force": true
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
bootId | string | required | The process being stopped. The portal waits for a different one. |
restartMode | object | required |
{
"bootId": "k7Qm2sLp9vX4aB1c",
"restartMode": {}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 409 | conflict | a restart is already under way, or the next start would fail; send force to restart anyway |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "conflict",
"message": "a restart is already under way, or the next start would fail; send force to restart anyway"
}
}
PUT /api/v1/admin/maintenance
Turn maintenance on or off.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
While on, new uploads, transfers and packages are paused for everyone, including administrators. Only administrators can sign in. Transfers already running carry on. Hot folders, sync jobs, and event rules that send to another server or send a package wait, and start once maintenance is off. Body: {"on": true, "message": "", "pauseRunning": false}.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
on | boolean | required | |
message | string | optional | A note for the banner. Ignored when turning maintenance off. |
pauseRunning | boolean | optional | Pause transfers already running, rather than letting them finish. |
{
"on": true,
"message": "The cut is in the folder.",
"pauseRunning": true
}
Success. 200.
| Field | Type | Meaning | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
maintenance | Maintenance | optional | |||||||||||||
It is an object:
| |||||||||||||||
paused | integer | required | Transfers paused by this change. | ||||||||||||
{
"maintenance": {
"since": "example",
"message": "The cut is in the folder."
},
"paused": 1
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the note is too long |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the note is too long"
}
}
Storage
GET /api/v1/admin/roots
Every storage location, and whether it can be reached.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
The body is an array.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | string | required | The id. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name | string | required | The name. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
kind | string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path | string | required | A path relative to the space or the storage location, with / between folders and no leading slash. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
createdAt | string | required | A time, as RFC 3339. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
grants | integer | required | How many grants point at it, so the delete dialog can say what it is about to take away instead of asking for a blind confirmation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
reachable | boolean | required | Absent when the back end cannot be reached, which is the state an admin most needs to see: an unmounted volume looks exactly like an empty folder until something says otherwise. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bucket | View | optional, left out when empty | A bucket's settings, for the edit form. Never its secret: only whether one is stored. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[
{
"id": "k7Qm2sLp9vX4aB1c",
"name": "Rush delivery",
"kind": "shared",
"path": "projects/rush",
"createdAt": "2026-10-05T18:00:00Z",
"grants": 1,
"reachable": true,
"bucket": {
"provider": "example",
"bucket": "example",
"region": "example",
"prefix": "example",
"addressing": {},
"readOnly": true,
"credentials": {},
"hasSecret": true,
"performance": {
"partSizeMb": 1,
"partsPerFile": 1,
"rangesPerFile": 1,
"smallFiles": 1,
"checksum": {}
}
}
}
]
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/roots
Add one: a folder that already exists, or a bucket on Amazon S3 or an S3-compatible service.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
A bucket is checked before it is saved. Body for a bucket: {"name", "bucket": {"provider", "bucket", "region", "endpoint", "prefix", "addressing", "readOnly", "credentials": "keys" | "ambient", "accessKeyId", "secretAccessKey"}}. The secret is stored encrypted and never returned.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
name | string | required | The name. |
path | string | optional | For a local root. Ignored for a bucket. |
bucket | BucketBody | optional | Absent means a folder on this server. |
{
"name": "Rush delivery",
"path": "projects/rush",
"bucket": {}
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | string | required | The id. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name | string | required | The name. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
kind | string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path | string | required | A path relative to the space or the storage location, with / between folders and no leading slash. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
createdAt | string | required | A time, as RFC 3339. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
grants | integer | required | How many grants point at it, so the delete dialog can say what it is about to take away instead of asking for a blind confirmation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
reachable | boolean | required | Absent when the back end cannot be reached, which is the state an admin most needs to see: an unmounted volume looks exactly like an empty folder until something says otherwise. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bucket | View | optional, left out when empty | A bucket's settings, for the edit form. Never its secret: only whether one is stored. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"id": "k7Qm2sLp9vX4aB1c",
"name": "Rush delivery",
"kind": "shared",
"path": "projects/rush",
"createdAt": "2026-10-05T18:00:00Z",
"grants": 1,
"reachable": true,
"bucket": {
"provider": "example",
"bucket": "example",
"region": "example",
"prefix": "example",
"addressing": {},
"readOnly": true,
"credentials": {},
"hasSecret": true,
"performance": {
"partSizeMb": 1,
"partsPerFile": 1,
"rangesPerFile": 1,
"smallFiles": 1,
"checksum": {}
}
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | missing, unreadable, or somewhere that would expose the server's keys; or the object store's own refusal |
| 409 | conflict | the name is taken, or another storage location already covers it |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "missing, unreadable, or somewhere that would expose the server's keys; or the object store's own refusal"
}
}
POST /api/v1/admin/roots/test
Try a bucket exactly as given, and report what the object store said. Changes nothing. With ?root={id}, an empty secret means the one stored for that storage location.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
root | query | string | optional | The storage location being edited, whose stored secret is used when the form leaves the secret box empty. |
Request body. None.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the object store's own refusal, passed through |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the object store's own refusal, passed through"
}
}
PATCH /api/v1/admin/roots/{id}
Rename it, and for a bucket change how it is reached (key, region, endpoint, read only). The folder, the bucket and the folder in it cannot be changed; an empty secret keeps the stored one.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
name | string | required | The name. |
bucket | BucketBody | optional | A bucket's connection settings. The bucket and the folder in it cannot change, for the same reason a local root's path cannot. |
{
"name": "Rush delivery",
"bucket": {}
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | string | required | The id. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name | string | required | The name. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
kind | string | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path | string | required | A path relative to the space or the storage location, with / between folders and no leading slash. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
createdAt | string | required | A time, as RFC 3339. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
grants | integer | required | How many grants point at it, so the delete dialog can say what it is about to take away instead of asking for a blind confirmation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
reachable | boolean | required | Absent when the back end cannot be reached, which is the state an admin most needs to see: an unmounted volume looks exactly like an empty folder until something says otherwise. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bucket | View | optional, left out when empty | A bucket's settings, for the edit form. Never its secret: only whether one is stored. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"id": "k7Qm2sLp9vX4aB1c",
"name": "Rush delivery",
"kind": "shared",
"path": "projects/rush",
"createdAt": "2026-10-05T18:00:00Z",
"grants": 1,
"reachable": true,
"bucket": {
"provider": "example",
"bucket": "example",
"region": "example",
"prefix": "example",
"addressing": {},
"readOnly": true,
"credentials": {},
"hasSecret": true,
"performance": {
"partSizeMb": 1,
"partsPerFile": 1,
"rangesPerFile": 1,
"smallFiles": 1,
"checksum": {}
}
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a root needs a name of 1 to 64 characters |
| 404 | not_found | not there, or not visible to this caller |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "a root needs a name of 1 to 64 characters"
}
}
DELETE /api/v1/admin/roots/{id}
Remove it. The files are left where they are.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 409 | conflict | an automation still uses it |
| 404 | not_found | not there, or not visible to this caller |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "conflict",
"message": "an automation still uses it"
}
}
GET /api/v1/admin/roots/{id}/access
Who may reach this storage location: each person and group, the folder, and read, write or delete. Prefer a space.
Still served, so an older script keeps working. New scripts should use spaces.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Success. 200.
The body is an array.
| Field | Type | Meaning | |
|---|---|---|---|
id | string | required | The id. |
kind | string | required | user or group. |
principalId | string | required | The id. |
principalName | string | required | The address for a person, the name for a group. |
members | integer | required | How many people a group grant reaches. One for a person. |
path | string | required | Relative to the storage location. Empty is the whole location. |
canRead | boolean | required | |
canWrite | boolean | required | |
canDelete | boolean | required | |
createdAt | string | required | A time, as RFC 3339. |
[
{
"id": "k7Qm2sLp9vX4aB1c",
"kind": "file",
"principalId": "k7Qm2sLp9vX4aB1c",
"principalName": "example",
"members": 1,
"path": "projects/rush",
"canRead": true,
"canWrite": true,
"canDelete": true,
"createdAt": "2026-10-05T18:00:00Z"
}
]
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/roots/{id}/access
Give a person or a group access, or change the level they have.
Still served, so an older script keeps working. New scripts should use spaces.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Body: {"userId" | "groupId", "path", "access": "read" | "write" | "delete"}; each level includes the ones before it. The grant is filed under the shared space that holds the folder, and a space over the whole storage location is made when none does. Prefer a space grant.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
userId | string | optional | The id. |
groupId | string | optional | The id. |
path | string | optional | A path relative to the space or the storage location, with / between folders and no leading slash. |
access | string | required | read, write or delete; each includes the ones before it. |
{
"userId": "k7Qm2sLp9vX4aB1c",
"groupId": "k7Qm2sLp9vX4aB1c",
"path": "projects/rush",
"access": "edit"
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
id | string | required | The id. |
kind | string | required | user or group. |
principalId | string | required | The id. |
principalName | string | required | The address for a person, the name for a group. |
members | integer | required | How many people a group grant reaches. One for a person. |
path | string | required | Relative to the storage location. Empty is the whole location. |
canRead | boolean | required | |
canWrite | boolean | required | |
canDelete | boolean | required | |
createdAt | string | required | A time, as RFC 3339. |
{
"id": "k7Qm2sLp9vX4aB1c",
"kind": "file",
"principalId": "k7Qm2sLp9vX4aB1c",
"principalName": "example",
"members": 1,
"path": "projects/rush",
"canRead": true,
"canWrite": true,
"canDelete": true,
"createdAt": "2026-10-05T18:00:00Z"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | neither or both of userId and groupId, or an unknown level |
| 404 | not_found | no such storage location, person or group |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "neither or both of userId and groupId, or an unknown level"
}
}
DELETE /api/v1/admin/roots/{id}/access/{grant_id}
Take access away. Takes effect on the next request; no file is touched.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The storage location. |
grant_id | path | string | required | The grant. |
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 404 | not_found | not there, or not visible to this caller |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "not_found",
"message": "not found"
}
}
Traffic and usage
GET /api/v1/traffic/rules
Speed and concurrency rules, from the server down to jobs.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/traffic/rules
Create or replace a rule. Empty fields mean no limit at that level.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
| Field | Type | Meaning | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | string | optional | The id. | ||||||||||||||||
scope | string | required | |||||||||||||||||
scopeId | string | optional | The id. | ||||||||||||||||
groupKind | string | optional | |||||||||||||||||
direction | string | required | |||||||||||||||||
totalBps | integer | optional | |||||||||||||||||
perTransferBps | integer | optional | |||||||||||||||||
concurrency | integer | optional | |||||||||||||||||
schedule | Schedule | optional | Left out keeps the stored schedule; null clears it. | ||||||||||||||||
It is an object:
| |||||||||||||||||||
priority | string | optional | Left out keeps the stored priority. | ||||||||||||||||
enabled | boolean | optional | |||||||||||||||||
{
"scope": "read",
"direction": "download"
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | string | required | The id. | ||||||||||||||||
scope | string | required | |||||||||||||||||
scopeId | string | required | The id. | ||||||||||||||||
groupKind | string | required | |||||||||||||||||
direction | string | required | |||||||||||||||||
totalBps | integer | optional | |||||||||||||||||
perTransferBps | integer | optional | |||||||||||||||||
concurrency | integer | optional | |||||||||||||||||
schedule | Schedule | optional | |||||||||||||||||
It is an object:
| |||||||||||||||||||
priority | string | required | |||||||||||||||||
enabled | boolean | required | |||||||||||||||||
licensed | boolean | required | |||||||||||||||||
createdAt | integer | required | |||||||||||||||||
updatedAt | integer | required | |||||||||||||||||
{
"id": "k7Qm2sLp9vX4aB1c",
"scope": "read",
"scopeId": "k7Qm2sLp9vX4aB1c",
"groupKind": "example",
"direction": "download",
"priority": "example",
"enabled": true,
"licensed": true,
"createdAt": 1,
"updatedAt": 1
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the request is not valid |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the request is not valid"
}
}
DELETE /api/v1/traffic/rules/{id}
Remove a rule.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 404 | not_found | not there, or not visible to this caller |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "not_found",
"message": "not found"
}
}
GET /api/v1/traffic/preview
Effective limits for a user: which rule would bind if they sent now.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
user | query | string | required | |
direction | query | string | optional |
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
perTransferBps | integer | optional | |||||||||||||||||||||||||||||||||
poolBps | integer | optional | |||||||||||||||||||||||||||||||||
concurrency | integer | optional | |||||||||||||||||||||||||||||||||
limitedBy | string | required | |||||||||||||||||||||||||||||||||
sentence | string | required | |||||||||||||||||||||||||||||||||
layers | array of objects | required | Every rule that took part, license first, the person's own last. | ||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||
winners | object | required | The layer that set each number. | ||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||
{
"perTransferBps": 1,
"poolBps": 1,
"concurrency": 1,
"limitedBy": "example",
"sentence": "example",
"layers": [
{
"kind": "file",
"id": "k7Qm2sLp9vX4aB1c",
"groupKind": "example",
"totalBps": 1,
"perTransferBps": 1,
"concurrency": 1,
"overridden": [
"example"
]
}
],
"winners": {
"total": "example",
"perTransfer": "example",
"concurrency": "example"
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/traffic/policies
Server timezone for traffic schedules.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when this server's license does not include traffic policies.
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
timezone | string | required |
{
"timezone": "example"
}
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | this server's license does not include traffic policies |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "This server's license does not include traffic policies"
}
}
GET /api/v1/usage/live
Active and waiting transfers, updated by the client every second.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
uploadBps | integer | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||
downloadBps | integer | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||
uploadLimitBps | integer | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||
downloadLimitBps | integer | optional | |||||||||||||||||||||||||||||||||||||||||||||||||||||
active | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||
waiting | array of objects | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||
recent | array of objects | required | The last [WINDOW] of readings, oldest first. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"uploadBps": 1,
"downloadBps": 1,
"uploadLimitBps": 1,
"downloadLimitBps": 1,
"active": [
{
"id": "k7Qm2sLp9vX4aB1c",
"who": "example",
"direction": "download",
"name": "Rush delivery",
"bps": 1,
"bytes": 1048576,
"limitedBy": "example",
"waiting": true,
"ahead": 1
}
],
"waiting": [
{
"id": "k7Qm2sLp9vX4aB1c",
"who": "example",
"direction": "download",
"name": "Rush delivery",
"bps": 1,
"bytes": 1048576,
"limitedBy": "example",
"waiting": true,
"ahead": 1
}
],
"recent": [
{
"ts": 1,
"uploadBps": 1,
"downloadBps": 1
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
DELETE /api/v1/usage/live/{id}
Cancel a running or waiting transfer.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The resource's id. |
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 404 | not_found | not there, or not visible to this caller |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "not_found",
"message": "not found"
}
}
GET /api/v1/usage/history
Usage by hour or minute. Free mode shows the last 30 days.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
from | query | integer | optional | |
to | query | integer | optional | |
grain | query | string | optional | |
series | query | string | optional | |
series_id | query | string | optional | |
direction | query | string | optional |
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/usage/history.csv
Same view as a CSV file.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when usage reports are not included.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
from | query | integer | optional | |
to | query | integer | optional | |
grain | query | string | optional | |
series | query | string | optional | |
series_id | query | string | optional | |
direction | query | string | optional |
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | usage reports are not included |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "Usage reports are not included"
}
}
GET /api/v1/usage/reports
Scheduled email reports.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/usage/reports
Add a weekly or monthly report.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when usage reports are not included.
Request body. JSON.
| Field | Type | Meaning | |
|---|---|---|---|
cadence | string | required | |
addresses | array of string | required |
{
"cadence": "example",
"addresses": [
"example"
]
}
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | usage reports are not included |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "Usage reports are not included"
}
}
POST /api/v1/usage/metrics-token
Mint a read-only token for the usage metrics feed.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when usage reports are not included.
Request body. None.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | usage reports are not included |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "Usage reports are not included"
}
}
GET /api/v1/usage/metrics
Prometheus text for traffic. HTTP 402 in Free mode. Existing /metrics is unchanged.
Who. Any user with a key that has the read scope. A key with the admin scope includes the others. The key needs a license that includes the REST API. This call can also answer 402 when usage reports are not included.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 402 | licence_required | usage reports are not included |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "licence_required",
"message": "Usage reports are not included"
}
}
Single sign-on
GET /api/v1/admin/sso
Single sign-on settings. Never the client secret.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required | What the administrator asked for. |
active | boolean | required | Whether it is actually working right now. These differ when the provider could not be reached at start-up, and the screen has to show the difference rather than a tick that means nothing. |
problem | string | optional, left out when empty | |
issuer | string | required | |
clientId | string | required | The id. |
clientSecretSet | boolean | required | |
createAccounts | boolean | required | |
groupsClaim | string | optional, left out when empty | |
label | string | required | |
redirectUri | string | required | What to register at the provider. Read-only, and worked out from web.hostname, so the administrator copies it rather than typing it into both places and getting them subtly different. |
included | boolean | required | Whether the license includes single sign-on. When it does not, settings may be saved switched off, switching it on is refused, and nobody can sign in this way. |
{
"enabled": true,
"active": true,
"issuer": "example",
"clientId": "k7Qm2sLp9vX4aB1c",
"clientSecretSet": true,
"createAccounts": true,
"label": "example",
"redirectUri": "example",
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/sso
Set them up, or change them.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when switching it on, when this server's license does not include single sign-on.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required | |
issuer | string | required | |
clientId | string | required | The id. |
clientSecret | string | optional | |
createAccounts | boolean | optional | |
groupsClaim | string | optional | |
label | string | optional |
{
"enabled": true,
"issuer": "example",
"clientId": "k7Qm2sLp9vX4aB1c",
"clientSecret": "a-secret-shown-once",
"createAccounts": true,
"groupsClaim": "example",
"label": "example"
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required | What the administrator asked for. |
active | boolean | required | Whether it is actually working right now. These differ when the provider could not be reached at start-up, and the screen has to show the difference rather than a tick that means nothing. |
problem | string | optional, left out when empty | |
issuer | string | required | |
clientId | string | required | The id. |
clientSecretSet | boolean | required | |
createAccounts | boolean | required | |
groupsClaim | string | optional, left out when empty | |
label | string | required | |
redirectUri | string | required | What to register at the provider. Read-only, and worked out from web.hostname, so the administrator copies it rather than typing it into both places and getting them subtly different. |
included | boolean | required | Whether the license includes single sign-on. When it does not, settings may be saved switched off, switching it on is refused, and nobody can sign in this way. |
{
"enabled": true,
"active": true,
"issuer": "example",
"clientId": "k7Qm2sLp9vX4aB1c",
"clientSecretSet": true,
"createAccounts": true,
"label": "example",
"redirectUri": "example",
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the provider could not be reached, or did not describe itself |
| 402 | licence_required | switching it on, when this server's license does not include single sign-on |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "the provider could not be reached, or did not describe itself"
}
}
DELETE /api/v1/admin/sso
Turn it off. Passwords keep working.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required | What the administrator asked for. |
active | boolean | required | Whether it is actually working right now. These differ when the provider could not be reached at start-up, and the screen has to show the difference rather than a tick that means nothing. |
problem | string | optional, left out when empty | |
issuer | string | required | |
clientId | string | required | The id. |
clientSecretSet | boolean | required | |
createAccounts | boolean | required | |
groupsClaim | string | optional, left out when empty | |
label | string | required | |
redirectUri | string | required | What to register at the provider. Read-only, and worked out from web.hostname, so the administrator copies it rather than typing it into both places and getting them subtly different. |
included | boolean | required | Whether the license includes single sign-on. When it does not, settings may be saved switched off, switching it on is refused, and nobody can sign in this way. |
{
"enabled": true,
"active": true,
"issuer": "example",
"clientId": "k7Qm2sLp9vX4aB1c",
"clientSecretSet": true,
"createAccounts": true,
"label": "example",
"redirectUri": "example",
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/admin/saml
SAML sign-in settings, and this server's service provider details to give the identity provider.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/saml
Set SAML sign-in up, or change it: the provider's metadata (pasted or by address), and whether it is switched on or required.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when switching it on or requiring it, when this server's license does not include single sign-on.
Every field is optional; one that is left out keeps what is stored. Switching it on saves nothing unless the settings could sign somebody in. Requiring single sign-on is refused unless a named break-glass administrator exists with a password and a second factor.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
enabled | boolean | optional | |||||||||||||||||||||
label | string | optional | |||||||||||||||||||||
idpMetadataUrl | string | optional | |||||||||||||||||||||
idpMetadataXml | string | optional | Pasted metadata, read now and not kept: what is kept is what it said. | ||||||||||||||||||||
idpEntityId | string | optional | The id. | ||||||||||||||||||||
idpSsoUrl | string | optional | |||||||||||||||||||||
idpSsoBinding | Binding | optional | |||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||
idpSloUrl | string | optional | |||||||||||||||||||||
idpCertificates | array of string | optional | |||||||||||||||||||||
wantAssertionsSigned | boolean | optional | Accepted so the screen can send what it shows, and refused when false: this build takes nothing unsigned. | ||||||||||||||||||||
wantAssertionsEncrypted | boolean | optional | Accepted to be refused when true; see saml::response. | ||||||||||||||||||||
allowIdpInitiated | boolean | optional | |||||||||||||||||||||
singleLogout | boolean | optional | |||||||||||||||||||||
createAccounts | boolean | optional | |||||||||||||||||||||
emailAttribute | string | optional | |||||||||||||||||||||
nameAttribute | string | optional | |||||||||||||||||||||
groupsAttribute | string | optional | |||||||||||||||||||||
groupMap | array of GroupMapping | optional | |||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
requireSso | boolean | optional | |||||||||||||||||||||
breakGlassEmail | string | optional | Named by email, because that is what an administrator knows. | ||||||||||||||||||||
breakGlassUserId | string | optional | The id. | ||||||||||||||||||||
{
"enabled": true,
"label": "example",
"idpMetadataUrl": "example",
"idpMetadataXml": "example",
"idpEntityId": "k7Qm2sLp9vX4aB1c",
"idpSsoUrl": "example",
"idpSsoBinding": {
"field": "example",
"bps": 1,
"concurrency": 1,
"label": "example"
},
"idpSloUrl": "example"
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
enabled | boolean | required | What the administrator asked for. | ||||||||||||||||||||
active | boolean | required | Whether sign-in works right now. These differ when the settings are incomplete or the license does not include it, and the screen has to show the difference rather than a tick that means nothing. | ||||||||||||||||||||
problem | string | optional | |||||||||||||||||||||
label | string | required | |||||||||||||||||||||
idpMetadataUrl | string | required | |||||||||||||||||||||
idpEntityId | string | required | The id. | ||||||||||||||||||||
idpSsoUrl | string | required | |||||||||||||||||||||
idpSsoBinding | object | required | |||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||
idpSloUrl | string | required | |||||||||||||||||||||
idpCertificates | array of string | required | Certificates, as PEM. Public, and never a key. | ||||||||||||||||||||
spEntityId | string | required | What to register at the provider. Read-only, worked out from the configured hostname so it is copied rather than typed twice. | ||||||||||||||||||||
spMetadataUrl | string | required | |||||||||||||||||||||
spAcsUrl | string | required | |||||||||||||||||||||
spLogoutUrl | string | required | |||||||||||||||||||||
spCertificate | string | required | The certificate this server serves its portal with, which our metadata lists. Empty when it cannot be read. | ||||||||||||||||||||
wantAssertionsSigned | boolean | required | Always true: this build accepts nothing unsigned, and says so. | ||||||||||||||||||||
allowIdpInitiated | boolean | required | |||||||||||||||||||||
singleLogout | boolean | required | |||||||||||||||||||||
createAccounts | boolean | required | |||||||||||||||||||||
emailAttribute | string | required | |||||||||||||||||||||
nameAttribute | string | required | |||||||||||||||||||||
groupsAttribute | string | required | |||||||||||||||||||||
groupMap | array of objects | required | |||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
requireSso | boolean | required | |||||||||||||||||||||
breakGlassEmail | string | required | An email address. | ||||||||||||||||||||
breakGlassUserId | string | optional | The id. | ||||||||||||||||||||
included | boolean | required | Whether the license includes SAML. When it does not, settings may be prepared switched off, switching on is refused, and nobody can sign in this way. | ||||||||||||||||||||
{
"enabled": true,
"active": true,
"label": "example",
"idpMetadataUrl": "example",
"idpEntityId": "k7Qm2sLp9vX4aB1c",
"idpSsoUrl": "example",
"idpSsoBinding": {
"field": "example",
"bps": 1,
"concurrency": 1,
"label": "example"
},
"idpSloUrl": "example",
"idpCertificates": [
"example"
],
"spEntityId": "k7Qm2sLp9vX4aB1c",
"spMetadataUrl": "example",
"spAcsUrl": "example",
"spLogoutUrl": "example",
"spCertificate": "example",
"wantAssertionsSigned": true,
"allowIdpInitiated": true,
"singleLogout": true,
"createAccounts": true,
"emailAttribute": "example",
"nameAttribute": "example",
"groupsAttribute": "example",
"groupMap": [
{
"from": "projects/rush",
"to": "projects/rush"
}
],
"requireSso": true,
"breakGlassEmail": "[email protected]",
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | nothing was saved; the message says why |
| 402 | licence_required | switching it on or requiring it, when this server's license does not include single sign-on |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "nothing was saved; the message says why"
}
}
DELETE /api/v1/admin/saml
Remove the SAML settings. Passwords keep working, and accounts keep the identities already linked to them.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
enabled | boolean | required | What the administrator asked for. | ||||||||||||||||||||
active | boolean | required | Whether sign-in works right now. These differ when the settings are incomplete or the license does not include it, and the screen has to show the difference rather than a tick that means nothing. | ||||||||||||||||||||
problem | string | optional | |||||||||||||||||||||
label | string | required | |||||||||||||||||||||
idpMetadataUrl | string | required | |||||||||||||||||||||
idpEntityId | string | required | The id. | ||||||||||||||||||||
idpSsoUrl | string | required | |||||||||||||||||||||
idpSsoBinding | object | required | |||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||
idpSloUrl | string | required | |||||||||||||||||||||
idpCertificates | array of string | required | Certificates, as PEM. Public, and never a key. | ||||||||||||||||||||
spEntityId | string | required | What to register at the provider. Read-only, worked out from the configured hostname so it is copied rather than typed twice. | ||||||||||||||||||||
spMetadataUrl | string | required | |||||||||||||||||||||
spAcsUrl | string | required | |||||||||||||||||||||
spLogoutUrl | string | required | |||||||||||||||||||||
spCertificate | string | required | The certificate this server serves its portal with, which our metadata lists. Empty when it cannot be read. | ||||||||||||||||||||
wantAssertionsSigned | boolean | required | Always true: this build accepts nothing unsigned, and says so. | ||||||||||||||||||||
allowIdpInitiated | boolean | required | |||||||||||||||||||||
singleLogout | boolean | required | |||||||||||||||||||||
createAccounts | boolean | required | |||||||||||||||||||||
emailAttribute | string | required | |||||||||||||||||||||
nameAttribute | string | required | |||||||||||||||||||||
groupsAttribute | string | required | |||||||||||||||||||||
groupMap | array of objects | required | |||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||
requireSso | boolean | required | |||||||||||||||||||||
breakGlassEmail | string | required | An email address. | ||||||||||||||||||||
breakGlassUserId | string | optional | The id. | ||||||||||||||||||||
included | boolean | required | Whether the license includes SAML. When it does not, settings may be prepared switched off, switching on is refused, and nobody can sign in this way. | ||||||||||||||||||||
{
"enabled": true,
"active": true,
"label": "example",
"idpMetadataUrl": "example",
"idpEntityId": "k7Qm2sLp9vX4aB1c",
"idpSsoUrl": "example",
"idpSsoBinding": {
"field": "example",
"bps": 1,
"concurrency": 1,
"label": "example"
},
"idpSloUrl": "example",
"idpCertificates": [
"example"
],
"spEntityId": "k7Qm2sLp9vX4aB1c",
"spMetadataUrl": "example",
"spAcsUrl": "example",
"spLogoutUrl": "example",
"spCertificate": "example",
"wantAssertionsSigned": true,
"allowIdpInitiated": true,
"singleLogout": true,
"createAccounts": true,
"emailAttribute": "example",
"nameAttribute": "example",
"groupsAttribute": "example",
"groupMap": [
{
"from": "projects/rush",
"to": "projects/rush"
}
],
"requireSso": true,
"breakGlassEmail": "[email protected]",
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/saml/test
Check the saved settings without changing anything. With a metadata address saved, it is fetched again to find a certificate the provider has rotated.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Always answers 200: ok says whether the settings would work, and detail says why in a sentence.
Request body. None.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
Settings
GET /api/v1/admin/smtp
Mail settings. Never the password.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required | |
host | string | required | |
port | integer | required | |
security | string | required | |
username | string | required | |
passwordSet | boolean | required | Whether one is stored. Never the password. |
fromName | string | required | |
fromAddress | string | required | |
problem | string | optional, left out when empty | What is stopping these settings working, if anything. Shown whether or not email is switched on, so an administrator filling the form in can see what is still missing. |
lastTestAt | string | optional, left out when empty | When a test send last worked, if one ever has. The screen uses this rather than the enabled flag to say email is working: a flag only records what somebody intended. |
{
"enabled": true,
"host": "files.example.com",
"port": 443,
"security": "example",
"username": "example",
"passwordSet": true,
"fromName": "example",
"fromAddress": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/smtp
Set them up, or change them.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required | |
host | string | required | |
port | integer | required | |
security | string | required | |
username | string | optional | |
password | string | optional | Absent means "keep the stored one". Present and empty means "there is no password", which some relays genuinely want. |
fromName | string | optional | |
fromAddress | string | required |
{
"enabled": true,
"host": "files.example.com",
"port": 443,
"security": "example",
"username": "example",
"password": "a-secret-shown-once",
"fromName": "example",
"fromAddress": "example"
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required | |
host | string | required | |
port | integer | required | |
security | string | required | |
username | string | required | |
passwordSet | boolean | required | Whether one is stored. Never the password. |
fromName | string | required | |
fromAddress | string | required | |
problem | string | optional, left out when empty | What is stopping these settings working, if anything. Shown whether or not email is switched on, so an administrator filling the form in can see what is still missing. |
lastTestAt | string | optional, left out when empty | When a test send last worked, if one ever has. The screen uses this rather than the enabled flag to say email is working: a flag only records what somebody intended. |
{
"enabled": true,
"host": "files.example.com",
"port": 443,
"security": "example",
"username": "example",
"passwordSet": true,
"fromName": "example",
"fromAddress": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
DELETE /api/v1/admin/smtp
Turn email off.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
enabled | boolean | required | |
host | string | required | |
port | integer | required | |
security | string | required | |
username | string | required | |
passwordSet | boolean | required | Whether one is stored. Never the password. |
fromName | string | required | |
fromAddress | string | required | |
problem | string | optional, left out when empty | What is stopping these settings working, if anything. Shown whether or not email is switched on, so an administrator filling the form in can see what is still missing. |
lastTestAt | string | optional, left out when empty | When a test send last worked, if one ever has. The screen uses this rather than the enabled flag to say email is working: a flag only records what somebody intended. |
{
"enabled": true,
"host": "files.example.com",
"port": 443,
"security": "example",
"username": "example",
"passwordSet": true,
"fromName": "example",
"fromAddress": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/smtp/test
Send a test message, and report what the mail server said.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
to | string | optional | Where to send it. Defaults to the signed-in administrator, which is the address they can actually check. |
{
"to": "projects/rush"
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
sentTo | string | required |
{
"sentTo": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the mail server refused; its own words are in the message |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the mail server refused; its own words are in the message"
}
}
Directory
GET /api/v1/admin/directory
LDAP and Active Directory sign-in settings.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. Switching directory sign-in on needs the sso feature; reading, preparing, testing and switching it off do not. Besides the settings the reply has presets to start from for Active Directory and OpenLDAP, and included, whether this license can switch it on. The service account's password is never returned: bindPasswordSet says whether one is stored. problem says why it cannot sign people in now, or something to know while it works, such as a connection that is not encrypted.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
view | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
presets | object | required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"view": {
"enabled": true,
"active": true,
"label": "example",
"host": "files.example.com",
"port": 443,
"security": {},
"allowPlain": true,
"bindDn": "example",
"bindPasswordSet": true,
"userSearchBase": "example",
"userFilter": "example",
"groupSearchBase": "example",
"groupFilter": "example",
"emailAttribute": "example",
"nameAttribute": "example",
"usernameAttribute": "example",
"memberAttribute": "example",
"nestedGroups": true,
"createAccounts": true,
"groupMap": [
{
"from": "projects/rush",
"to": "projects/rush"
}
],
"syncEnabled": true,
"included": true
},
"presets": {
"activeDirectory": {
"port": 443,
"security": {},
"userFilter": "example",
"usernameAttribute": "example",
"emailAttribute": "example",
"nameAttribute": "example",
"memberAttribute": "example",
"groupFilter": "example",
"nestedGroups": true
},
"openLdap": {
"port": 443,
"security": {},
"userFilter": "example",
"usernameAttribute": "example",
"emailAttribute": "example",
"nameAttribute": "example",
"memberAttribute": "example",
"groupFilter": "example",
"nestedGroups": true
}
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/directory
Set up directory sign-in, or change it.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API. This call can also answer 402 when switching it on, when this server's license does not include single sign-on.
Administrators only. Send only what changes; fields that are only read, such as active and included, are ignored, and a missing or empty bindPassword keeps the stored one. When the result is switched on, the server connects, signs in as the service account and runs the user search before anything is saved, so settings that have never been shown to work are not left switched on. Preparing it switched off needs no directory at all. Switching it on needs the sso feature, and a connection with no encryption needs allowPlain. The audit log names the fields that changed, never their values.
Request body. JSON.
| Field | Type | Meaning | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
enabled | boolean | optional | |||||||||||||
label | string | optional | |||||||||||||
host | string | optional | |||||||||||||
port | integer | optional | |||||||||||||
security | Security | optional | |||||||||||||
allowPlain | boolean | optional | |||||||||||||
bindDn | string | optional | |||||||||||||
bindPassword | string | optional | |||||||||||||
userSearchBase | string | optional | |||||||||||||
userFilter | string | optional | |||||||||||||
groupSearchBase | string | optional | |||||||||||||
groupFilter | string | optional | |||||||||||||
emailAttribute | string | optional | |||||||||||||
nameAttribute | string | optional | |||||||||||||
usernameAttribute | string | optional | |||||||||||||
memberAttribute | string | optional | |||||||||||||
nestedGroups | boolean | optional | |||||||||||||
createAccounts | boolean | optional | |||||||||||||
groupMap | array of Mapping | optional | |||||||||||||
Each item is an object:
| |||||||||||||||
syncEnabled | boolean | optional | |||||||||||||
{
"enabled": true,
"label": "example",
"host": "files.example.com",
"port": 443,
"security": {},
"allowPlain": true,
"bindDn": "example",
"bindPassword": "a-secret-shown-once"
}
Success. 200.
| Field | Type | Meaning | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
enabled | boolean | required | What the administrator asked for. | ||||||||||||
active | boolean | required | Whether it can sign people in right now. | ||||||||||||
problem | string | optional | Why not, or something to be aware of while it works. | ||||||||||||
label | string | required | |||||||||||||
host | string | required | |||||||||||||
port | integer | required | |||||||||||||
security | object | required | |||||||||||||
allowPlain | boolean | required | |||||||||||||
bindDn | string | required | |||||||||||||
bindPasswordSet | boolean | required | |||||||||||||
userSearchBase | string | required | |||||||||||||
userFilter | string | required | |||||||||||||
groupSearchBase | string | required | |||||||||||||
groupFilter | string | required | |||||||||||||
emailAttribute | string | required | |||||||||||||
nameAttribute | string | required | |||||||||||||
usernameAttribute | string | required | |||||||||||||
memberAttribute | string | required | |||||||||||||
nestedGroups | boolean | required | |||||||||||||
createAccounts | boolean | required | |||||||||||||
groupMap | array of objects | required | |||||||||||||
Each item is an object:
| |||||||||||||||
syncEnabled | boolean | required | |||||||||||||
lastSyncAt | string | optional | A time, as RFC 3339. | ||||||||||||
lastSyncDetail | string | optional | |||||||||||||
included | boolean | required | Whether the license includes switching this on. | ||||||||||||
{
"enabled": true,
"active": true,
"label": "example",
"host": "files.example.com",
"port": 443,
"security": {},
"allowPlain": true,
"bindDn": "example",
"bindPasswordSet": true,
"userSearchBase": "example",
"userFilter": "example",
"groupSearchBase": "example",
"groupFilter": "example",
"emailAttribute": "example",
"nameAttribute": "example",
"usernameAttribute": "example",
"memberAttribute": "example",
"nestedGroups": true,
"createAccounts": true,
"groupMap": [
{
"from": "projects/rush",
"to": "projects/rush"
}
],
"syncEnabled": true,
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a value that is out of range or not usable, or the directory could not be used (the message says why); nothing is saved |
| 402 | licence_required | switching it on, when this server's license does not include single sign-on |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
{
"error": {
"code": "bad_request",
"message": "a value that is out of range or not usable, or the directory could not be used (the message says why); nothing is saved"
}
}
DELETE /api/v1/admin/directory
Remove directory sign-in.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. Deletes the settings and the service account's password. The links between directory names and accounts stay, so people who signed in this way keep their accounts if the same directory is added again.
Success. 200.
| Field | Type | Meaning | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
enabled | boolean | required | What the administrator asked for. | ||||||||||||
active | boolean | required | Whether it can sign people in right now. | ||||||||||||
problem | string | optional | Why not, or something to be aware of while it works. | ||||||||||||
label | string | required | |||||||||||||
host | string | required | |||||||||||||
port | integer | required | |||||||||||||
security | object | required | |||||||||||||
allowPlain | boolean | required | |||||||||||||
bindDn | string | required | |||||||||||||
bindPasswordSet | boolean | required | |||||||||||||
userSearchBase | string | required | |||||||||||||
userFilter | string | required | |||||||||||||
groupSearchBase | string | required | |||||||||||||
groupFilter | string | required | |||||||||||||
emailAttribute | string | required | |||||||||||||
nameAttribute | string | required | |||||||||||||
usernameAttribute | string | required | |||||||||||||
memberAttribute | string | required | |||||||||||||
nestedGroups | boolean | required | |||||||||||||
createAccounts | boolean | required | |||||||||||||
groupMap | array of objects | required | |||||||||||||
Each item is an object:
| |||||||||||||||
syncEnabled | boolean | required | |||||||||||||
lastSyncAt | string | optional | A time, as RFC 3339. | ||||||||||||
lastSyncDetail | string | optional | |||||||||||||
included | boolean | required | Whether the license includes switching this on. | ||||||||||||
{
"enabled": true,
"active": true,
"label": "example",
"host": "files.example.com",
"port": 443,
"security": {},
"allowPlain": true,
"bindDn": "example",
"bindPasswordSet": true,
"userSearchBase": "example",
"userFilter": "example",
"groupSearchBase": "example",
"groupFilter": "example",
"emailAttribute": "example",
"nameAttribute": "example",
"usernameAttribute": "example",
"memberAttribute": "example",
"nestedGroups": true,
"createAccounts": true,
"groupMap": [
{
"from": "projects/rush",
"to": "projects/rush"
}
],
"syncEnabled": true,
"included": true
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/directory/test
Try the saved directory settings.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. Connects, signs in as the service account, and counts the people and groups the searches find, to 1,000 of each before it says "at least". Works whether directory sign-in is switched on or not. A directory that cannot be reached is 200 with ok: false and the directory's own words, because finding that out is what the call is for.
Request body. None.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
ok | boolean | required | |
detail | string | required | |
users | integer | optional, left out when empty | |
groups | integer | optional, left out when empty |
{
"ok": true,
"detail": "example",
"users": 1,
"groups": 1
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/directory/test-login
Try one person's sign-in and show which groups come back.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. Signs one person in against the saved settings and reports their email address and name, the groups the directory lists for them, and which Farwing groups those map to. Nothing is created and no session is issued, so it is safe to run for anybody, including someone who has never signed in. The password is used once and appears nowhere, neither in the reply nor in the audit log, which records only the username. A refusal is 200 with ok: false.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
username | string | required | |
password | string | required |
{
"username": "example",
"password": "a-secret-shown-once"
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
ok | boolean | required | |
detail | string | required | |
email | string | optional, left out when empty | An email address. |
name | string | optional, left out when empty | The name. |
groups | array of string | optional, left out when empty | |
mapped | array of string | optional, left out when empty |
{
"ok": true,
"detail": "example",
"email": "[email protected]",
"name": "Rush delivery",
"groups": [
"example"
],
"mapped": [
"example"
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
POST /api/v1/admin/directory/sync
Check now which people the directory no longer has.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. The hourly check, run on demand. It disables the Farwing accounts of people the directory no longer has, and never deletes anyone.
Request body. None.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
checked | integer | required | |
disabled | integer | required | |
detail | string | required |
{
"checked": 1,
"disabled": 1,
"detail": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | directory sign-in is switched off, its settings have a problem, or the directory could not be read |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "directory sign-in is switched off, its settings have a problem, or the directory could not be read"
}
}
Virus scanning
GET /api/v1/admin/scanning
The virus scanner's settings.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. Virus scanning works on every plan, Free included. scanner is off, clamav, icap or command, and only the chosen scanner's fields are checked. The command comes back in plain text, since an administrator who cannot read what they typed cannot fix it. If the stored settings cannot be read, the defaults are shown and files are held rather than released, so a damaged setting never switches scanning off.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
scanner | object | required | |||||||||||||||||
clamavAddress | string | required | host:port, or the path of the clamd socket. | ||||||||||||||||
icapUrl | string | required | icap://host:1344/service. | ||||||||||||||||
icapMode | object | required | |||||||||||||||||
command | string | required | The program and its arguments, split on spaces with quotes to group. | ||||||||||||||||
maxFileMb | integer | required | Megabytes. Anything larger follows too_large. | ||||||||||||||||
timeoutSecs | integer | required | |||||||||||||||||
scanReceiveLinks | boolean | required | |||||||||||||||||
scanOutsidePackages | boolean | required | |||||||||||||||||
scanUserUploads | boolean | required | |||||||||||||||||
scanServerToServer | boolean | required | |||||||||||||||||
tooLarge | object | required | |||||||||||||||||
It is an object:
| |||||||||||||||||||
onError | object | required | |||||||||||||||||
It is an object:
| |||||||||||||||||||
lastTestAt | string | optional | A time, as RFC 3339. | ||||||||||||||||
lastTestDetail | string | optional | |||||||||||||||||
{
"scanner": {},
"clamavAddress": "example",
"icapUrl": "example",
"icapMode": {},
"command": "example",
"maxFileMb": 1,
"timeoutSecs": 1,
"scanReceiveLinks": true,
"scanOutsidePackages": true,
"scanUserUploads": true,
"scanServerToServer": true,
"tooLarge": {
"allowNeverExpires": true,
"defaultExpiryDays": 1,
"formFieldsIncluded": true
},
"onError": {
"allowNeverExpires": true,
"defaultExpiryDays": 1,
"formFieldsIncluded": true
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
PUT /api/v1/admin/scanning
Change the virus scanner's settings.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. Send only the fields that change. Saving does not contact the scanner, so one that is down for an hour does not stop the right address being recorded; use the test call for that. A change to how the scanner is reached clears the result of the last test. The audit log records which scanner and which switches, never the command line, which can hold a token.
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
scanner | ScannerKind | optional | |||||||||||||||||
clamavAddress | string | optional | |||||||||||||||||
icapUrl | string | optional | |||||||||||||||||
icapMode | IcapMode | optional | |||||||||||||||||
command | string | optional | |||||||||||||||||
maxFileMb | integer | optional | |||||||||||||||||
timeoutSecs | integer | optional | |||||||||||||||||
scanReceiveLinks | boolean | optional | |||||||||||||||||
scanOutsidePackages | boolean | optional | |||||||||||||||||
scanUserUploads | boolean | optional | |||||||||||||||||
scanServerToServer | boolean | optional | |||||||||||||||||
tooLarge | Policy | optional | |||||||||||||||||
It is an object:
| |||||||||||||||||||
onError | Policy | optional | |||||||||||||||||
It is an object:
| |||||||||||||||||||
lastTestAt | value | optional | |||||||||||||||||
lastTestDetail | value | optional | |||||||||||||||||
{
"scanner": {},
"clamavAddress": "example",
"icapUrl": "example",
"icapMode": {},
"command": "example",
"maxFileMb": 1,
"timeoutSecs": 1,
"scanReceiveLinks": true
}
Success. 200.
| Field | Type | Meaning | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
scanner | object | required | |||||||||||||||||
clamavAddress | string | required | host:port, or the path of the clamd socket. | ||||||||||||||||
icapUrl | string | required | icap://host:1344/service. | ||||||||||||||||
icapMode | object | required | |||||||||||||||||
command | string | required | The program and its arguments, split on spaces with quotes to group. | ||||||||||||||||
maxFileMb | integer | required | Megabytes. Anything larger follows too_large. | ||||||||||||||||
timeoutSecs | integer | required | |||||||||||||||||
scanReceiveLinks | boolean | required | |||||||||||||||||
scanOutsidePackages | boolean | required | |||||||||||||||||
scanUserUploads | boolean | required | |||||||||||||||||
scanServerToServer | boolean | required | |||||||||||||||||
tooLarge | object | required | |||||||||||||||||
It is an object:
| |||||||||||||||||||
onError | object | required | |||||||||||||||||
It is an object:
| |||||||||||||||||||
lastTestAt | string | optional | A time, as RFC 3339. | ||||||||||||||||
lastTestDetail | string | optional | |||||||||||||||||
{
"scanner": {},
"clamavAddress": "example",
"icapUrl": "example",
"icapMode": {},
"command": "example",
"maxFileMb": 1,
"timeoutSecs": 1,
"scanReceiveLinks": true,
"scanOutsidePackages": true,
"scanUserUploads": true,
"scanServerToServer": true,
"tooLarge": {
"allowNeverExpires": true,
"defaultExpiryDays": 1,
"formFieldsIncluded": true
},
"onError": {
"allowNeverExpires": true,
"defaultExpiryDays": 1,
"formFieldsIncluded": true
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | a file size outside 1 to 10,485,760 MB, a timeout outside 1 to 3,600 seconds, or an address, URL or command the chosen scanner cannot use |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "a file size outside 1 to 10,485,760 MB, a timeout outside 1 to 3,600 seconds, or an address, URL or command the chosen scanner cannot use"
}
}
POST /api/v1/admin/scanning/test
Check that the saved scanner can be reached.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only. Reports the scanner's own words rather than a summary of them, because "Access denied" from ClamAV and "Method not allowed" from an ICAP server point at different fixes. A scanner that fails is still 200 with ok: false: the request worked, the scanner did not. The result is kept in the settings as lastTestAt and lastTestDetail. It tests what is saved, not what is typed.
Request body. None.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
ok | boolean | required | |
detail | string | required | |
version | string | optional, left out when empty |
{
"ok": true,
"detail": "example",
"version": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/admin/quarantine
Files the scanner held or blocked.
Someone who may not call this is answered 404, the same as a missing record, so the refusal does not confirm the route exists.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only, and answered 404 to anyone else, as for a route that does not exist: a 403 would tell an account that this server has a quarantine, and so a virus scanner. Newest first, up to 500.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
state | query | string | optional | held (the default), released, deleted or all. |
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
items | array of objects | required | The quarantined files, newest first, up to 500. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each item is an object:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{
"items": [
{
"id": "k7Qm2sLp9vX4aB1c",
"fileName": "example",
"intendedPath": "projects/rush",
"bytes": 1048576,
"threat": "2026-10-05T18:00:00Z",
"reason": "The scanner was wrong about this file.",
"origin": "example",
"state": "active",
"createdAt": "2026-10-05T18:00:00Z"
}
]
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | `state` is not held, released, deleted or all |
| 404 | not_found | the caller is not an administrator |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "`state` is not held, released, deleted or all"
}
}
POST /api/v1/admin/quarantine/{id}
Release or delete a held file.
Someone who may not call this is answered 404, the same as a missing record, so the refusal does not confirm the route exists.
Who. An administrator, with a key that has the admin scope. An administrator's key that does not have that scope is refused. The key needs a license that includes the REST API.
Administrators only, and answered 404 to anyone else. release puts the file where it was meant to go and announces it to event rules as arrived, marked as an administrator's override; delete removes it for good. A reason is required, and goes into the audit log with the file's name.
Parameters.
| Name | In | Type | Meaning | |
|---|---|---|---|---|
id | path | string | required | The quarantined file. |
Request body. JSON.
A field the server does not know is refused with 400.
| Field | Type | Meaning | |
|---|---|---|---|
action | string | required | release puts the file back where it was meant to go. delete removes it. |
reason | string | required | Why this decision was made. Required, and written to the audit log. |
{
"action": "release",
"reason": "The scanner was wrong about this file."
}
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
id | string | required | The id. |
fileName | string | required | The file's name. |
intendedPath | string | required | Where the file was meant to be stored. |
rootName | string | optional | The storage location's name. |
bytes | integer | required | Size in bytes. |
threat | string | required | The threat's name for an infected file; for a held one, what the scanner said, which is the administrator's to read. |
reason | string | required | infected, too_large or error. |
origin | string | required | How the file arrived: browser, receive_link, package, server, or desktop. |
actorEmail | string | optional | An email address. |
state | string | required | held, released or deleted. |
decidedBy | string | optional | The administrator who decided, when someone has. |
decidedAt | string | optional | A time, as RFC 3339. |
decisionReason | string | optional | The reason recorded with that decision. |
createdAt | string | required | A time, as RFC 3339. |
{
"id": "k7Qm2sLp9vX4aB1c",
"fileName": "example",
"intendedPath": "projects/rush",
"bytes": 1048576,
"threat": "2026-10-05T18:00:00Z",
"reason": "The scanner was wrong about this file.",
"origin": "example",
"state": "active",
"createdAt": "2026-10-05T18:00:00Z"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the body is not an action and a reason, the action is not `release` or `delete`, or there is no reason |
| 404 | not_found | no such file, or the caller is not an administrator |
| 409 | conflict | the file has already been released or deleted |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the body is not an action and a reason, the action is not `release` or `delete`, or there is no reason"
}
}