Server API.
Call your Farwing Server from a script. This reference lists every call an API key can make.
Bytes at speed still go over the data port. The API issues a ticket, then the
farwing command moves the file.
Base URL
Send requests to the same host you open the portal on:
https://<your-server>/api/v1
There is no separate API host. Replace <your-server> with that hostname. A call that is not under /api/v1 is written out in full below, such as GET /metrics.
Create a key
An administrator creates a key under Admin, then API keys. Anyone else creates their own under Profile, then API keys. Name the key, choose when it expires, and give it the least it needs.
readlists and downloads.writeuploads, creates folders, packages, receive links, and event rules.deletedeletes files and packages, and ends receive links.transferasks for a ticket so the data port can move bytes.adminis the administrator calls. Only an administrator can grant it, and it includes the other scopes.
A key cannot do more than the account that owns it, and a key cannot create another key.
The secret is shown once. It starts with fw_. If you lose it, revoke the key and make another.
Send the key
Put the whole key in the Authorization header. Bearer may be any capitalisation. The space after it is required.
Authorization: Bearer fw_your_key_here
Send the key only in that header. A key placed in the query string is ignored, and the call is refused as if you had not signed in.
Query strings are also written to logs. Do not send a session cookie on the same request.
If a cookie and a key are both present, the cookie is used and the key is ignored.
A key does not send x-farwing-csrf. That header is only for a browser session.
Every call below needs a license that includes the REST API, except reading the license itself.
A first call
List the spaces this key can open. Create the key first, then:
curl -sS \
-H "Authorization: Bearer fw_your_key_here" \
"https://files.example.com/api/v1/home"
const res = await fetch("https://files.example.com/api/v1/home", {
headers: { Authorization: `Bearer ${key}` },
});
const body = await res.json();
if (!res.ok) {
throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);
}
console.log(body.spaces);
A space comes back like this:
{
"spaces": [
{
"id": "k7Qm2sLp9vX4aB1c",
"name": "Projects",
"icon": "folder",
"kind": "shared",
"level": "edit"
}
]
}
level is view, edit, or full.
Take id and call GET /api/v1/spaces/{id}/files to list a folder.
The other areas are
transfers,
packages,
receive links,
automation,
accounts, and
administration.
Errors
A failed call returns JSON in one shape. Match on code. message is a sentence for a person and can change.
{
"error": {
"code": "not_found",
"message": "not found"
}
}
| Status | code | Meaning |
|---|---|---|
| 400 | bad_request | The request is missing something or asks for something the server will not do. message says what to change. |
| 401 | unauthenticated | No key, or a key that is not valid. The message is sign in to continue. The response includes WWW-Authenticate: Bearer. |
| 402 | licence_required | The license does not include this. For a missing REST API the message says the keys are kept and will work again when a license that includes the API is loaded. |
| 403 | forbidden | The key is valid and the thing exists, and this key may not do it. The message is you do not have access to this. |
| 404 | not_found | Not there, or not visible to this caller. Those are the same answer, so a script cannot use 403 to discover paths. The message is not found. |
| 409 | conflict | The request clashes with what is already there, such as a name in use. message says which. |
| 429 | too_many_requests | Slow down. Retry-After is how many seconds to wait. Most API-key calls are not limited this way. The calls that are say so on this page. |
500 means something broke. The message is something went wrong; the details are in the server log, and the code is internal.
503 means the server is busy or a piece of it is down. The message is the server is busy; try again shortly, and the code is unavailable.
A transfer call uses 503 when every transfer the license allows at once is already running.
A file or folder you are not allowed to see is 404, not 403. 403 is for a key that is good but does not hold the scope, or for an administrator call made by someone who is not an administrator.
Pagination
There is no single paging style. Each call below says which one it uses.
- Folders (
GET /api/v1/files).limitis 1 to 1000 and defaults to 200. The reply hastotaland, when more remain,nextCursor. Send that value back ascursor. The cursor counts entries to skip. It is not a file name. - Transfers.
pagestarts at 1.pageSizeis 25, 50, or 100, and defaults to 25. The reply's summary counts every row the filters match, not only this page. - Audit.
limitis at most 500 and is clamped, not refused.beforeis an id to read older rows. - Other lists either return a bounded set (often 200 or 500, newest first) or take
limitas that call describes. A value outside the range is refused when the call says it is refused, and clamped when the call says it is clamped.
Server version
GET /api/v1/version needs no credential, so a script or a client can check the server before it signs in.
{
"version": "0.4.0",
"commit": "abc1234",
"build": "0.4.0 (abc1234)",
"protocol": { "version": 1, "features": ["pace"] },
"api": "v1",
"minClient": { "cli": "0.1.2", "desktop": "0.2.0" }
}
versionis the server's version.commitis the build it came from, orunknown.buildis the two together, as the portal shows it.protocolis what the data port speaks: its handshake version and the optional features it offers. A client uses a feature when it is listed, not because of a version number.apiis the version in the path of every call on these pages.minClientis the oldestfarwingcommand line and the oldest Farwing Desktop this server works with.
The farwing command and Farwing Desktop name themselves in the User-Agent header, such as farwing-cli/0.4.0 (linux; x86_64) or farwing-desktop/0.3.0 (windows; x86_64). The server keeps the last version each account and each API key used. Administrators see it on the People and API keys pages, and in clients on the account and key lists.
Calls an API key cannot make
These exist on the server. They are not in the pages that follow, because an API key is the wrong credential.
- Sign-in. Password, second factor, SSO, and SAML. A key is already signed in as the account that owns it.
- First-time setup of the first administrator, and
GET /health,GET /api/v1/versionandGET /api/v1/server/state, which need no credential. - Package links under
/api/v1/r/{token}. The link in the email is the credential, for someone who has no account. - The receive page under
/api/v1/send/{token}, and the Farwing Desktop hand-off under/api/v1/receive/desktop. The sender has no API key. Managing the link is under Receive links. - Signing out one session (
POST /api/v1/auth/sign-out). A key has no session. Revoke the key instead. Signing out every browser session is a different call and is listed below. - Password, authenticator app, and replacing recovery codes. Those need a person at the portal. An API key can read how many recovery codes are left, and nothing more.
- Creating, listing, or revoking API keys (
/api/v1/profile/api-keys). A key must not be able to mint another key. Do that in the portal. An administrator key can list and revoke keys on the server; that call is under Administration.
License routes are written with the spelling license. The same handlers also answer at licence
(/api/v1/licence and /api/v1/admin/licence/…) so an older script keeps working.
Service
These calls sit next to the API. GET /api/v1/license is with administration, because that is where the rest of the license calls are.
GET /metrics
Counts and totals in the Prometheus text format. Needs an API key with any scope; a portal session is refused.
Who. Any API key. The handler does not check a scope, so the narrowest key you can make is enough. A portal session is refused with 400. The key still needs a license that includes the REST API. The OpenAPI document describes this call as needing the read scope.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 400 | bad_request | the request is not valid |
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "bad_request",
"message": "the request is not valid"
}
}
GET /api/docs
This page.
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.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/docs/openapi.json
This description, as JSON.
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.
Success. 200.
JSON object.
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/server
Version and address.
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.
Success. 200.
| Field | Type | Meaning | |
|---|---|---|---|
address | string | required | What the admin configured as this server's public name. Empty when nothing is configured: the portal then falls back to the address the browser used, which is at least true. |
version | string | required | |
build | string | required | The version and the commit it was built from, such as 0.4.0 (abc1234). |
{
"address": "example",
"version": "example",
"build": "example"
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}
GET /api/v1/overview
The numbers on the home screen.
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.
Success. 200.
| Field | Type | Meaning | |||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
transfers | object | required | |||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||
storage | object | required | |||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||
people | People | optional, left out when empty | Only present for an administrator. An ordinary user has no business knowing how many accounts exist. | ||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||
automation | object | required | |||||||||||||||||||||||||||||
It is an object:
| |||||||||||||||||||||||||||||||
{
"transfers": {
"running": 1,
"queued": 1,
"paused": 1,
"doneToday": 1,
"failedToday": 1,
"bytesMovedToday": 1
},
"storage": {
"roots": 1
},
"people": {
"total": 1,
"administrators": 1,
"disabled": 1,
"withoutSecondFactor": 1
},
"automation": {
"hotFolders": 1,
"syncJobs": 1,
"running": true,
"pausedReason": {}
}
}
Errors. The body always has the shape in Errors. Match on code.
| Status | code | When |
|---|---|---|
| 401 | unauthenticated | no key, or a key that is unknown, expired, revoked, or owned by a disabled account |
| 403 | forbidden | the key is valid but does not have the scope, or an administrator route was called by someone who is not an administrator |
| 402 | licence_required | the license does not include the REST API |
{
"error": {
"code": "unauthenticated",
"message": "sign in to continue"
}
}