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/profileIf 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.
| Path | Node |
|---|---|
/api/auth | authorization-server issues tokens, see a bearer token from an OAuth client |
/api/item | item-services works on Items and Records, and tile-services sends tiles under /api/item/:id/actions/getTile |
/api/file | file-services sends files |
/api/realtime | realtime-services pushes events over socket.io |
the rest of /api | core-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.
| Way | Header | Use it when |
|---|---|---|
| bearer token | Authorization: Bearer <token> | An App where a person signs in and the App acts for them |
| API key | X-API-Key: <key> | A program that works by itself, with nobody signed in |
| signed link | none, sig is in the URL | Showing 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.
-
Make a random
code_verifierand compute thecode_challengefrom 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 '=') -
Send the user to the sign-in page. The
redirect_urimust 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> -
After the sign-in the browser comes back to
redirect_uriwithcodeandstate. Check thatstateis the one you sent. -
Exchange the
codefor 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" } -
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.
| Scope | Allows |
|---|---|
public:reader | Reading public content |
items:reader | Reading the Items that the key's owner owns or that are shared with them |
items:organizer | Managing the Items that the key's owner owns |
groups:reader, groups:organizer | Reading, or managing, the groups of the key's owner |
admin:reader, admin:organizer | Reading, 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.
Signed links
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/linksuse 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 tables | Role in the Apps |
|---|---|
reader | Reader |
writer | Writer |
itemOrganizer | Item 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.
| Node | Body |
|---|---|
| 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:
| Status | When | Reasons you see |
|---|---|---|
| 401 | No credential, or one that does not work | INVALID_TOKEN, EXPIRED_TOKEN, INVALID_API_KEY, EXPIRED_API_KEY, INVALID_AUTH_TYPE |
| 403 | Signed in, but not allowed | INSUFFICIENT_SCOPE, INSUFFICIENT_PERMISSION |
| 404 | Not found, or you may not see it | NOT_FOUND_ITEM, NOT_FOUND_FILE |
| 405 | The 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": [ … ] }