API

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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

StatuscodeWhen
400bad_requestnot a valid license, or not for this install
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
400bad_requestmore than the limit was picked
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
actionquerystringoptionalExact action, or a prefix ending in . such as auth..
actorKindquerystringoptionaluser, api_key, system or recipient.
fromquerystringoptionalRFC 3339. Inclusive.
toquerystringoptionalRFC 3339. Exclusive, so paging by at cannot repeat a row.
beforequeryintegeroptionalPage backwards: only rows with a smaller id than this one.
limitqueryintegeroptionalUp to 500. Clamped, not refused.
qquerystringoptionalMatched against the action, the target and the detail.
actorIdquerystringoptional

Success. 200.

FieldTypeMeaning
itemsarray of objectsrequiredThe records in this reply.

Each item is an object:

FieldTypeMeaning
idintegerrequiredMonotonic, and the paging key. at is not unique enough: a burst of sign-in attempts can share a timestamp to the second.
atstringrequired
actorIdstringoptionalThe id.
actorKindstringrequired
actorEmailstringoptionalResolved when the actor is still a known account, so the screen can show a name instead of an opaque id. Accounts are kept rather than deleted precisely so this usually succeeds.
actionstringrequired
targetstringoptional
ipstringoptional
detailstringoptional
nextBeforeintegeroptionalPass as before for the next page. Null when there is no next page.
actionsarray of stringrequiredEvery 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.

StatuscodeWhen
400bad_requestan unknown actorKind
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
actionquerystringoptionalExact action, or a prefix ending in . such as auth..
actorIdquerystringoptional
actorKindquerystringoptional
fromquerystringoptionalRFC 3339. Inclusive.
toquerystringoptionalRFC 3339. Exclusive, so paging by at cannot repeat a row.
beforequeryintegeroptionalPage backwards: only rows with a smaller id than this one.
limitqueryintegeroptional
qquerystringoptionalMatched against the action, the target and the detail.

Success. 200.

JSON object.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
linkMinutesintegerrequired
defaultMinutesintegerrequired
minMinutesintegerrequired
maxMinutesintegerrequired
resumeHoursintegerrequiredHow 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
linkMinutesintegerrequired
{
  "linkMinutes": 1
}

Success. 200.

FieldTypeMeaning
linkMinutesintegerrequired
defaultMinutesintegerrequired
minMinutesintegerrequired
maxMinutesintegerrequired
resumeHoursintegerrequiredHow 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.

StatuscodeWhen
400bad_requesta value outside its allowed range
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
passwordMinLengthintegerrequired
passwordRequireMixedCasebooleanrequired
passwordRequireDigitbooleanrequired
passwordRequireSymbolbooleanrequired
requireTotpbooleanrequiredWhether every account must carry an authenticator code, rather than each person deciding for themselves.
lockoutAttemptsintegerrequired
lockoutMinutesintegerrequired
sessionIdleMinutesintegerrequired
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
passwordMinLengthintegerrequired
passwordRequireMixedCasebooleanrequired
passwordRequireDigitbooleanrequired
passwordRequireSymbolbooleanrequired
requireTotpbooleanrequired
lockoutAttemptsintegerrequired
lockoutMinutesintegerrequired
sessionIdleMinutesintegerrequired
{
  "passwordMinLength": 1,
  "passwordRequireMixedCase": true,
  "passwordRequireDigit": true,
  "passwordRequireSymbol": true,
  "requireTotp": true,
  "lockoutAttempts": 1,
  "lockoutMinutes": 1,
  "sessionIdleMinutes": 1
}

Success. 200.

