aWhere Manual
DevelopAPI

API guide

Call the platform's API with a bearer token or an API key. Where each endpoint is, what an error looks like and how paging works.

The whole API of the platform is at <site>/api/..., and every request needs a bearer token or an API key. In this page <site> is the address of your site, for example https://platform.example.com.

Try it:

curl -H "X-API-Key: $KEY" https://platform.example.com/api/profile

If you get the profile of the key's owner back, the key works.

Addresses

Each path starts at one node. The page of each node lists every endpoint.

PathNode
/api/authauthorization-server issues tokens, see a bearer token from an OAuth client
/api/itemitem-services works on Items and Records, and tile-services sends tiles under /api/item/:id/actions/getTile
/api/filefile-services sends files
/api/realtimerealtime-services pushes events over socket.io
the rest of /apicore-services users, groups, Drive, the profile and the administration

item-services sends an OpenAPI document made from its code at /api/item/openapi/openapi.json, and a viewer at /api/item/openapi/ui. These two need no sign-in.

Authentication

Always send the credential in a header, never in the URL, because URLs are logged in many places. There is no other way: no endpoint takes a username and password, and no header x-auth-* is believed.

WayHeaderUse it when
bearer tokenAuthorization: Bearer <token>An App where a person signs in and the App acts for them
API keyX-API-Key: <key>A program that works by itself, with nobody signed in
signed linknone, sig is in the URLShowing or downloading a file in an <img> or a link

A bearer token from an OAuth client

You need an OAuth client first. Make one in Account (OAuth client) or ask an administrator (OAuth clients of the site). Note the Client ID and the redirect URI registered with it, then follow these steps, which use PKCE.

  1. Make a random code_verifier and compute the code_challenge from it.

    verifier=$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-64)
    challenge=$(printf %s "$verifier" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
  2. Send the user to the sign-in page. The redirect_uri must start with a value registered in the client.

    https://platform.example.com/api/auth/oauth2/authorize
      ?response_type=code
      &client_id=my-app
      &redirect_uri=https://app.example.com/callback
      &code_challenge=<challenge>
      &code_challenge_method=sha256
      &state=<a random value>
  3. After the sign-in the browser comes back to redirect_uri with code and state. Check that state is the one you sent.

  4. Exchange the code for a token.

    curl -s https://platform.example.com/api/auth/oauth2/token \
      -d grant_type=authorization_code \
      -d client_id=my-app \
      -d code="$CODE" \
      -d code_verifier="$verifier" \
      -d redirect_uri=https://app.example.com/callback
    { "access_token": "…", "refresh_token": "…", "expires_in": 86400, "token_type": "Bearer" }
  5. Call the API with Authorization: Bearer <access_token>.

The access_token lasts about a day (expires_in is in seconds). The refresh_token lasts about 30 days and works once. Renew with:

curl -s https://platform.example.com/api/auth/oauth2/token \
  -d grant_type=refresh_token -d client_id=my-app \
  -d refresh_token="$REFRESH" -d code_verifier="$verifier"

If your server can keep a secret, send client_secret in the body instead of code_verifier, or use HTTP Basic (client_id:client_secret), on the endpoint /oauth2/token only. Revoke a token with POST /api/auth/oauth2/revoke and the body token=<access_token>.

An API key

Make one in Account (personal API keys) or ask an administrator (API keys of the site). The key acts for its owner, and does only what its scopes allow.

ScopeAllows
public:readerReading public content
items:readerReading the Items that the key's owner owns or that are shared with them
items:organizerManaging the Items that the key's owner owns
groups:reader, groups:organizerReading, or managing, the groups of the key's owner
admin:reader, admin:organizerReading, or managing, administration data such as users, roles, clients and API keys

GET /api/credential/scope lists every scope of this Release. The endpoints under /api/admin need a key with a scope that starts with admin, and the owner must hold an administration permission.

A file in an <img> or a download link cannot send a header. Ask file-services for a link with your token or API key:

curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"files":[{"id":"<file id>","use":"content"}]}' \
  https://platform.example.com/api/file/links

use is content (show in a page), download (save) or thumbnail with a width of 160, 320, 640, 1280 or 2048. The answer has links for each file, and denied, the ids of the files you cannot read. A link is a path that starts with /api/file/ and carries sig. A plain GET opens it, with no header, until the end of the next whole hour (between one and two hours). A system key cannot ask for links, because a link is tied to one user.

Item roles

The endpoints under /api/item/:id check your role on that Item. The tables in item-services name the lowest role that is needed, and a higher role can do it too.

Role in the tablesRole in the Apps
readerReader
writerWriter
itemOrganizerItem manager

A person's role is set when the Item is shared, see Share. A key without the scope items:organizer cannot do what needs more than reader.

Errors

Every node answers with an HTTP status and a JSON body, and the bodies differ a little.

NodeBody
core-services{ "errors": [ { "code": "FORBIDDEN", "message": "Forbidden." }, { "message": "INSUFFICIENT_PERMISSION" } ] } The first entry is the kind, the next ones give the reason.
item-services{ "message": "Not found", "code": 404, "type": "NOT_FOUND_ITEM" } code is the HTTP status and type gives the reason.
file-services{ "message": "Not found", "code": 404, "type": "NOT_FOUND_FILE" } For a 5xx, type is only INTERNAL_ERROR.

The statuses you meet most:

StatusWhenReasons you see
401No credential, or one that does not workINVALID_TOKEN, EXPIRED_TOKEN, INVALID_API_KEY, EXPIRED_API_KEY, INVALID_AUTH_TYPE
403Signed in, but not allowedINSUFFICIENT_SCOPE, INSUFFICIENT_PERMISSION
404Not found, or you may not see itNOT_FOUND_ITEM, NOT_FOUND_FILE
405The path does not take that method

/api/file answers 404 alike for a file that does not exist and a file that you cannot read, so nobody can guess ids.

Paging

Most endpoints that list take skip and limit, in the query string of a GET or the body of a POST. limit is 10 unless you say otherwise, and limit=0 means no limit. They also take keyword to search, sort as JSON such as {"name":1}, and query as a JSON condition.

curl -H "X-API-Key: $KEY" \
  "https://platform.example.com/api/admin/user?keyword=somchai&skip=20&limit=10"

The answers of core-services and item-services carry total (how many match) and data (this page):

{ "total": 134, "data": [ … ] }

On this page