FieldTypeMeaning
passwordMinLengthintegerrequired
passwordRequireMixedCasebooleanrequired
passwordRequireDigitbooleanrequired
passwordRequireSymbolbooleanrequired
requireTotpbooleanrequiredWhether every account must carry an authenticator code, rather than each person deciding for themselves.
lockoutAttemptsintegerrequired
lockoutMinutesintegerrequired
sessionIdleMinutesintegerrequired
{
  "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.

StatuscodeWhen
400bad_requesta value outside its allowed range
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
appearanceobjectrequired

It is an object:

FieldTypeMeaning
companyNamestringrequiredEmpty means there is no company name and the product name should be used instead.
accentstringrequired
logostringrequiredA data URL, or empty for no logo.
logoDarkstringrequiredA version of the logo for dark backgrounds, or empty to use logo everywhere.
eyebrowstringrequired
headlinestringrequired
subtextstringrequired
linkLabelstringrequiredBoth empty, or both set.
linkUrlstringrequired
cardTitlestringrequired
cardSubtitlestringrequired
globebooleanrequired
routeFromstringrequired
routeTostringrequired
footerLinksarray of objectsrequired

Each item is an object:

FieldTypeMeaning
labelstringrequired
urlstringrequired
noticestringrequired
accentContrastnumberrequiredThe 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.
includedbooleanrequiredWhether 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
companyNamestringrequired
accentstringrequired
logostringrequiredEmpty removes the logo, which is the only way to go back to the product's own mark once one has been uploaded.
logoDarkstringoptional
eyebrowstringoptional
headlinestringoptional
subtextstringoptional
linkLabelstringoptional
linkUrlstringoptional
cardTitlestringoptional
cardSubtitlestringoptional
globebooleanoptional
routeFromstringoptional
routeTostringoptional
footerLinksarray of objectsoptional

Each item is an object:

FieldTypeMeaning
labelstringrequired
urlstringrequired
noticestringoptional
{
  "companyName": "example",
  "accent": "example",
  "logo": "example"
}

Success. 200.

FieldTypeMeaning
appearanceobjectrequired

It is an object:

FieldTypeMeaning
companyNamestringrequiredEmpty means there is no company name and the product name should be used instead.
accentstringrequired
logostringrequiredA data URL, or empty for no logo.
logoDarkstringrequiredA version of the logo for dark backgrounds, or empty to use logo everywhere.
eyebrowstringrequired
headlinestringrequired
subtextstringrequired
linkLabelstringrequiredBoth empty, or both set.
linkUrlstringrequired
cardTitlestringrequired
cardSubtitlestringrequired
globebooleanrequired
routeFromstringrequired
routeTostringrequired
footerLinksarray of objectsrequired

Each item is an object:

FieldTypeMeaning
labelstringrequired
urlstringrequired
noticestringrequired
accentContrastnumberrequiredThe 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.
includedbooleanrequiredWhether 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.

StatuscodeWhen
400bad_requesta 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
402licence_requiredthis server's license does not include custom branding
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
appearanceobjectrequired

It is an object:

FieldTypeMeaning
companyNamestringrequiredEmpty means there is no company name and the product name should be used instead.
accentstringrequired
logostringrequiredA data URL, or empty for no logo.
logoDarkstringrequiredA version of the logo for dark backgrounds, or empty to use logo everywhere.
eyebrowstringrequired
headlinestringrequired
subtextstringrequired
linkLabelstringrequiredBoth empty, or both set.
linkUrlstringrequired
cardTitlestringrequired
cardSubtitlestringrequired
globebooleanrequired
routeFromstringrequired
routeTostringrequired
footerLinksarray of objectsrequired

Each item is an object:

FieldTypeMeaning
labelstringrequired
urlstringrequired
noticestringrequired
accentContrastnumberrequiredThe 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.
includedbooleanrequiredWhether 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
idstringrequiredThe id.
namestringrequiredThe name.
ownerIdstringrequiredThe id.
ownerEmailstringrequiredThe address, because an id tells whoever has to judge the key nothing. Kept rather than deleted accounts are what make this resolvable.
scopesarray of stringrequired
createdAtstringrequiredA time, as RFC 3339.
expiresAtstringoptional, left out when emptyA time, as RFC 3339.
lastUsedAtstringoptional, left out when emptyAbsent on a key that has never been used, which is itself the answer to "can we take this one away".
revokedbooleanrequiredThe plain answer, so the list can be read without working out what a null timestamp means.
revokedAtstringoptional, left out when emptyA time, as RFC 3339.
clientsarray of ClientSeenoptional, left out when emptyThe Farwing clients the key was last used from. In the list of keys only.

Each item is an object:

FieldTypeMeaning
clientstringrequiredcli or desktop.
versionstringrequiredThe version the client gave, such as 0.5.0.
seenAtstringrequiredWhen it was last seen, RFC 3339.
[
  {
    "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
maxDaysintegerrequiredThe longest life anyone may ask for, in days.
allowNeverbooleanrequiredWhether 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
maxDaysintegerrequiredBoth 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.
allowNeverbooleanrequired
{
  "maxDays": 1,
  "allowNever": true
}

Success. 200.

FieldTypeMeaning
maxDaysintegerrequiredThe longest life anyone may ask for, in days.
allowNeverbooleanrequiredWhether 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.

StatuscodeWhen
400bad_requesta value outside its allowed range
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Success. 200.

FieldTypeMeaning
idstringrequiredThe id.
namestringrequiredThe name.
ownerIdstringrequiredThe id.
ownerEmailstringrequiredThe address, because an id tells whoever has to judge the key nothing. Kept rather than deleted accounts are what make this resolvable.
scopesarray of stringrequired
createdAtstringrequiredA time, as RFC 3339.
expiresAtstringoptional, left out when emptyA time, as RFC 3339.
lastUsedAtstringoptional, left out when emptyAbsent on a key that has never been used, which is itself the answer to "can we take this one away".
revokedbooleanrequiredThe plain answer, so the list can be read without working out what a null timestamp means.
revokedAtstringoptional, left out when emptyA time, as RFC 3339.
clientsarray of ClientSeenoptional, left out when emptyThe Farwing clients the key was last used from. In the list of keys only.

Each item is an object:

FieldTypeMeaning
clientstringrequiredcli or desktop.
versionstringrequiredThe version the client gave, such as 0.5.0.
seenAtstringrequiredWhen it was last seen, RFC 3339.
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
versionstringrequired
buildstringrequiredThe version and the commit it was built from, such as 0.4.0 (abc1234).
installIdstringrequiredThe id.
startedAtstringrequiredWhen this process started, not when the installation was made.
uptimeSecondsintegerrequiredCounted 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.
databaseobjectrequired

It is an object:

FieldTypeMeaning
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
bytesintegeroptionalAbsent when the file cannot be stated, which is itself worth seeing: a zero would read as an empty database.
schemaVersionintegerrequired
diskarray of objectsrequired

Each item is an object:

FieldTypeMeaning
namestringrequiredWhich volume this is, in the words the administrator chose: the data directory, or the name they gave the storage location.
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
bytesFreeintegeroptionalNull where this build cannot measure it. See the module note on why that is not filled in with a guess.
bytesTotalintegeroptional
checksarray of objectsrequired

Each item is an object:

FieldTypeMeaning
namestringrequiredThe name.
okbooleanrequired
detailstringrequiredWhat was found, in a sentence. Read by an administrator, so it carries the error the operating system or the database gave rather than a category: "permission denied" and "no such file" need different fixes and only the caller of the failing call knows which it was.
backupsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
namestringrequiredThe name.
bytesintegerrequiredSize in bytes.
modifiedAtstringrequiredA time, as RFC 3339.
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
fromquerystringoptionalRFC 3339. Inclusive. Absent means no lower bound.
toquerystringoptionalRFC 3339. Inclusive. Absent means no upper bound.
limitqueryintegeroptionalHow many checks to return, from 1 to 200. Defaults to 50. A larger number is brought down to 200.
offsetqueryintegeroptionalHow many matching checks to skip. Defaults to 0.

Success. 200.

FieldTypeMeaning
runsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
statestringrequiredWhere this record is in its life.
verdictstringrequired
limiterStageIdoptional
receiveBpsintegeroptional
sendBpsintegeroptional
startedAtstringrequiredA time, as RFC 3339.
totalintegerrequiredHow many checks match from and to, including ones not on this page.
scheduleobjectrequired

It is an object:

FieldTypeMeaning
enabledbooleanrequired
weekdayintegerrequired
minuteintegerrequired
addressesarray of stringrequired
lastRunAtstringoptionalA time, as RFC 3339.
includedbooleanrequiredWhether the license includes automation, which scheduled checks need and the check itself does not.
transfersRunningintegerrequired
estimatedSecondsintegerrequired
placesarray of objectsrequiredEvery place a check can time, so the screen can say what this run will cover before it starts.

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
titlestringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
rolestringrequired
regionstringoptional
savedBucketIdsarray of stringrequiredBuckets 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.

StatuscodeWhen
400bad_requesta time that is not RFC 3339, a page size below 1, or a page that starts before 0
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
confirmBusybooleanoptional
testSizeMbintegeroptionalMegabytes written to each location. Moved into the allowed range; see [store::MIN_TEST_MB].
bucketIdsarray of stringoptionalBuckets 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.

FieldTypeMeaning
outcomeobjectrequired
idstringrequiredThe id.
statestringrequiredWhere this record is in its life.
triggerstringrequired
progressPercentintegerrequired
progressStepstringoptional
errorstringoptional
serverVersionstringrequired
licensePlanstringrequired
startedAtstringrequiredA time, as RFC 3339.
finishedAtstringoptionalA time, as RFC 3339.
currentUdpAdviceAdviceoptionalThe 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:

FieldTypeMeaning
idstringrequiredThe id.
textstringrequired
whystringrequiredThe page in the documentation that explains it.
fixstringoptional
fixExplainsstringoptional
severitystringrequiredlimit, warning or note.
{
  "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.

StatuscodeWhen
409conflicttransfers are running and `confirmBusy` was not set
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
enabledbooleanoptional
weekdayintegeroptionalRead as a number of any size so that 9 is refused in words rather than by the parser.
minuteintegeroptional
addressesarray of stringoptional
{
  "enabled": true,
  "weekday": 1,
  "minute": 1,
  "addresses": [
    "example"
  ]
}

Success. 200.

FieldTypeMeaning
enabledbooleanrequired
weekdayintegerrequired
minuteintegerrequired
addressesarray of stringrequired
lastRunAtstringoptionalA time, as RFC 3339.
includedbooleanrequiredWhether 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.

StatuscodeWhen
400bad_requesta day outside 0 to 6, a time outside 0 to 1439, an address that is not one, or more than 10 addresses
402licence_requiredturning it on, when this server's license does not include automation
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Success. 200.

FieldTypeMeaning
outcomeobjectrequired
idstringrequiredThe id.
statestringrequiredWhere this record is in its life.
triggerstringrequired
progressPercentintegerrequired
progressStepstringoptional
errorstringoptional
serverVersionstringrequired
licensePlanstringrequired
startedAtstringrequiredA time, as RFC 3339.
finishedAtstringoptionalA time, as RFC 3339.
currentUdpAdviceAdviceoptionalThe 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:

FieldTypeMeaning
idstringrequiredThe id.
textstringrequired
whystringrequiredThe page in the documentation that explains it.
fixstringoptional
fixExplainsstringoptional
severitystringrequiredlimit, warning or note.
{
  "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.

StatuscodeWhen
404not_foundno such check
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Success. 200.

The reply has no body.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
404not_foundno such check
409conflictthe check is still running
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Success. 200.

The body is plain text, not JSON.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
404not_foundno such check
409conflictthe check is still running; ask again when it has finished
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe license does not include the REST API
{
  "error": {
    "code": "not_found",
    "message": "not found"
  }
}

Email

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.

FieldTypeMeaning
includedbooleanrequiredThe licence includes branding, so writes are accepted.
freeModebooleanrequiredSomething custom is stored and is switched off for want of a licence.
serverLanguagestringrequired
languagesarray of stringrequired
layoutobjectrequired

It is an object:

FieldTypeMeaning
custombooleanrequired
versionintegeroptional
hasDraftbooleanrequired
kindsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
groupstringrequired
titlestringrequired
aboutstringrequired
securitybooleanrequired
canDisablebooleanrequired
enabledbooleanrequired
custombooleanrequired
hasDraftbooleanrequired
languagesarray of stringrequired
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.
langquerystringoptionalThe language, such as en or pt-BR. This server's language when it is left out.

Success. 200.

FieldTypeMeaning
kindobjectrequired

It is an object:

FieldTypeMeaning
idstringrequiredThe id.
groupstringrequired
titlestringrequired
aboutstringrequired
securitybooleanrequired
canDisablebooleanrequired
enabledbooleanrequired
custombooleanrequired
hasDraftbooleanrequired
languagesarray of stringrequired
langstringrequired
fallsBackTostringoptionalThe 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.
variablesarray of objectsrequired

Each item is an object:

FieldTypeMeaning
groupstringrequired
varsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
labelstringrequired
aboutstringrequired
samplestringrequired
kindobjectrequired
requiredLocksarray of stringrequired
lockWordingobjectrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
publishedPublishedoptional

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
{
  "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.

StatuscodeWhen
404not_foundno such email type
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.
langquerystringoptionalThe language, such as en or pt-BR. This server's language when it is left out.

Request body. JSON.

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
{
  "subject": [
    {}
  ],
  "previewText": [
    {}
  ],
  "plainText": "example",
  "doc": {
    "blocks": [
      {}
    ]
  }
}

Success. 200.

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
{
  "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.

StatuscodeWhen
400bad_requestthe document is not a shape this server understands
402licence_requiredthis server's license does not include email templates
404not_foundno such email type
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.
langquerystringoptionalThe language, such as en or pt-BR. This server's language when it is left out.

Success. 200.

FieldTypeMeaning
kindobjectrequired

It is an object:

FieldTypeMeaning
idstringrequiredThe id.
groupstringrequired
titlestringrequired
aboutstringrequired
securitybooleanrequired
canDisablebooleanrequired
enabledbooleanrequired
custombooleanrequired
hasDraftbooleanrequired
languagesarray of stringrequired
langstringrequired
fallsBackTostringoptionalThe 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.
variablesarray of objectsrequired

Each item is an object:

FieldTypeMeaning
groupstringrequired
varsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
labelstringrequired
aboutstringrequired
samplestringrequired
kindobjectrequired
requiredLocksarray of stringrequired
lockWordingobjectrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
publishedPublishedoptional

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
{
  "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.

StatuscodeWhen
402licence_requiredthis server's license does not include email templates
404not_foundno such email type
409conflictthere is no draft
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.
langquerystringoptionalThe language, such as en or pt-BR. This server's language when it is left out.

Request body. JSON.

FieldTypeMeaning
notestringoptional
{
  "note": "The cut is in the folder."
}

Success. 200.

FieldTypeMeaning
kindobjectrequired

It is an object:

FieldTypeMeaning
idstringrequiredThe id.
groupstringrequired
titlestringrequired
aboutstringrequired
securitybooleanrequired
canDisablebooleanrequired
enabledbooleanrequired
custombooleanrequired
hasDraftbooleanrequired
languagesarray of stringrequired
langstringrequired
fallsBackTostringoptionalThe 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.
variablesarray of objectsrequired

Each item is an object:

FieldTypeMeaning
groupstringrequired
varsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
labelstringrequired
aboutstringrequired
samplestringrequired
kindobjectrequired
requiredLocksarray of stringrequired
lockWordingobjectrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
publishedPublishedoptional

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
{
  "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.

StatuscodeWhen
400bad_requestthe wording has a problem that must be fixed first
402licence_requiredthis server's license does not include email templates
404not_foundno such email type
409conflictthere is no draft
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.
langquerystringoptionalThe language, such as en or pt-BR. This server's language when it is left out.

Request body. None.

Success. 200.

FieldTypeMeaning
kindobjectrequired

It is an object:

FieldTypeMeaning
idstringrequiredThe id.
groupstringrequired
titlestringrequired
aboutstringrequired
securitybooleanrequired
canDisablebooleanrequired
enabledbooleanrequired
custombooleanrequired
hasDraftbooleanrequired
languagesarray of stringrequired
langstringrequired
fallsBackTostringoptionalThe 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.
variablesarray of objectsrequired

Each item is an object:

FieldTypeMeaning
groupstringrequired
varsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
labelstringrequired
aboutstringrequired
samplestringrequired
kindobjectrequired
requiredLocksarray of stringrequired
lockWordingobjectrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
publishedPublishedoptional

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
{
  "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.

StatuscodeWhen
402licence_requiredthis server's license does not include email templates
404not_foundno such email type
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.
langquerystringoptionalThe language, such as en or pt-BR. This server's language when it is left out.

Request body. JSON.

FieldTypeMeaning
versionintegerrequired
{
  "version": 1
}

Success. 200.

FieldTypeMeaning
kindobjectrequired

It is an object:

FieldTypeMeaning
idstringrequiredThe id.
groupstringrequired
titlestringrequired
aboutstringrequired
securitybooleanrequired
canDisablebooleanrequired
enabledbooleanrequired
custombooleanrequired
hasDraftbooleanrequired
languagesarray of stringrequired
langstringrequired
fallsBackTostringoptionalThe 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.
variablesarray of objectsrequired

Each item is an object:

FieldTypeMeaning
groupstringrequired
varsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
labelstringrequired
aboutstringrequired
samplestringrequired
kindobjectrequired
requiredLocksarray of stringrequired
lockWordingobjectrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
publishedPublishedoptional

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
{
  "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.

StatuscodeWhen
402licence_requiredthis server's license does not include email templates
404not_foundno such email type, or no such version
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.

Request body. JSON.

FieldTypeMeaning
langstringrequired
copyFromstringoptionalThe language to copy from. Empty copies the built-in wording.
{
  "lang": "example",
  "copyFrom": "example"
}

Success. 200.

FieldTypeMeaning
kindobjectrequired

It is an object:

FieldTypeMeaning
idstringrequiredThe id.
groupstringrequired
titlestringrequired
aboutstringrequired
securitybooleanrequired
canDisablebooleanrequired
enabledbooleanrequired
custombooleanrequired
hasDraftbooleanrequired
languagesarray of stringrequired
langstringrequired
fallsBackTostringoptionalThe 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.
variablesarray of objectsrequired

Each item is an object:

FieldTypeMeaning
groupstringrequired
varsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
labelstringrequired
aboutstringrequired
samplestringrequired
kindobjectrequired
requiredLocksarray of stringrequired
lockWordingobjectrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
publishedPublishedoptional

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
{
  "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.

StatuscodeWhen
400bad_requestnot a language code such as en or pt-BR
402licence_requiredthis server's license does not include email templates
404not_foundno such email type
409conflictthat language is already there
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.
langpathstringrequiredThe language, such as pt-BR.

Success. 200.

FieldTypeMeaning
idstringrequiredThe id.
groupstringrequired
titlestringrequired
aboutstringrequired
securitybooleanrequired
canDisablebooleanrequired
enabledbooleanrequired
custombooleanrequired
hasDraftbooleanrequired
languagesarray of stringrequired
{
  "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.

StatuscodeWhen
402licence_requiredthis server's license does not include email templates
404not_foundno such email type, or no such language
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.

Request body. JSON.

FieldTypeMeaning
enabledbooleanrequired
{
  "enabled": true
}

Success. 200.

FieldTypeMeaning
idstringrequiredThe id.
groupstringrequired
titlestringrequired
aboutstringrequired
securitybooleanrequired
canDisablebooleanrequired
enabledbooleanrequired
custombooleanrequired
hasDraftbooleanrequired
languagesarray of stringrequired
{
  "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.

StatuscodeWhen
400bad_requestthis email cannot be switched off
402licence_requiredthis server's license does not include email templates
404not_foundno such email type
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.
langquerystringoptionalThe language, such as en or pt-BR. This server's language when it is left out.

Request body. JSON.

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
datastringoptionalsample, long, empty or real.
realIdstringoptionalThe package or user to fill real from.
{
  "body": {
    "subject": [
      {}
    ],
    "previewText": [
      {}
    ],
    "plainText": "example",
    "doc": {
      "blocks": [
        {}
      ]
    }
  },
  "data": "example",
  "realId": "k7Qm2sLp9vX4aB1c"
}

Success. 200.

FieldTypeMeaning
subjectstringrequired
previewTextstringrequired
htmlstringrequired
textstringrequired
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
{
  "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.

StatuscodeWhen
400bad_requestthe document is not a shape this server understands
404not_foundno such email type
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
kindpathstringrequiredThe email type, such as package.received.
langquerystringoptionalThe language, such as en or pt-BR. This server's language when it is left out.

Request body. JSON.

FieldTypeMeaning
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
datastringoptionalsample, long, empty or real.
realIdstringoptionalThe package or user to fill real from.
{
  "body": {
    "subject": [
      {}
    ],
    "previewText": [
      {}
    ],
    "plainText": "example",
    "doc": {
      "blocks": [
        {}
      ]
    }
  },
  "data": "example",
  "realId": "k7Qm2sLp9vX4aB1c"
}

Success. 200.

FieldTypeMeaning
sentbooleanrequired
tostringrequired
{
  "sent": true,
  "to": "projects/rush"
}

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
400bad_requestthe wording has a problem, or the mail relay refused it
402licence_requiredthis server's license does not include email templates
404not_foundno such email type
503unavailablethis server is not set up to send email
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
packagesarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
labelstringrequired
subjectstringrequired
senderstringrequired
fileCountintegerrequired
totalSizeintegerrequired
createdAtstringrequiredA time, as RFC 3339.
usersarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
labelstringrequired
namestringrequiredThe name.
emailstringrequiredAn email address.
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
includedbooleanrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
publishedPublishedLayoutoptional

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftLayoutViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
contrastobjectrequired

It is an object:

FieldTypeMeaning
buttonnumberrequiredHow well white text reads on the button colour.
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
{
  "logo": "example",
  "logoAlt": "example",
  "brandColour": "example",
  "buttonColour": "example",
  "headerText": [
    {}
  ],
  "footerText": [
    {}
  ],
  "showServerUrl": true
}

Success. 200.

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
{
  "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.

StatuscodeWhen
400bad_requesta colour that is not #rrggbb, a logo that is not a PNG or an SVG, or one over 256 KiB
402licence_requiredthis server's license does not include email templates
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
includedbooleanrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
publishedPublishedLayoutoptional

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftLayoutViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
contrastobjectrequired

It is an object:

FieldTypeMeaning
buttonnumberrequiredHow well white text reads on the button colour.
{
  "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.

StatuscodeWhen
402licence_requiredthis server's license does not include email templates
409conflictthere is no draft
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
notestringoptional
{
  "note": "The cut is in the folder."
}

Success. 200.

FieldTypeMeaning
includedbooleanrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
publishedPublishedLayoutoptional

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftLayoutViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
contrastobjectrequired

It is an object:

FieldTypeMeaning
buttonnumberrequiredHow well white text reads on the button colour.
{
  "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.

StatuscodeWhen
400bad_requestthe layout has a problem that must be fixed first
402licence_requiredthis server's license does not include email templates
409conflictthere is no draft
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
includedbooleanrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
publishedPublishedLayoutoptional

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftLayoutViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
contrastobjectrequired

It is an object:

FieldTypeMeaning
buttonnumberrequiredHow well white text reads on the button colour.
{
  "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.

StatuscodeWhen
402licence_requiredthis server's license does not include email templates
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
versionintegerrequired
{
  "version": 1
}

Success. 200.

FieldTypeMeaning
includedbooleanrequired
defaultobjectoptional

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
publishedPublishedLayoutoptional

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
draftDraftLayoutViewoptional

It is an object:

FieldTypeMeaning
draftobjectrequired

It is an object:

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
updatedAtstringrequiredA time, as RFC 3339.
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
versionsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
versionintegerrequired
atstringrequired
bystringrequired
notestringrequired
contrastobjectrequired

It is an object:

FieldTypeMeaning
buttonnumberrequiredHow well white text reads on the button colour.
{
  "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.

StatuscodeWhen
402licence_requiredthis server's license does not include email templates
404not_foundno such version
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
layoutobjectrequired

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
kindstringoptionalThe email to draw it around. package.received when left out.
datastringoptional
{
  "layout": {
    "logo": "example",
    "logoAlt": "example",
    "brandColour": "example",
    "buttonColour": "example",
    "headerText": [
      {}
    ],
    "footerText": [
      {}
    ],
    "showServerUrl": true
  },
  "kind": "shared",
  "data": "example"
}

Success. 200.

FieldTypeMeaning
subjectstringrequired
previewTextstringrequired
htmlstringrequired
textstringrequired
problemsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
whereobjectrequiredwhere on the wire.
blockIdstringoptionalThe id.
messagestringrequired
suggestionstringoptionalThe id of the variable that was probably meant.
{
  "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.

StatuscodeWhen
400bad_requestthe layout is not a shape this server understands
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
versionintegerrequired
templatesarray of objectsoptional

Each item is an object:

FieldTypeMeaning
kindobjectrequired
langstringrequired
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
layoutLayoutoptionalNone when the layout is the built-in one.

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
enabledobjectoptionalThe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
versionintegerrequired
templatesarray of objectsoptional

Each item is an object:

FieldTypeMeaning
kindobjectrequired
langstringrequired
bodyobjectrequired

It is an object:

FieldTypeMeaning
subjectarray of Inlinerequired
previewTextarray of Inlinerequired
plainTextstringoptionalThe administrator's own plain-text version, or empty to have one made from the document.
docobjectrequired

It is an object:

FieldTypeMeaning
blocksarray of Blockrequired
layoutLayoutoptionalNone when the layout is the built-in one.

It is an object:

FieldTypeMeaning
logostringrequiredEmpty, or a data: URL of a PNG or SVG. Checked by whoever saves it; see [super::validate::layout].
logoAltstringrequired
brandColourstringrequired#rrggbb. Used for links and for the server's name when there is no logo.
buttonColourstringrequired#rrggbb. The button's background.
headerTextarray of Inlinerequired
footerTextarray of Inlinerequired
showServerUrlbooleanrequired
enabledobjectoptionalThe 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.

FieldTypeMeaning
templatesarray of stringrequiredkind/lang of every template that is now a new version.
removedarray of stringrequiredkind/lang of every template that was here and is not in the file, and so was removed.
unchangedintegerrequiredTemplates in the file that were already live, word for word.
layoutstringrequiredreplaced, removed or unchanged.
switchesChangedintegerrequiredHow 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.

StatuscodeWhen
400bad_requestthe file is not one of ours, or something in it would not publish
402licence_requiredthis server's license does not include email templates
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
hostnamestringrequiredWhat the next start will use: the config file, then the environment.
dataPortintegerrequired
webPortintegerrequired
runningobjectrequiredWhat this process is using now.

It is an object:

FieldTypeMeaning
hostnamestringrequired
webPortintegerrequired
dataPortintegerrequired
pendingarray of stringrequiredWhat a restart would change, one line each.
pinnedobjectrequiredThe 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:

FieldTypeMeaning
hostnamestringoptional
webPortstringoptional
dataPortstringoptional
lowPortsobjectrequiredWhether this server may open ports below 1024.
problemsarray of objectsrequiredWhat would go wrong opening the saved ports, found by trying them.

Each item is an object:

FieldTypeMeaning
blockingbooleanrequiredTrue when the server would come back without the thing this port is for. False for the TCP fallback, which transfers can do without.
textstringrequired
notesarray of stringrequiredWhat start-up had to work around, such as a port it could not open.
rulesarray of objectsrequiredWhat 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:

FieldTypeMeaning
portintegerrequired
protocolstringrequired
whatstringrequired
costIfClosedstringrequired
dataServiceRunningbooleanrequiredWhether the data service is actually listening.
checkedFromOutsidebooleanrequiredAlways 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
hostnamestringoptionalThe name people will reach this server by. Empty clears it; absent leaves it as it is.
webPortintegeroptionalTCP, 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.
dataPortintegeroptionalUDP for transfers, and TCP on the same number for the fallback.
{
  "hostname": "example",
  "webPort": 443,
  "dataPort": 443
}

Success. 200.

FieldTypeMeaning
hostnamestringrequiredWhat the next start will use: the config file, then the environment.
dataPortintegerrequired
webPortintegerrequired
runningobjectrequiredWhat this process is using now.

It is an object:

FieldTypeMeaning
hostnamestringrequired
webPortintegerrequired
dataPortintegerrequired
pendingarray of stringrequiredWhat a restart would change, one line each.
pinnedobjectrequiredThe 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:

FieldTypeMeaning
hostnamestringoptional
webPortstringoptional
dataPortstringoptional
lowPortsobjectrequiredWhether this server may open ports below 1024.
problemsarray of objectsrequiredWhat would go wrong opening the saved ports, found by trying them.

Each item is an object:

FieldTypeMeaning
blockingbooleanrequiredTrue when the server would come back without the thing this port is for. False for the TCP fallback, which transfers can do without.
textstringrequired
notesarray of stringrequiredWhat start-up had to work around, such as a port it could not open.
rulesarray of objectsrequiredWhat 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:

FieldTypeMeaning
portintegerrequired
protocolstringrequired
whatstringrequired
costIfClosedstringrequired
dataServiceRunningbooleanrequiredWhether the data service is actually listening.
checkedFromOutsidebooleanrequiredAlways 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.

StatuscodeWhen
400bad_requestnot a host name, a port out of range, a port fixed by the environment, or a port this server cannot open
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
checkedFromOutsidebooleanrequiredWhether farwing.io actually ran the check.
skippedReasonstringoptional, left out when empty
addressstringoptional, left out when emptyThe address farwing.io connected to: this server's outgoing address.
hoststringrequired
hostMatchesbooleanoptional, left out when emptyWhether the host name points at the address that was tried. None when the check did not run.
hostAddressesarray of stringoptional
resultsarray of objectsrequired

Each item is an object:

FieldTypeMeaning
portintegerrequired
protocolstringrequired
openbooleanoptionalNone for anything that was not tried.
statestringoptionalopen, refused, timeout, error or untested; and for UDP, oneway when packets arrived here but the replies did not get back.
detailstringoptional
bytesintegeroptionalFor UDP: the size of the test file that came through.
msintegeroptionalFor UDP: how long the download took, in milliseconds.
{
  "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.

StatuscodeWhen
400bad_requesta check ran a moment ago
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
idstringrequiredThe id.
ticketstringrequiredFor farwing cp --ticket. Names no TCP port, and works once.
commandstringoptional, left out when emptyThe whole command, when the server knows the name it is reached by. Without one the client needs --ticket-host as well.
fileNamestringrequiredThe file's name.
bytesintegerrequiredSize in bytes.
portintegerrequired
expiresAtstringrequiredA 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.

StatuscodeWhen
409conflictfour tests are already waiting
503unavailablethe transfer port is not open
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Success. 200.

FieldTypeMeaning
idstringrequiredThe id.
statestringrequiredwaiting, receiving, passed, failed or expired.
bytesintegeroptional, left out when emptySize in bytes.
secondsnumberoptional, left out when empty
fromstringoptional, left out when emptyThe address the download came from.
detailstringoptional, left out when empty
expiresAtstringrequiredA 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.

StatuscodeWhen
404not_foundno such test, or it is more than ten minutes old
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
sourceobjectrequired

It is an object:

FieldTypeMeaning
rootstringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
relobjectrequired
trustedbooleanrequiredIssued by somebody browsers already trust.
certificateDetailsoptional

It is an object:

FieldTypeMeaning
subjectstringrequired
namesarray of stringrequiredThe names it is valid for: the alternative names, or the common name when there are none.
issuerstringrequired
notBeforestringrequired
notAfterstringrequired
selfSignedbooleanrequired
chainLengthintegerrequiredCertificates served, the server's own included.
keyTypestringrequired
fingerprintSha256stringrequired
daysLeftintegeroptional
lifetimeDaysintegeroptional
hostnamestringrequiredThe address the next start uses.
coversHostnamebooleanrequired
autoRenewbooleanrequired
renewalRenewaloptional

It is an object:

FieldTypeMeaning
renewAtstringrequiredA time, as RFC 3339.
nextAttemptstringoptional
lastAttemptstringoptional
lastErrorstringoptional
failuresintegerrequired
warningWarningoptional

It is an object:

FieldTypeMeaning
kindstringrequiredexpired, expiring or renewal-failed.
messagestringrequired
progressobjectrequired

It is an object:

FieldTypeMeaning
stageobjectrequired
renewalbooleanrequiredA renewal rather than a first certificate.
errorFailureoptional

It is an object:

FieldTypeMeaning
messagestringrequired
detailstringoptionalLet's Encrypt's own words, when it gave any.
atstringrequired
acmeobjectrequired

It is an object:

FieldTypeMeaning
emailstringrequiredThe contact address used last time, or the caller's own.
termsUrlstringrequired
listenerobjectrequired
planarray of ChallengerequiredThe ways this server could be checked, best first.
webPortintegerrequired
notesarray of stringrequired
bootFingerprintstringrequiredSHA-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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
sourceobjectrequired

It is an object:

FieldTypeMeaning
rootstringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
relobjectrequired
trustedbooleanrequiredIssued by somebody browsers already trust.
certificateDetailsoptional

It is an object:

FieldTypeMeaning
subjectstringrequired
namesarray of stringrequiredThe names it is valid for: the alternative names, or the common name when there are none.
issuerstringrequired
notBeforestringrequired
notAfterstringrequired
selfSignedbooleanrequired
chainLengthintegerrequiredCertificates served, the server's own included.
keyTypestringrequired
fingerprintSha256stringrequired
daysLeftintegeroptional
lifetimeDaysintegeroptional
hostnamestringrequiredThe address the next start uses.
coversHostnamebooleanrequired
autoRenewbooleanrequired
renewalRenewaloptional

It is an object:

FieldTypeMeaning
renewAtstringrequiredA time, as RFC 3339.
nextAttemptstringoptional
lastAttemptstringoptional
lastErrorstringoptional
failuresintegerrequired
warningWarningoptional

It is an object:

FieldTypeMeaning
kindstringrequiredexpired, expiring or renewal-failed.
messagestringrequired
progressobjectrequired

It is an object:

FieldTypeMeaning
stageobjectrequired
renewalbooleanrequiredA renewal rather than a first certificate.
errorFailureoptional

It is an object:

FieldTypeMeaning
messagestringrequired
detailstringoptionalLet's Encrypt's own words, when it gave any.
atstringrequired
acmeobjectrequired

It is an object:

FieldTypeMeaning
emailstringrequiredThe contact address used last time, or the caller's own.
termsUrlstringrequired
listenerobjectrequired
planarray of ChallengerequiredThe ways this server could be checked, best first.
webPortintegerrequired
notesarray of stringrequired
bootFingerprintstringrequiredSHA-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.

StatuscodeWhen
409conflicta certificate is being requested; wait for it to finish
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
eligiblebooleanrequiredNothing on the list is red.
hostnamestringrequired
publicAddressstringoptionalThe address this server reaches the internet from, as farwing.io saw it.
dnsAddressesarray of stringrequired
challengeChallengeoptionalHow Let's Encrypt will check, if it can.
itemsarray of objectsrequiredThe records in this reply.

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
stateobjectrequiredWhere this record is in its life.
titlestringrequired
detailstringrequiredWhat was found, and when it is not good, exactly what to change.
checkedFromOutsidebooleanrequired
{
  "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.

StatuscodeWhen
400bad_requesta check ran a moment ago
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
emailstringoptionalWhere Let's Encrypt sends notices. Empty for none.
agreeTermsbooleanrequired
{
  "email": "[email protected]",
  "agreeTerms": true
}

Success. 202.

FieldTypeMeaning
sourceobjectrequired

It is an object:

FieldTypeMeaning
rootstringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
relobjectrequired
trustedbooleanrequiredIssued by somebody browsers already trust.
certificateDetailsoptional

It is an object:

FieldTypeMeaning
subjectstringrequired
namesarray of stringrequiredThe names it is valid for: the alternative names, or the common name when there are none.
issuerstringrequired
notBeforestringrequired
notAfterstringrequired
selfSignedbooleanrequired
chainLengthintegerrequiredCertificates served, the server's own included.
keyTypestringrequired
fingerprintSha256stringrequired
daysLeftintegeroptional
lifetimeDaysintegeroptional
hostnamestringrequiredThe address the next start uses.
coversHostnamebooleanrequired
autoRenewbooleanrequired
renewalRenewaloptional

It is an object:

FieldTypeMeaning
renewAtstringrequiredA time, as RFC 3339.
nextAttemptstringoptional
lastAttemptstringoptional
lastErrorstringoptional
failuresintegerrequired
warningWarningoptional

It is an object:

FieldTypeMeaning
kindstringrequiredexpired, expiring or renewal-failed.
messagestringrequired
progressobjectrequired

It is an object:

FieldTypeMeaning
stageobjectrequired
renewalbooleanrequiredA renewal rather than a first certificate.
errorFailureoptional

It is an object:

FieldTypeMeaning
messagestringrequired
detailstringoptionalLet's Encrypt's own words, when it gave any.
atstringrequired
acmeobjectrequired

It is an object:

FieldTypeMeaning
emailstringrequiredThe contact address used last time, or the caller's own.
termsUrlstringrequired
listenerobjectrequired
planarray of ChallengerequiredThe ways this server could be checked, best first.
webPortintegerrequired
notesarray of stringrequired
bootFingerprintstringrequiredSHA-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.

StatuscodeWhen
400bad_requestthe terms were not accepted, the email is not an address, or the address is not a public name
409conflicta request is already running
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
sourceobjectrequired

It is an object:

FieldTypeMeaning
rootstringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
relobjectrequired
trustedbooleanrequiredIssued by somebody browsers already trust.
certificateDetailsoptional

It is an object:

FieldTypeMeaning
subjectstringrequired
namesarray of stringrequiredThe names it is valid for: the alternative names, or the common name when there are none.
issuerstringrequired
notBeforestringrequired
notAfterstringrequired
selfSignedbooleanrequired
chainLengthintegerrequiredCertificates served, the server's own included.
keyTypestringrequired
fingerprintSha256stringrequired
daysLeftintegeroptional
lifetimeDaysintegeroptional
hostnamestringrequiredThe address the next start uses.
coversHostnamebooleanrequired
autoRenewbooleanrequired
renewalRenewaloptional

It is an object:

FieldTypeMeaning
renewAtstringrequiredA time, as RFC 3339.
nextAttemptstringoptional
lastAttemptstringoptional
lastErrorstringoptional
failuresintegerrequired
warningWarningoptional

It is an object:

FieldTypeMeaning
kindstringrequiredexpired, expiring or renewal-failed.
messagestringrequired
progressobjectrequired

It is an object:

FieldTypeMeaning
stageobjectrequired
renewalbooleanrequiredA renewal rather than a first certificate.
errorFailureoptional

It is an object:

FieldTypeMeaning
messagestringrequired
detailstringoptionalLet's Encrypt's own words, when it gave any.
atstringrequired
acmeobjectrequired

It is an object:

FieldTypeMeaning
emailstringrequiredThe contact address used last time, or the caller's own.
termsUrlstringrequired
listenerobjectrequired
planarray of ChallengerequiredThe ways this server could be checked, best first.
webPortintegerrequired
notesarray of stringrequired
bootFingerprintstringrequiredSHA-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.

StatuscodeWhen
400bad_requestthe certificate in use is not from Let's Encrypt
409conflicta request is already running
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
enabledbooleanrequired
{
  "enabled": true
}

Success. 200.

FieldTypeMeaning
sourceobjectrequired

It is an object:

FieldTypeMeaning
rootstringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
relobjectrequired
trustedbooleanrequiredIssued by somebody browsers already trust.
certificateDetailsoptional

It is an object:

FieldTypeMeaning
subjectstringrequired
namesarray of stringrequiredThe names it is valid for: the alternative names, or the common name when there are none.
issuerstringrequired
notBeforestringrequired
notAfterstringrequired
selfSignedbooleanrequired
chainLengthintegerrequiredCertificates served, the server's own included.
keyTypestringrequired
fingerprintSha256stringrequired
daysLeftintegeroptional
lifetimeDaysintegeroptional
hostnamestringrequiredThe address the next start uses.
coversHostnamebooleanrequired
autoRenewbooleanrequired
renewalRenewaloptional

It is an object:

FieldTypeMeaning
renewAtstringrequiredA time, as RFC 3339.
nextAttemptstringoptional
lastAttemptstringoptional
lastErrorstringoptional
failuresintegerrequired
warningWarningoptional

It is an object:

FieldTypeMeaning
kindstringrequiredexpired, expiring or renewal-failed.
messagestringrequired
progressobjectrequired

It is an object:

FieldTypeMeaning
stageobjectrequired
renewalbooleanrequiredA renewal rather than a first certificate.
errorFailureoptional

It is an object:

FieldTypeMeaning
messagestringrequired
detailstringoptionalLet's Encrypt's own words, when it gave any.
atstringrequired
acmeobjectrequired

It is an object:

FieldTypeMeaning
emailstringrequiredThe contact address used last time, or the caller's own.
termsUrlstringrequired
listenerobjectrequired
planarray of ChallengerequiredThe ways this server could be checked, best first.
webPortintegerrequired
notesarray of stringrequired
bootFingerprintstringrequiredSHA-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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
certificatestringrequiredPEM: the server's certificate, then any intermediates. Any order.
privateKeystringrequiredPEM, without a password.
dryRunbooleanoptionalCheck only; install nothing.
{
  "certificate": "example",
  "privateKey": "example",
  "dryRun": false
}

Success. 200.

FieldTypeMeaning
installedbooleanrequired
inspectionobjectrequired

It is an object:

FieldTypeMeaning
detailsDetailsoptional

It is an object:

FieldTypeMeaning
subjectstringrequired
namesarray of stringrequiredThe names it is valid for: the alternative names, or the common name when there are none.
issuerstringrequired
notBeforestringrequired
notAfterstringrequired
selfSignedbooleanrequired
chainLengthintegerrequiredCertificates served, the server's own included.
keyTypestringrequired
fingerprintSha256stringrequired
errorsarray of stringrequiredAny of these, and it is not installed.
warningsarray of stringrequired
statusStatusoptional

It is an object:

FieldTypeMeaning
sourceobjectrequired

It is an object:

FieldTypeMeaning
rootstringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
relobjectrequired
trustedbooleanrequiredIssued by somebody browsers already trust.
certificateDetailsoptional

It is an object:

FieldTypeMeaning
subjectstringrequired
namesarray of stringrequiredThe names it is valid for: the alternative names, or the common name when there are none.
issuerstringrequired
notBeforestringrequired
notAfterstringrequired
selfSignedbooleanrequired
chainLengthintegerrequiredCertificates served, the server's own included.
keyTypestringrequired
fingerprintSha256stringrequired
daysLeftintegeroptional
lifetimeDaysintegeroptional
hostnamestringrequiredThe address the next start uses.
coversHostnamebooleanrequired
autoRenewbooleanrequired
renewalRenewaloptional

It is an object:

FieldTypeMeaning
renewAtstringrequiredA time, as RFC 3339.
nextAttemptstringoptional
lastAttemptstringoptional
lastErrorstringoptional
failuresintegerrequired
warningWarningoptional

It is an object:

FieldTypeMeaning
kindstringrequiredexpired, expiring or renewal-failed.
messagestringrequired
progressobjectrequired

It is an object:

FieldTypeMeaning
stageobjectrequired
renewalbooleanrequiredA renewal rather than a first certificate.
errorFailureoptional

It is an object:

FieldTypeMeaning
messagestringrequired
detailstringoptionalLet's Encrypt's own words, when it gave any.
atstringrequired
acmeobjectrequired

It is an object:

FieldTypeMeaning
emailstringrequiredThe contact address used last time, or the caller's own.
termsUrlstringrequired
listenerobjectrequired
planarray of ChallengerequiredThe ways this server could be checked, best first.
webPortintegerrequired
notesarray of stringrequired
bootFingerprintstringrequiredSHA-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.
{
  "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.

StatuscodeWhen
400bad_requestthe request is not valid
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
bootIdstringrequiredThe id.
restartModeobjectrequiredHow the process comes back: exit for a supervisor to restart it, exec to replace itself in place.
restartingbooleanrequired
maintenanceMaintenanceoptional

It is an object:

FieldTypeMeaning
sincestringrequired
messagestringrequiredWhat the administrator wrote for everyone to read. May be empty.
runningTransfersintegerrequired
pendingarray of stringrequiredWhat a restart would change, one line each.
problemsarray of objectsrequiredWhat would go wrong on the way back up, found by trying.

Each item is an object:

FieldTypeMeaning
blockingbooleanrequiredTrue when the server would come back without the thing this port is for. False for the TCP fallback, which transfers can do without.
textstringrequired
notesarray of stringrequiredWhat 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
forcebooleanoptionalRestart 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.

FieldTypeMeaning
bootIdstringrequiredThe process being stopped. The portal waits for a different one.
restartModeobjectrequired
{
  "bootId": "k7Qm2sLp9vX4aB1c",
  "restartMode": {}
}

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
409conflicta restart is already under way, or the next start would fail; send force to restart anyway
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
onbooleanrequired
messagestringoptionalA note for the banner. Ignored when turning maintenance off.
pauseRunningbooleanoptionalPause transfers already running, rather than letting them finish.
{
  "on": true,
  "message": "The cut is in the folder.",
  "pauseRunning": true
}

Success. 200.

FieldTypeMeaning
maintenanceMaintenanceoptional

It is an object:

FieldTypeMeaning
sincestringrequired
messagestringrequiredWhat the administrator wrote for everyone to read. May be empty.
pausedintegerrequiredTransfers 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.

StatuscodeWhen
400bad_requestthe note is too long
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
idstringrequiredThe id.
namestringrequiredThe name.
kindstringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
createdAtstringrequiredA time, as RFC 3339.
grantsintegerrequiredHow 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.
reachablebooleanrequiredAbsent 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.
bucketViewoptional, left out when emptyA bucket's settings, for the edit form. Never its secret: only whether one is stored.

It is an object:

FieldTypeMeaning
providerstringrequired
bucketstringrequired
regionstringrequired
endpointstringoptional
prefixstringrequired
addressingobjectrequired
readOnlybooleanrequired
credentialsobjectrequired
accessKeyIdstringoptionalAn identifier, not a secret: the service prints it in its own logs and error messages.
hasSecretbooleanrequiredWhether a secret is stored, so the form can say "Saved".
performanceobjectrequiredAs it will be used, inside the limits.

It is an object:

FieldTypeMeaning
partSizeMbintegerrequiredMegabytes per part. A very large file raises it so it fits in 10,000 parts.
partsPerFileintegerrequiredParts of one file uploading at once.
rangesPerFileintegerrequiredByte ranges of one file downloading at once.
smallFilesintegerrequiredSmall files moving at once when many come together.
checksumobjectrequired
[
  {
    "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
namestringrequiredThe name.
pathstringoptionalFor a local root. Ignored for a bucket.
bucketBucketBodyoptionalAbsent means a folder on this server.
{
  "name": "Rush delivery",
  "path": "projects/rush",
  "bucket": {}
}

Success. 200.

FieldTypeMeaning
idstringrequiredThe id.
namestringrequiredThe name.
kindstringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
createdAtstringrequiredA time, as RFC 3339.
grantsintegerrequiredHow 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.
reachablebooleanrequiredAbsent 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.
bucketViewoptional, left out when emptyA bucket's settings, for the edit form. Never its secret: only whether one is stored.

It is an object:

FieldTypeMeaning
providerstringrequired
bucketstringrequired
regionstringrequired
endpointstringoptional
prefixstringrequired
addressingobjectrequired
readOnlybooleanrequired
credentialsobjectrequired
accessKeyIdstringoptionalAn identifier, not a secret: the service prints it in its own logs and error messages.
hasSecretbooleanrequiredWhether a secret is stored, so the form can say "Saved".
performanceobjectrequiredAs it will be used, inside the limits.

It is an object:

FieldTypeMeaning
partSizeMbintegerrequiredMegabytes per part. A very large file raises it so it fits in 10,000 parts.
partsPerFileintegerrequiredParts of one file uploading at once.
rangesPerFileintegerrequiredByte ranges of one file downloading at once.
smallFilesintegerrequiredSmall files moving at once when many come together.
checksumobjectrequired
{
  "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.

StatuscodeWhen
400bad_requestmissing, unreadable, or somewhere that would expose the server's keys; or the object store's own refusal
409conflictthe name is taken, or another storage location already covers it
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
rootquerystringoptionalThe 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.

StatuscodeWhen
400bad_requestthe object store's own refusal, passed through
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Request body. JSON.

A field the server does not know is refused with 400.

FieldTypeMeaning
namestringrequiredThe name.
bucketBucketBodyoptionalA 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.

FieldTypeMeaning
idstringrequiredThe id.
namestringrequiredThe name.
kindstringrequired
pathstringrequiredA path relative to the space or the storage location, with / between folders and no leading slash.
createdAtstringrequiredA time, as RFC 3339.
grantsintegerrequiredHow 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.
reachablebooleanrequiredAbsent 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.
bucketViewoptional, left out when emptyA bucket's settings, for the edit form. Never its secret: only whether one is stored.

It is an object:

FieldTypeMeaning
providerstringrequired
bucketstringrequired
regionstringrequired
endpointstringoptional
prefixstringrequired
addressingobjectrequired
readOnlybooleanrequired
credentialsobjectrequired
accessKeyIdstringoptionalAn identifier, not a secret: the service prints it in its own logs and error messages.
hasSecretbooleanrequiredWhether a secret is stored, so the form can say "Saved".
performanceobjectrequiredAs it will be used, inside the limits.

It is an object:

FieldTypeMeaning
partSizeMbintegerrequiredMegabytes per part. A very large file raises it so it fits in 10,000 parts.
partsPerFileintegerrequiredParts of one file uploading at once.
rangesPerFileintegerrequiredByte ranges of one file downloading at once.
smallFilesintegerrequiredSmall files moving at once when many come together.
checksumobjectrequired
{
  "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.

StatuscodeWhen
400bad_requesta root needs a name of 1 to 64 characters
404not_foundnot there, or not visible to this caller
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Success. 200.

JSON object.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
409conflictan automation still uses it
404not_foundnot there, or not visible to this caller
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Success. 200.

The body is an array.

FieldTypeMeaning
idstringrequiredThe id.
kindstringrequireduser or group.
principalIdstringrequiredThe id.
principalNamestringrequiredThe address for a person, the name for a group.
membersintegerrequiredHow many people a group grant reaches. One for a person.
pathstringrequiredRelative to the storage location. Empty is the whole location.
canReadbooleanrequired
canWritebooleanrequired
canDeletebooleanrequired
createdAtstringrequiredA 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Request body. JSON.

A field the server does not know is refused with 400.

FieldTypeMeaning
userIdstringoptionalThe id.
groupIdstringoptionalThe id.
pathstringoptionalA path relative to the space or the storage location, with / between folders and no leading slash.
accessstringrequiredread, write or delete; each includes the ones before it.
{
  "userId": "k7Qm2sLp9vX4aB1c",
  "groupId": "k7Qm2sLp9vX4aB1c",
  "path": "projects/rush",
  "access": "edit"
}

Success. 200.

FieldTypeMeaning
idstringrequiredThe id.
kindstringrequireduser or group.
principalIdstringrequiredThe id.
principalNamestringrequiredThe address for a person, the name for a group.
membersintegerrequiredHow many people a group grant reaches. One for a person.
pathstringrequiredRelative to the storage location. Empty is the whole location.
canReadbooleanrequired
canWritebooleanrequired
canDeletebooleanrequired
createdAtstringrequiredA 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.

StatuscodeWhen
400bad_requestneither or both of userId and groupId, or an unknown level
404not_foundno such storage location, person or group
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe storage location.
grant_idpathstringrequiredThe grant.

Success. 200.

JSON object.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
404not_foundnot there, or not visible to this caller
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
idstringoptionalThe id.
scopestringrequired
scopeIdstringoptionalThe id.
groupKindstringoptional
directionstringrequired
totalBpsintegeroptional
perTransferBpsintegeroptional
concurrencyintegeroptional
scheduleScheduleoptionalLeft out keeps the stored schedule; null clears it.

It is an object:

FieldTypeMeaning
daysarray of integerrequiredMonday = 1 … Sunday = 7, ISO.
startMinintegerrequired
endMinintegerrequired
prioritystringoptionalLeft out keeps the stored priority.
enabledbooleanoptional
{
  "scope": "read",
  "direction": "download"
}

Success. 200.

FieldTypeMeaning
idstringrequiredThe id.
scopestringrequired
scopeIdstringrequiredThe id.
groupKindstringrequired
directionstringrequired
totalBpsintegeroptional
perTransferBpsintegeroptional
concurrencyintegeroptional
scheduleScheduleoptional

It is an object:

FieldTypeMeaning
daysarray of integerrequiredMonday = 1 … Sunday = 7, ISO.
startMinintegerrequired
endMinintegerrequired
prioritystringrequired
enabledbooleanrequired
licensedbooleanrequired
createdAtintegerrequired
updatedAtintegerrequired
{
  "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.

StatuscodeWhen
400bad_requestthe request is not valid
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Success. 200.

JSON object.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
404not_foundnot there, or not visible to this caller
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
userquerystringrequired
directionquerystringoptional

Success. 200.

FieldTypeMeaning
perTransferBpsintegeroptional
poolBpsintegeroptional
concurrencyintegeroptional
limitedBystringrequired
sentencestringrequired
layersarray of objectsrequiredEvery rule that took part, license first, the person's own last.

Each item is an object:

FieldTypeMeaning
kindstringrequiredlicense, server, group or user.
idstringrequiredThe group or user id. Empty for the license and the server.
groupKindstringoptionalshared or member for a group rule.
totalBpsintegeroptional
perTransferBpsintegeroptional
concurrencyintegeroptional
overriddenarray of stringrequiredFields this layer sets that the person's own rule replaces: total, perTransfer, concurrency.
winnersobjectrequiredThe layer that set each number.

It is an object:

FieldTypeMeaning
totalstringoptionalHow many matched, including ones not on this page.
perTransferstringoptional
concurrencystringoptional
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
timezonestringrequired
{
  "timezone": "example"
}

Success. 200.

JSON object.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
402licence_requiredthis server's license does not include traffic policies
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
uploadBpsintegerrequired
downloadBpsintegerrequired
uploadLimitBpsintegeroptional
downloadLimitBpsintegeroptional
activearray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
whostringrequired
groupstringoptional
directionstringrequired
namestringrequiredThe name.
bpsintegerrequired
bytesintegerrequiredSize in bytes.
sizeintegeroptionalSize in bytes.
limitedBystringrequired
waitingbooleanrequired
waitReasonstringoptional
aheadintegerrequired
waitingarray of objectsrequired

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
whostringrequired
groupstringoptional
directionstringrequired
namestringrequiredThe name.
bpsintegerrequired
bytesintegerrequiredSize in bytes.
sizeintegeroptionalSize in bytes.
limitedBystringrequired
waitingbooleanrequired
waitReasonstringoptional
aheadintegerrequired
recentarray of objectsrequiredThe last [WINDOW] of readings, oldest first.

Each item is an object:

FieldTypeMeaning
tsintegerrequiredSeconds since the epoch.
uploadBpsintegerrequired
downloadBpsintegerrequired
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe resource's id.

Success. 200.

JSON object.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
404not_foundnot there, or not visible to this caller
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
fromqueryintegeroptional
toqueryintegeroptional
grainquerystringoptional
seriesquerystringoptional
series_idquerystringoptional
directionquerystringoptional

Success. 200.

JSON object.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
fromqueryintegeroptional
toqueryintegeroptional
grainquerystringoptional
seriesquerystringoptional
series_idquerystringoptional
directionquerystringoptional

Success. 200.

JSON object.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
402licence_requiredusage reports are not included
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
cadencestringrequired
addressesarray of stringrequired
{
  "cadence": "example",
  "addresses": [
    "example"
  ]
}

Success. 200.

JSON object.

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
402licence_requiredusage reports are not included
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

StatuscodeWhen
402licence_requiredusage reports are not included
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

StatuscodeWhen
402licence_requiredusage reports are not included
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
enabledbooleanrequiredWhat the administrator asked for.
activebooleanrequiredWhether 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.
problemstringoptional, left out when empty
issuerstringrequired
clientIdstringrequiredThe id.
clientSecretSetbooleanrequired
createAccountsbooleanrequired
groupsClaimstringoptional, left out when empty
labelstringrequired
redirectUristringrequiredWhat 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.
includedbooleanrequiredWhether 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
enabledbooleanrequired
issuerstringrequired
clientIdstringrequiredThe id.
clientSecretstringoptional
createAccountsbooleanoptional
groupsClaimstringoptional
labelstringoptional
{
  "enabled": true,
  "issuer": "example",
  "clientId": "k7Qm2sLp9vX4aB1c",
  "clientSecret": "a-secret-shown-once",
  "createAccounts": true,
  "groupsClaim": "example",
  "label": "example"
}

Success. 200.

FieldTypeMeaning
enabledbooleanrequiredWhat the administrator asked for.
activebooleanrequiredWhether 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.
problemstringoptional, left out when empty
issuerstringrequired
clientIdstringrequiredThe id.
clientSecretSetbooleanrequired
createAccountsbooleanrequired
groupsClaimstringoptional, left out when empty
labelstringrequired
redirectUristringrequiredWhat 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.
includedbooleanrequiredWhether 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.

StatuscodeWhen
400bad_requestthe provider could not be reached, or did not describe itself
402licence_requiredswitching it on, when this server's license does not include single sign-on
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
enabledbooleanrequiredWhat the administrator asked for.
activebooleanrequiredWhether 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.
problemstringoptional, left out when empty
issuerstringrequired
clientIdstringrequiredThe id.
clientSecretSetbooleanrequired
createAccountsbooleanrequired
groupsClaimstringoptional, left out when empty
labelstringrequired
redirectUristringrequiredWhat 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.
includedbooleanrequiredWhether 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
enabledbooleanoptional
labelstringoptional
idpMetadataUrlstringoptional
idpMetadataXmlstringoptionalPasted metadata, read now and not kept: what is kept is what it said.
idpEntityIdstringoptionalThe id.
idpSsoUrlstringoptional
idpSsoBindingBindingoptional

It is an object:

FieldTypeMeaning
fieldstringrequired
bpsintegeroptional
concurrencyintegeroptional
labelstringrequired
idpSloUrlstringoptional
idpCertificatesarray of stringoptional
wantAssertionsSignedbooleanoptionalAccepted so the screen can send what it shows, and refused when false: this build takes nothing unsigned.
wantAssertionsEncryptedbooleanoptionalAccepted to be refused when true; see saml::response.
allowIdpInitiatedbooleanoptional
singleLogoutbooleanoptional
createAccountsbooleanoptional
emailAttributestringoptional
nameAttributestringoptional
groupsAttributestringoptional
groupMaparray of GroupMappingoptional

Each item is an object:

FieldTypeMeaning
fromstringrequired
tostringrequired
requireSsobooleanoptional
breakGlassEmailstringoptionalNamed by email, because that is what an administrator knows.
breakGlassUserIdstringoptionalThe 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.

FieldTypeMeaning
enabledbooleanrequiredWhat the administrator asked for.
activebooleanrequiredWhether 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.
problemstringoptional
labelstringrequired
idpMetadataUrlstringrequired
idpEntityIdstringrequiredThe id.
idpSsoUrlstringrequired
idpSsoBindingobjectrequired

It is an object:

FieldTypeMeaning
fieldstringrequired
bpsintegeroptional
concurrencyintegeroptional
labelstringrequired
idpSloUrlstringrequired
idpCertificatesarray of stringrequiredCertificates, as PEM. Public, and never a key.
spEntityIdstringrequiredWhat to register at the provider. Read-only, worked out from the configured hostname so it is copied rather than typed twice.
spMetadataUrlstringrequired
spAcsUrlstringrequired
spLogoutUrlstringrequired
spCertificatestringrequiredThe certificate this server serves its portal with, which our metadata lists. Empty when it cannot be read.
wantAssertionsSignedbooleanrequiredAlways true: this build accepts nothing unsigned, and says so.
allowIdpInitiatedbooleanrequired
singleLogoutbooleanrequired
createAccountsbooleanrequired
emailAttributestringrequired
nameAttributestringrequired
groupsAttributestringrequired
groupMaparray of objectsrequired

Each item is an object:

FieldTypeMeaning
fromstringrequired
tostringrequired
requireSsobooleanrequired
breakGlassEmailstringrequiredAn email address.
breakGlassUserIdstringoptionalThe id.
includedbooleanrequiredWhether 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.

StatuscodeWhen
400bad_requestnothing was saved; the message says why
402licence_requiredswitching it on or requiring it, when this server's license does not include single sign-on
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
enabledbooleanrequiredWhat the administrator asked for.
activebooleanrequiredWhether 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.
problemstringoptional
labelstringrequired
idpMetadataUrlstringrequired
idpEntityIdstringrequiredThe id.
idpSsoUrlstringrequired
idpSsoBindingobjectrequired

It is an object:

FieldTypeMeaning
fieldstringrequired
bpsintegeroptional
concurrencyintegeroptional
labelstringrequired
idpSloUrlstringrequired
idpCertificatesarray of stringrequiredCertificates, as PEM. Public, and never a key.
spEntityIdstringrequiredWhat to register at the provider. Read-only, worked out from the configured hostname so it is copied rather than typed twice.
spMetadataUrlstringrequired
spAcsUrlstringrequired
spLogoutUrlstringrequired
spCertificatestringrequiredThe certificate this server serves its portal with, which our metadata lists. Empty when it cannot be read.
wantAssertionsSignedbooleanrequiredAlways true: this build accepts nothing unsigned, and says so.
allowIdpInitiatedbooleanrequired
singleLogoutbooleanrequired
createAccountsbooleanrequired
emailAttributestringrequired
nameAttributestringrequired
groupsAttributestringrequired
groupMaparray of objectsrequired

Each item is an object:

FieldTypeMeaning
fromstringrequired
tostringrequired
requireSsobooleanrequired
breakGlassEmailstringrequiredAn email address.
breakGlassUserIdstringoptionalThe id.
includedbooleanrequiredWhether 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
enabledbooleanrequired
hoststringrequired
portintegerrequired
securitystringrequired
usernamestringrequired
passwordSetbooleanrequiredWhether one is stored. Never the password.
fromNamestringrequired
fromAddressstringrequired
problemstringoptional, left out when emptyWhat 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.
lastTestAtstringoptional, left out when emptyWhen 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
enabledbooleanrequired
hoststringrequired
portintegerrequired
securitystringrequired
usernamestringoptional
passwordstringoptionalAbsent means "keep the stored one". Present and empty means "there is no password", which some relays genuinely want.
fromNamestringoptional
fromAddressstringrequired
{
  "enabled": true,
  "host": "files.example.com",
  "port": 443,
  "security": "example",
  "username": "example",
  "password": "a-secret-shown-once",
  "fromName": "example",
  "fromAddress": "example"
}

Success. 200.

FieldTypeMeaning
enabledbooleanrequired
hoststringrequired
portintegerrequired
securitystringrequired
usernamestringrequired
passwordSetbooleanrequiredWhether one is stored. Never the password.
fromNamestringrequired
fromAddressstringrequired
problemstringoptional, left out when emptyWhat 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.
lastTestAtstringoptional, left out when emptyWhen 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
enabledbooleanrequired
hoststringrequired
portintegerrequired
securitystringrequired
usernamestringrequired
passwordSetbooleanrequiredWhether one is stored. Never the password.
fromNamestringrequired
fromAddressstringrequired
problemstringoptional, left out when emptyWhat 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.
lastTestAtstringoptional, left out when emptyWhen 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
tostringoptionalWhere to send it. Defaults to the signed-in administrator, which is the address they can actually check.
{
  "to": "projects/rush"
}

Success. 200.

FieldTypeMeaning
sentTostringrequired
{
  "sentTo": "example"
}

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
400bad_requestthe mail server refused; its own words are in the message
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
viewobjectrequired

It is an object:

FieldTypeMeaning
enabledbooleanrequiredWhat the administrator asked for.
activebooleanrequiredWhether it can sign people in right now.
problemstringoptionalWhy not, or something to be aware of while it works.
labelstringrequired
hoststringrequired
portintegerrequired
securityobjectrequired
allowPlainbooleanrequired
bindDnstringrequired
bindPasswordSetbooleanrequired
userSearchBasestringrequired
userFilterstringrequired
groupSearchBasestringrequired
groupFilterstringrequired
emailAttributestringrequired
nameAttributestringrequired
usernameAttributestringrequired
memberAttributestringrequired
nestedGroupsbooleanrequired
createAccountsbooleanrequired
groupMaparray of objectsrequired

Each item is an object:

FieldTypeMeaning
fromstringrequiredA group's name or its distinguished name.
tostringrequired
syncEnabledbooleanrequired
lastSyncAtstringoptionalA time, as RFC 3339.
lastSyncDetailstringoptional
includedbooleanrequiredWhether the license includes switching this on.
presetsobjectrequired

It is an object:

FieldTypeMeaning
activeDirectoryobjectrequired

It is an object:

FieldTypeMeaning
portintegerrequired
securityobjectrequired
userFilterstringrequired
usernameAttributestringrequired
emailAttributestringrequired
nameAttributestringrequired
memberAttributestringrequired
groupFilterstringrequired
nestedGroupsbooleanrequired
openLdapobjectrequired

It is an object:

FieldTypeMeaning
portintegerrequired
securityobjectrequired
userFilterstringrequired
usernameAttributestringrequired
emailAttributestringrequired
nameAttributestringrequired
memberAttributestringrequired
groupFilterstringrequired
nestedGroupsbooleanrequired
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
enabledbooleanoptional
labelstringoptional
hoststringoptional
portintegeroptional
securitySecurityoptional
allowPlainbooleanoptional
bindDnstringoptional
bindPasswordstringoptional
userSearchBasestringoptional
userFilterstringoptional
groupSearchBasestringoptional
groupFilterstringoptional
emailAttributestringoptional
nameAttributestringoptional
usernameAttributestringoptional
memberAttributestringoptional
nestedGroupsbooleanoptional
createAccountsbooleanoptional
groupMaparray of Mappingoptional

Each item is an object:

FieldTypeMeaning
fromstringrequiredA group's name or its distinguished name.
tostringrequired
syncEnabledbooleanoptional
{
  "enabled": true,
  "label": "example",
  "host": "files.example.com",
  "port": 443,
  "security": {},
  "allowPlain": true,
  "bindDn": "example",
  "bindPassword": "a-secret-shown-once"
}

Success. 200.

FieldTypeMeaning
enabledbooleanrequiredWhat the administrator asked for.
activebooleanrequiredWhether it can sign people in right now.
problemstringoptionalWhy not, or something to be aware of while it works.
labelstringrequired
hoststringrequired
portintegerrequired
securityobjectrequired
allowPlainbooleanrequired
bindDnstringrequired
bindPasswordSetbooleanrequired
userSearchBasestringrequired
userFilterstringrequired
groupSearchBasestringrequired
groupFilterstringrequired
emailAttributestringrequired
nameAttributestringrequired
usernameAttributestringrequired
memberAttributestringrequired
nestedGroupsbooleanrequired
createAccountsbooleanrequired
groupMaparray of objectsrequired

Each item is an object:

FieldTypeMeaning
fromstringrequiredA group's name or its distinguished name.
tostringrequired
syncEnabledbooleanrequired
lastSyncAtstringoptionalA time, as RFC 3339.
lastSyncDetailstringoptional
includedbooleanrequiredWhether 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.

StatuscodeWhen
400bad_requesta value that is out of range or not usable, or the directory could not be used (the message says why); nothing is saved
402licence_requiredswitching it on, when this server's license does not include single sign-on
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe 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.

FieldTypeMeaning
enabledbooleanrequiredWhat the administrator asked for.
activebooleanrequiredWhether it can sign people in right now.
problemstringoptionalWhy not, or something to be aware of while it works.
labelstringrequired
hoststringrequired
portintegerrequired
securityobjectrequired
allowPlainbooleanrequired
bindDnstringrequired
bindPasswordSetbooleanrequired
userSearchBasestringrequired
userFilterstringrequired
groupSearchBasestringrequired
groupFilterstringrequired
emailAttributestringrequired
nameAttributestringrequired
usernameAttributestringrequired
memberAttributestringrequired
nestedGroupsbooleanrequired
createAccountsbooleanrequired
groupMaparray of objectsrequired

Each item is an object:

FieldTypeMeaning
fromstringrequiredA group's name or its distinguished name.
tostringrequired
syncEnabledbooleanrequired
lastSyncAtstringoptionalA time, as RFC 3339.
lastSyncDetailstringoptional
includedbooleanrequiredWhether 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
okbooleanrequired
detailstringrequired
usersintegeroptional, left out when empty
groupsintegeroptional, left out when empty
{
  "ok": true,
  "detail": "example",
  "users": 1,
  "groups": 1
}

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
usernamestringrequired
passwordstringrequired
{
  "username": "example",
  "password": "a-secret-shown-once"
}

Success. 200.

FieldTypeMeaning
okbooleanrequired
detailstringrequired
emailstringoptional, left out when emptyAn email address.
namestringoptional, left out when emptyThe name.
groupsarray of stringoptional, left out when empty
mappedarray of stringoptional, 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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
checkedintegerrequired
disabledintegerrequired
detailstringrequired
{
  "checked": 1,
  "disabled": 1,
  "detail": "example"
}

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
400bad_requestdirectory sign-in is switched off, its settings have a problem, or the directory could not be read
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
scannerobjectrequired
clamavAddressstringrequiredhost:port, or the path of the clamd socket.
icapUrlstringrequiredicap://host:1344/service.
icapModeobjectrequired
commandstringrequiredThe program and its arguments, split on spaces with quotes to group.
maxFileMbintegerrequiredMegabytes. Anything larger follows too_large.
timeoutSecsintegerrequired
scanReceiveLinksbooleanrequired
scanOutsidePackagesbooleanrequired
scanUserUploadsbooleanrequired
scanServerToServerbooleanrequired
tooLargeobjectrequired

It is an object:

FieldTypeMeaning
allowNeverExpiresbooleanrequired
defaultExpiryDaysintegerrequired
formFieldsIncludedbooleanrequiredWhether custom form fields are included in this license.
onErrorobjectrequired

It is an object:

FieldTypeMeaning
allowNeverExpiresbooleanrequired
defaultExpiryDaysintegerrequired
formFieldsIncludedbooleanrequiredWhether custom form fields are included in this license.
lastTestAtstringoptionalA time, as RFC 3339.
lastTestDetailstringoptional
{
  "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.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
scannerScannerKindoptional
clamavAddressstringoptional
icapUrlstringoptional
icapModeIcapModeoptional
commandstringoptional
maxFileMbintegeroptional
timeoutSecsintegeroptional
scanReceiveLinksbooleanoptional
scanOutsidePackagesbooleanoptional
scanUserUploadsbooleanoptional
scanServerToServerbooleanoptional
tooLargePolicyoptional

It is an object:

FieldTypeMeaning
allowNeverExpiresbooleanrequired
defaultExpiryDaysintegerrequired
formFieldsIncludedbooleanrequiredWhether custom form fields are included in this license.
onErrorPolicyoptional

It is an object:

FieldTypeMeaning
allowNeverExpiresbooleanrequired
defaultExpiryDaysintegerrequired
formFieldsIncludedbooleanrequiredWhether custom form fields are included in this license.
lastTestAtvalueoptional
lastTestDetailvalueoptional
{
  "scanner": {},
  "clamavAddress": "example",
  "icapUrl": "example",
  "icapMode": {},
  "command": "example",
  "maxFileMb": 1,
  "timeoutSecs": 1,
  "scanReceiveLinks": true
}

Success. 200.

FieldTypeMeaning
scannerobjectrequired
clamavAddressstringrequiredhost:port, or the path of the clamd socket.
icapUrlstringrequiredicap://host:1344/service.
icapModeobjectrequired
commandstringrequiredThe program and its arguments, split on spaces with quotes to group.
maxFileMbintegerrequiredMegabytes. Anything larger follows too_large.
timeoutSecsintegerrequired
scanReceiveLinksbooleanrequired
scanOutsidePackagesbooleanrequired
scanUserUploadsbooleanrequired
scanServerToServerbooleanrequired
tooLargeobjectrequired

It is an object:

FieldTypeMeaning
allowNeverExpiresbooleanrequired
defaultExpiryDaysintegerrequired
formFieldsIncludedbooleanrequiredWhether custom form fields are included in this license.
onErrorobjectrequired

It is an object:

FieldTypeMeaning
allowNeverExpiresbooleanrequired
defaultExpiryDaysintegerrequired
formFieldsIncludedbooleanrequiredWhether custom form fields are included in this license.
lastTestAtstringoptionalA time, as RFC 3339.
lastTestDetailstringoptional
{
  "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.

StatuscodeWhen
400bad_requesta 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
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

FieldTypeMeaning
okbooleanrequired
detailstringrequired
versionstringoptional, left out when empty
{
  "ok": true,
  "detail": "example",
  "version": "example"
}

Errors. The body always has the shape in Errors. Match on code.

StatuscodeWhen
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
403forbiddenthe key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator
402licence_requiredthe 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.

NameInTypeMeaning
statequerystringoptionalheld (the default), released, deleted or all.

Success. 200.

FieldTypeMeaning
itemsarray of objectsrequiredThe quarantined files, newest first, up to 500.

Each item is an object:

FieldTypeMeaning
idstringrequiredThe id.
fileNamestringrequiredThe file's name.
intendedPathstringrequiredWhere the file was meant to be stored.
rootNamestringoptionalThe storage location's name.
bytesintegerrequiredSize in bytes.
threatstringrequiredThe threat's name for an infected file; for a held one, what the scanner said, which is the administrator's to read.
reasonstringrequiredinfected, too_large or error.
originstringrequiredHow the file arrived: browser, receive_link, package, server, or desktop.
actorEmailstringoptionalAn email address.
statestringrequiredheld, released or deleted.
decidedBystringoptionalThe administrator who decided, when someone has.
decidedAtstringoptionalA time, as RFC 3339.
decisionReasonstringoptionalThe reason recorded with that decision.
createdAtstringrequiredA time, as RFC 3339.
{
  "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.

StatuscodeWhen
400bad_request`state` is not held, released, deleted or all
404not_foundthe caller is not an administrator
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
402licence_requiredthe 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.

NameInTypeMeaning
idpathstringrequiredThe quarantined file.

Request body. JSON.

A field the server does not know is refused with 400.

FieldTypeMeaning
actionstringrequiredrelease puts the file back where it was meant to go. delete removes it.
reasonstringrequiredWhy this decision was made. Required, and written to the audit log.
{
  "action": "release",
  "reason": "The scanner was wrong about this file."
}

Success. 200.

FieldTypeMeaning
idstringrequiredThe id.
fileNamestringrequiredThe file's name.
intendedPathstringrequiredWhere the file was meant to be stored.
rootNamestringoptionalThe storage location's name.
bytesintegerrequiredSize in bytes.
threatstringrequiredThe threat's name for an infected file; for a held one, what the scanner said, which is the administrator's to read.
reasonstringrequiredinfected, too_large or error.
originstringrequiredHow the file arrived: browser, receive_link, package, server, or desktop.
actorEmailstringoptionalAn email address.
statestringrequiredheld, released or deleted.
decidedBystringoptionalThe administrator who decided, when someone has.
decidedAtstringoptionalA time, as RFC 3339.
decisionReasonstringoptionalThe reason recorded with that decision.
createdAtstringrequiredA 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.

StatuscodeWhen
400bad_requestthe body is not an action and a reason, the action is not `release` or `delete`, or there is no reason
404not_foundno such file, or the caller is not an administrator
409conflictthe file has already been released or deleted
401unauthenticatedno key, or a key that is unknown, expired, revoked, or owned by a disabled account
402licence_requiredthe 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"
  }
}