eCorpus HTTP API

If you're just getting started with the API, you are probably interested in authentication and the Scenes section. The Using the API guide offers a more task-oriented walkthrough.

For more advanced uses, this API is organized in sections (tags in OpenAPI language), each under a namespace prefix.

Introduction

This HTTP API provides all necessary routes to access and edit scenes stored on an eCorpus instance under the /scenes path.

Additionally it provides a number of namespaced utilities for users management (/users), groups management (/groups), authentication, tokens, OAuth2 and ACL edition (/auth), changes history management (/history), gathering scenes under collections (/tags), background tasks management (/tasks), public embedding/discovery services (/services) and instance administration (/admin).

It provides some webDAV utility routes for the /scenes resources but is far from Class 1 Compliance: Only routes that are necessary for proper Voyager support are implemented. WebDAV-specific methods (PROPFIND, MKCOL, MOVE) are declared through OpenAPI 3.2’s additionalOperations.

Other namespaces tends to adhere to a stricter REST philosophy where possible.

Authentication in a nutshell

Requests generally authenticate using a Bearer token (Authorization: Bearer ec_…). Personal tokens can be created from an authenticated user’s personnal page (/ui/user/tokens), then used like this:

curl -XGET -H "Authorization: Bearer ec_xxxxxxxx" "https://<server>/<path>"

See the authentication guide for the complete picture (user levels, tokens and scopes, OAuth2), and the auth section below for the route reference.

API Sections

auth

Authentication, personal access tokens, OAuth2 authorization server, session management and per-scene access-control edition routes.

Every request’s effective authority is the intersection of three factors: the server-wide user level (none < use < create < manage < admin), the scopes delegated to the presented credential (a token carries a frozen family:level scope list; a session carries its user’s full authority) and the per-resource access level (none < read < write < admin). Requests authenticate with a session cookie (obtained through POST /auth/login) or a Bearer token (Authorization: Bearer ec_…); HTTP Basic authentication only identifies a registered OAuth client on the token endpoint (user Basic auth was dropped in v0.3.0 — see the legacy guide for older instances).

See the authentication guide for usage examples or the design document for an in-depth explanation of its structure.

get /auth

Get the identity resolved from the current credentials. Called without authentication, returns the default anonymous user (uid 0, level none).

Responses

200

The current user.

application/json

get /auth/login

Get login data for the current authentication. Content-negotiated: JSON returns the current user, HTML renders the login page.

Query parameters

:redirect

Same-origin URL to redirect to once logged in (HTML flow).

Responses

200

The current user (JSON) or the login page (HTML).

application/json
text/html
string

post /auth/login

Log in with a username and password, opening a server-side session and setting the session cookie. Rate-limited to 10 requests/minute/IP and protected against cross-site (login) CSRF.

Request Body

required

The credentials. Accepts JSON or url-encoded form data.

application/json
usernamerequired
string
passwordrequired
string
application/x-www-form-urlencoded
usernamerequired
string
passwordrequired
string

Responses

200

Logged in. Returns the user (JSON) and sets the session cookie.

application/json
400

Generic HTTP error response whose content depends on the request’s Accept header.

application/json
coderequired
number (int32)
messagerequired
string
text/plain
string
text/html
string
401

Generic HTTP error response whose content depends on the request’s Accept header.

application/json
coderequired
number (int32)
messagerequired
string
text/plain
string
text/html
string
429

Too many login attempts.

Get a signed magic login link for this user. Session-only: requires the non-mintable users:admin scope (an interactive administrator session). Returns the URL as plain text.

Path parameters

:username

Human-readable unique name of a user.

Query parameters

:redirect

Same-origin URL the link should land on.

Responses

200

The signed login link.

text/plain
string

Email a signed login link to the user. Callers holding the users:write scope (administrators) are unlimited; other callers are rate-limited to 1 request/minute/IP. Fails if the user has no registered email.

Path parameters

:username

Human-readable unique name of a user.

Responses

204

The email was queued.

400

The user has no registered email address.

post /auth/logout

Revoke the current server-side session and clear the session cookie.

Request Body

application/json
redirect
string

Responses

200

Logged out.

application/json

get /auth/payload/{payload}

Consume a signed magic-link payload: open a session for the referenced user and redirect. Used to automate the login process.

Path parameters

:payload

Signed, base64url-encoded login payload.

Responses

302

Session opened; redirect to the validated destination.

403

The payload has expired.

400

The payload is malformed or the user no longer matches.

get /auth/sessions

List the requester’s own active sessions. Requires the account:read scope.

Responses

200

The active sessions.

application/json
array of Session

delete /auth/sessions/{id}

Revoke a session by id. Owner-scoped; administrators may revoke anyone’s. Requires the account:write scope.

Path parameters

:id

Session id.

Responses

204

Success.

404

No such session.

get /auth/tokens

List the requester’s personal access tokens (metadata only). Requires the account:read scope.

Responses

200

The tokens.

application/json
array of Token

post /auth/tokens

Create a personal access token. Minting requires the non-mintable account:admin scope, i.e. an interactive session — a token, even all-scoped, cannot mint another token. The secret is returned once, in the token field, and is never stored server-side.

Request Body

required
application/json
namerequired
string
scope
array of TokenScope

Defaults to [“all”].

expires
string (date-time)

Optional expiry date.

Responses

201

The created token, including the one-time secret.

application/json
  • Token
  • tokenrequired
    string
400

Generic HTTP error response whose content depends on the request’s Accept header.

application/json
coderequired
number (int32)
messagerequired
string
text/plain
string
text/html
string
403

Tokens cannot be used to create other tokens.

delete /auth/tokens/{id}

Revoke one of the requester’s own tokens. Requires the account:write scope.

Path parameters

:id

Token id.

Responses

204

Success.

get /auth/oauth/grants

List the OAuth applications the requester has authorized. Requires the account:read scope.

Responses

200

The persisted grants.

application/json
array of OAuthGrant

delete /auth/oauth/grants/{clientId}

Withdraw consent for a client and revoke every token that client holds for the requester. Idempotent. Requires the account:write scope.

Path parameters

:clientId

OAuth client id.

Responses

204

Success.

get /auth/oauth/authorize

OAuth2 authorization endpoint (authorization code + mandatory PKCE). Validates the client and redirect URI, requires a logged-in session, and either issues a code silently (when a covering grant exists) or renders the consent page. Post-validation errors are reported by redirect to redirect_uri.

Query parameters

:client_id required
:redirect_uri required

Must exactly match a URI registered on the client.

:response_type required
:scope required

Space-delimited, non-empty subset of the supported scopes.

:code_challenge required
:code_challenge_method required
:state
:prompt

Responses

200

The consent page (HTML).

text/html
string
302

Redirect back to redirect_uri with a code (or an error), or to the login page.

400

Invalid client or redirect URI (reported on eCorpus’s own page).

post /auth/oauth/authorize

Submit the consent decision. Requires a logged-in session (a token cannot self-consent). On approval, the grant is persisted (union of approved scopes) and a single-use code is issued by redirect.

Request Body

required
application/x-www-form-urlencoded
client_id
string
redirect_uri
string
response_type
string
scope
string
code_challenge
string
code_challenge_method
string
state
string
action
  • approve
  • deny

Responses

302

Redirect to redirect_uri with a code (approved) or error=access_denied (denied).

401

Not session-authenticated.

post /auth/oauth/token

OAuth2 token endpoint. Exchanges a single-use authorization code for a Bearer access token, verifying PKCE (sha256(code_verifier) == code_challenge). Confidential clients authenticate with HTTP Basic (client_secret_basic) or credentials in the body (client_secret_post); public clients present no secret.

Request Body

required
application/x-www-form-urlencoded
grant_typerequired
  • authorization_code
coderequired
string
redirect_urirequired
string
code_verifierrequired
string
client_id
string
client_secret
string

Responses

200

The issued access token.

application/json
access_tokenrequired
string
token_typerequired
  • Bearer
expires_inrequired
number (int32)
scoperequired
string
400

invalid_request, unsupported_grant_type or invalid_grant (bad/expired code or PKCE failure).

application/json
401

invalid_client (bad client credentials).

application/json

post /auth/oauth/revoke

OAuth2 token revocation (RFC 7009). Deletes the token by its secret; possession suffices. Always returns 200, even for unknown tokens.

Request Body

required
application/x-www-form-urlencoded
tokenrequired
string

Responses

200

Acknowledged (whether or not the token existed).

get /auth/oauth/clients

List registered OAuth clients. Requires the instance:read scope.

Responses

200

The registered clients.

application/json
array of OAuthClient

post /auth/oauth/clients

Register an OAuth client. Session-only: requires the non-mintable instance:write scope. Confidential clients (the default) receive a secret, shown once. Redirect URIs must be valid http/https URLs without a fragment.

Request Body

required
application/json
namerequired
string
redirect_urisrequired
array of string
confidential
boolean

Defaults to true. Set false for a public (PKCE-only) client.

Responses

201

The created client, including the one-time client_secret (null for public clients).

application/json
409

A client with this name already exists.

delete /auth/oauth/clients/{id}

Delete an OAuth client, cascading a revocation of every token minted through it. Session-only: requires the non-mintable instance:write scope.

Path parameters

:id

OAuth client id.

Responses

204

Success.

get /auth/access/{scene}

Get a scene’s explicit access grants. Requires the scenes:read scope and read access on the scene — anonymous readers of a public scene cannot enumerate its users.

Path parameters

:scene

Unique name of a scene.

Responses

200

The access grants defined for this scene (per-user and per-group).

application/json
array of AccessGrant
401

Generic HTTP error response whose content depends on the request’s Accept header.

application/json
coderequired
number (int32)
messagerequired
string
text/plain
string
text/html
string

patch /auth/access/{scene}

Edit a scene’s access grants. Requires admin access on the scene. Merge-patch semantics: setting an access to "none" or null removes the grant. Accepts a single grant or an array.

Path parameters

:scene

Unique name of a scene.

Request Body

required
application/json

Responses

204

Success.

get /.well-known/oauth-authorization-server

OAuth2 authorization server metadata (RFC 8414). Publicly cacheable.

Responses

200

The authorization server metadata.

application/json

scenes

Where all the actual data is stored. API design for the /scenes/* makes use of the liberal definition of GET for collections in the webDAV specification to return well-defined JSON documents for those queries, allowing most use cases to bypass cumbersome PROPFIND queries.

get /scenes

Get a list of scenes with optional search parameters. Similar to PROPFIND but returns JSON. Provides advanced search and pagination semantics.

Anonymous requests are allowed and see only publicly-accessible scenes. Can also return a text/plain list of names or a application/zip archive of the matched scenes.

Query parameters

:id

Scene IDs to get. If provided (along with or instead of name), returns exactly those scenes and ignores every other search/pagination parameter. Mainly useful for zip batch downloads.

:name

Scene names to get. If provided, returns exactly those scenes and ignores other search parameters.

:match

Full-text search against a scene’s name, author, authorized users and document meta. When present, results are ranked by relevance. See examples for the accepted syntax.

:access

Return only scenes for which the current user has at least the requested effective access.

:author

Restrict results to scenes originally authored by this user (by uid or username). You’d generally want to use access=admin instead.

:type

Restrict results to a given scene type.

:limit

Limit the number of returned results. Defaults to 10, with a hard maximum of 100.

:offset

Skip the first n results. Use with limit to paginate.

:orderBy

Field to sort by. Defaults to mtime, or rank when a match is provided.

:orderDirection

Switch between ascending and descending order.

:archived

Filter on archival state. Defaults to false (only live scenes). Use the special value any to return both. Requesting archived scenes requires an authenticated, sufficiently-privileged user.

:format

Force the response representation, bypassing Accept header negotiation. The plain-text name list is only available through Accept: text/plain.

Responses

200

A list of scenes matching this query.

By default returns a JSON object with basic metadata for all matched scenes. Can also return a plain-text list of names or a ZIP archive of the matched scenes’ content (zip export requires read access).

Headers
Etag

Weak entity tag for the returned resource, usable for cache validation.

Last-Modified

Last modification date of the resource.

application/json
scenesrequired
array of Scene
text/plain
string
application/zip
string (binary)
304

The resource has not changed since the version referenced by the conditional request.

post /scenes

Import an archive of scenes to be extracted into the scenes/ folder. The credential must carry both the corpus:write (create scenes) and scenes:write (overwrite scenes) scopes. Any authenticated user may start an import: each scene found in the archive is re-checked as it is processed — write access to update an existing scene, create level to create a new one.

eCorpus uses a simplified internal zip decoder guaranteed to handle only data exported from another eCorpus instance. A Content-Length header is required (chunked uploads are not supported).

Request Body

required

A zip file containing an archived scene hierarchy.

application/zip
string (binary)

Responses

200

The import result, one entry per scene found in the archive.

application/json
array of ImportResult
400

Generic HTTP error response whose content depends on the request’s Accept header.

application/json
coderequired
number (int32)
messagerequired
string
text/plain
string
text/html
string

propfind /scenes

List all readable content in scenes/. See rfc4918. A minimal implementation that lists contained elements, respecting the Depth header (defaults to infinite depth).

Responses

207

A PROPFIND multistatus response.

application/xml
<D:multistatus xmlns:D="DAV:">
  <D:responseDescription>string</D:responseDescription>
  <D:response>
    <D:href>https://example.com/foo</D:href>
    <!-- Array of <D:href> -->
    <D:propstat>
      <D:status>HTTP/1.1 200 OK</D:status>
      <D:prop>
        <D:getlastmodified>Tue, 28 May 2024 11:59:56 GMT</D:getlastmodified>
        <D:creationdate>Tue, 28 May 2024 11:59:56 GMT</D:creationdate>
        <D:resourcetype></D:resourcetype>
      </D:prop>
    </D:propstat>
  </D:response>
  <!-- Array of <D:response> -->
</D:multistatus>

get /scenes/{scene}

Get a scene’s metadata as JSON, or a ZIP archive of its current files (content-negotiated, or forced with ?format=zip).

Path parameters

:scene

Unique name of a scene.

Query parameters

:format

Force the response representation.

Responses

200

The requested scene’s data.

Headers
Etag

Entity tag for the returned resource.

Last-Modified

Last modification date of the resource.

application/json
application/zip
string (binary)
404

Scene not found — it doesn’t exist or isn’t readable with the current authentication.

post /scenes/{scene}

Create a new scene initialized from an uploaded GLB model, positioned at (0,0,0), together with a default scene.svx.json document. Requires the corpus:write scope and at least create level.

Path parameters

:scene

Unique name of a scene.

Query parameters

:language

Language of the generated default document (a valid scene language).

Request Body

required

A .glb model file.

model/gltf-binary
string (binary)

Responses

201

A new scene was created and initialized with a default scene.svx.json document.

application/json
400

Generic HTTP error response whose content depends on the request’s Accept header.

application/json
coderequired
number (int32)
messagerequired
string
text/plain
string
text/html
string
409

A scene with this name already exists.

patch /scenes/{scene}

Edit a scene’s metadata. Requires admin access over the scene. All changes are applied in one transaction.

Permissions are additive; to reset a user or group to default permissions, set its access to "none" or null. Tags are treated as a full replacement set: any tag not present in a provided tags array is removed.

Path parameters

:scene

Unique name of a scene.

Request Body

required
application/json
name
string

Rename the scene.

archived
boolean

Archive (true) or restore (false) the scene.

public_access
AccessType
default_access
AccessType
permissions
additional properties
AccessType

Map of username to access level.

groupPermissions
additional properties
AccessType

Map of group name to access level.

tags
array of string

Responses

200

The updated scene data.

application/json

delete /scenes/{scene}

Archive a scene, or force-delete it (instance administrators only).

Path parameters

:scene

Unique name of a scene.

Query parameters

:archive

Defaults to true: archive the scene in a way administrators can undo. Set to false to force-delete it permanently — restricted to instance administrators.

Responses

204

Success.

401

The request presented no valid authentication.

403

Insufficient access rights. Archiving requires admin rights over the scene; force-delete requires instance-level administrative rights.

mkcol /scenes/{scene}

Create a new empty scene. Requires the corpus:write scope and at least create level. The scene is essentially invisible until populated.

Path parameters

:scene

Unique name of a scene.

Responses

201

The empty scene was created.

409

A scene with this name already exists.

propfind /scenes/{scene}

Fetch the scene’s content tree as a WebDAV multistatus response.

Path parameters

:scene

Unique name of a scene.

Responses

207

A PROPFIND multistatus response.

application/xml
<D:multistatus xmlns:D="DAV:">
  <D:responseDescription>string</D:responseDescription>
  <D:response>
    <D:href>https://example.com/foo</D:href>
    <!-- Array of <D:href> -->
    <D:propstat>
      <D:status>HTTP/1.1 200 OK</D:status>
      <D:prop>
        <D:getlastmodified>Tue, 28 May 2024 11:59:56 GMT</D:getlastmodified>
        <D:creationdate>Tue, 28 May 2024 11:59:56 GMT</D:creationdate>
        <D:resourcetype></D:resourcetype>
      </D:prop>
    </D:propstat>
  </D:response>
  <!-- Array of <D:response> -->
</D:multistatus>

get /scenes/{scene}/{file}

Get a file from a scene. Supports single-range requests through the Range header (returning 206). A request for scene.svx.json returns the Voyager document with its asset.id injected so clients can reference it for merge-on-PUT.

Path parameters

:scene

Unique name of a scene.

:file

Relative path to a scene’s file. May contain slashes, though the OpenAPI spec won’t allow them in test queries.

Responses

200

The file. Its Content-Type matches the stored MIME type (common MIME types).

Headers
Etag

Weak entity tag for the file.

Last-Modified

Last modification date of the file.

*/*
string (binary)
206

Partial content, in response to a Range request.

304

Not modified (conditional request).

416

Requested range not satisfiable.

put /scenes/{scene}/{file}

Overwrite a file with new content. Requires write access. A scene.svx.json upload may be structurally merged against the current server state when the enable_document_merge feature is on and the document carries a reference asset.id.

Path parameters

:scene

Unique name of a scene.

:file

Relative path to a scene’s file. May contain slashes, though the OpenAPI spec won’t allow them in test queries.

Request Body

The file’s data.

*/*
string (binary)
application/si-dpo-3d.document+json

Responses

200

An existing file was overwritten.

201

A new file generation was created.

204

A scene document was written directly (no merge).

delete /scenes/{scene}/{file}

Archive a file (creating a deletion generation). It stays accessible through the history API. Requires write access.

Path parameters

:scene

Unique name of a scene.

:file

Relative path to a scene’s file. May contain slashes, though the OpenAPI spec won’t allow them in test queries.

Responses

204

Success.

move /scenes/{scene}/{file}

Move/rename a file within the same scene. Requires write access and a Destination header pointing under the same scene. Cross-scene moves are rejected. The Overwrite header is not supported.

Path parameters

:scene

Unique name of a scene.

:file

Relative path to a scene’s file. May contain slashes, though the OpenAPI spec won’t allow them in test queries.

Responses

201

The file was moved.

mkcol /scenes/{scene}/{file}

Create a folder in a scene. Requires write access.

Path parameters

:scene

Unique name of a scene.

:file

Relative path to a scene’s file. May contain slashes, though the OpenAPI spec won’t allow them in test queries.

Responses

201

The folder was created.

propfind /scenes/{scene}/{file}

Get a file’s (or folder subtree’s) WebDAV properties.

Path parameters

:scene

Unique name of a scene.

:file

Relative path to a scene’s file. May contain slashes, though the OpenAPI spec won’t allow them in test queries.

Responses

207

A PROPFIND multistatus response.

application/xml
<D:multistatus xmlns:D="DAV:">
  <D:responseDescription>string</D:responseDescription>
  <D:response>
    <D:href>https://example.com/foo</D:href>
    <!-- Array of <D:href> -->
    <D:propstat>
      <D:status>HTTP/1.1 200 OK</D:status>
      <D:prop>
        <D:getlastmodified>Tue, 28 May 2024 11:59:56 GMT</D:getlastmodified>
        <D:creationdate>Tue, 28 May 2024 11:59:56 GMT</D:creationdate>
        <D:resourcetype></D:resourcetype>
      </D:prop>
    </D:propstat>
  </D:response>
  <!-- Array of <D:response> -->
</D:multistatus>

history

History management utilities for the /scenes namespace. Scene names in /history directly and uniquely maps to scenes in /scenes. The whole namespace requires write access to the target scene.

get /history/{scene}

Get the full history of a scene’s file modifications (every generation of every file). Requires write access. The total count is returned in the X-Total-Count header.

Path parameters

:scene

Unique name of a scene.

Query parameters

:limit

Limit the number of returned entries (default 10, max 100).

:offset

Skip the first n entries.

:orderDirection

Ordering by time (default desc, newest first).

Responses

200

The scene’s history.

Headers
X-Total-Count

Total number of history entries for this scene.

number (int32)
application/json
array of HistoryEntry
text/plain
string

post /history/{scene}

Restore a scene to a previous point in its history, undoing every change newer than the referenced point. Requires admin access over the scene.

Path parameters

:scene

Unique name of a scene.

Request Body

required

The restore point, identified either by name+generation or by a single file id.

application/json
  • namerequired
    string
    generationrequired
    number (int32)
  • idrequired
    Uid

Responses

200

The scene was rolled back.

application/json
coderequired
number (int32)
messagerequired
string
changesrequired
array of string

get /history/{scene}/{id}/diff

Diff the file generation {id} against the previous one. For two SVX documents a structured JSON-pointer change summary is produced; otherwise a unified diff -u. Requires write access.

Path parameters

:scene

Unique name of a scene.

:id

A file id (a history reference point).

Responses

200

The computed diff.

text/plain
string
application/json
diff
string

get /history/{scene}/{id}/diff/{from}

Same as /history/{scene}/{id}/diff but against an explicit base generation/file. Requires write access.

Path parameters

:scene

Unique name of a scene.

:id

A file id (a history reference point).

:from

The comparison base: a positive file id, or a negative offset applied to {id}’s generation (-1, the default when this segment is omitted, means the previous generation).

Responses

200

The computed diff.

text/plain
string
application/json
diff
string

get /history/{scene}/{id}/show/{file}

Fetch a file as it existed at the history point {id} — the newest generation of {file} that is not newer than {id} (inclusive: if {id} is a generation of {file} itself, that very generation is returned). Requires write access.

Path parameters

:scene

Unique name of a scene.

:id

A file id (a history reference point).

:file

Relative path to a scene’s file. May contain slashes, though the OpenAPI spec won’t allow them in test queries.

Responses

200

The historical file bytes, with its stored MIME type.

*/*
string (binary)
404

The file did not exist at that point in history.

tags

Collections (tags) management routes.

get /tags

Get the list of tags defined on this server, each with the number of scenes carrying it.

Responses

200

The list of tags.

application/json
array of Tag

patch /tags

Batch-add or remove tags on scenes. Requires an identified requester (the corpus:read baseline — any level, any token); each item is authorized individually against the requester’s write access on the target scene.

Request Body

required

A single operation or an array of operations.

application/json

Responses

200

The operations were applied.

get /tags/{tag}

Get all scenes associated with this tag that are visible to the requester.

Path parameters

:tag

Name of a tag.

Responses

200

The scenes carrying this tag.

application/json
array of Scene

users

Users management

get /users

Get the list of registered users. Requires the users:read scope (administrators only — no other level holds it).

Query parameters

:limit

Limit the number of results (1–100).

:offset

Skip the first n results.

Responses

200

An array of all users registered on this server.

application/json
array of User
401

Generic HTTP error response whose content depends on the request’s Accept header.

application/json
coderequired
number (int32)
messagerequired
string
text/plain
string
text/html
string

post /users

Create a new user. Requires the users:write scope — mintable, so a provisioning script may run on a token — except when no users exist yet, allowing the first account to be bootstrapped unauthenticated. Creating an administrator requires the non-mintable users:admin scope (an interactive admin session).

Request Body

required

The user to create.

application/json
usernamerequired
string
emailrequired
string
passwordrequired
string

At least 8 characters.

level
UserLevel
lang
  • en
  • fr

Language of the onboarding email.

send_onboarding
boolean

Send an onboarding email to the new user.

Responses

201

User created successfully.

application/json
400

Generic HTTP error response whose content depends on the request’s Accept header.

application/json
coderequired
number (int32)
messagerequired
string
text/plain
string
text/html
string
409

A user with this username or email already exists.

delete /users/{uid}

Delete a user. Requires the users:write scope. A user cannot delete themselves.

Path parameters

:uid

Unique ID of a user (stays stable through user renames).

Responses

204

Success.

400

Bad request (e.g. attempting self-deletion).

patch /users/{uid}

Change a user’s data. Administrators may edit any account, including its level (but cannot demote themselves). A regular user may only edit their own account and cannot change their own level. At least one editable field must be provided. Session-only: gated on the non-mintable account:admin scope (a patch may rotate a password or email, converting a credential into a session), so no token — even all-scoped — can reach it.

Path parameters

:uid

Unique ID of a user (stays stable through user renames).

Request Body

required
application/json
username
string
email
string
password
string

Changing your own password evicts all your other sessions.

level
UserLevel

Responses

200

The updated user.

application/json

get /users/{uid}/sessions

List a user’s active sessions. Requires the users:read scope.

Path parameters

:uid

Unique ID of a user (stays stable through user renames).

Responses

200

The user’s sessions.

application/json
array of Session

get /users/{uid}/tokens

List a user’s personal access tokens (metadata only). Requires the users:read scope.

Path parameters

:uid

Unique ID of a user (stays stable through user renames).

Responses

200

The user’s tokens.

application/json
array of Token

delete /users/{uid}/tokens/{id}

Revoke one of a user’s tokens. Requires the users:write scope.

Path parameters

:uid

Unique ID of a user (stays stable through user renames).

:id

Token id.

Responses

204

Success.

groups

User groups management. A group bundles users together and can hold per-scene access grants that all its members inherit. Members can read their own groups; managing groups requires the manage level (the groups:admin scope).

get /groups

List all groups (name and id only). Requires the groups:read scope (the manage level and above).

Responses

200

The list of groups.

application/json
array of Group

post /groups

Create a group, optionally with an initial member list. Requires the groups:admin scope (the manage level and above).

Request Body

required
application/json
groupNamerequired
string
members
array of string

Usernames (or numeric user ids) of initial members.

Responses

201

The created group.

application/json
409

A group with this name already exists.

get /groups/{group}

Get a group with its members and per-scene access grants. Requires read access on the group: members can read their own group, manage-level users any group.

Path parameters

:group

Name of a group.

Responses

200

The group, including members and scenes.

application/json
404

No such group.

delete /groups/{group}

Delete a group. Requires admin access on the group (the manage level).

Path parameters

:group

Name of a group.

Responses

204

Success.

404

No such group.

put /groups/{group}/{member}

Add a user to a group (idempotent). Requires admin access on the group (the manage level).

Path parameters

:group

Name of a group.

:member

Username of the member.

Responses

200

The member was added (or was already present).

404

No such user or group.

delete /groups/{group}/{member}

Remove a user from a group. Requires admin access on the group (the manage level).

Path parameters

:group

Name of a group.

:member

Username of the member.

Responses

204

Success.

404

No such membership.

tasks

Background tasks: schedulable, persisted units of server-side work (uploads, archive extraction, scene creation, email sending, cleanup jobs…). Access is gated by the tasks:* scope family plus a per-task ACL: a task’s owner has admin access to it; anyone else derives access from the task’s scene.

post /tasks

Create a background task. Requires the tasks:write scope and at least create level. A pending task runs immediately; an initializing task prepares a workspace (e.g. for a resumable upload) without running.

Request Body

required
application/json
typerequired
string

A known task handler name.

data
additional properties
any

Task-type-specific payload.

status
  • initializing
  • pending

Defaults to “pending”.

Responses

201

The created task.

application/json
400

Generic HTTP error response whose content depends on the request’s Accept header.

application/json
coderequired
number (int32)
messagerequired
string
text/plain
string
text/html
string

get /tasks/{id}

Get a task and its log lines. Accessible to the task owner, to users with read access on the task’s scene, and to administrators.

Path parameters

:id

A task id.

Responses

200

The task and its logs.

application/json
taskrequired
Task
logsrequired
array of TaskLog

delete /tasks/{id}

Delete a task and its workspace. Requires admin access to the task (owner, scene admin, or instance admin); a token credential must carry the tasks:admin scope.

Path parameters

:id

A task id.

Responses

204

Success.

get /tasks/{id}/tree

Get a task with its descendant subtasks nested under children, plus a flat, time-ordered log of every task in the tree. Requires read access to the root task.

Path parameters

:id

A task id.

Responses

200

The task tree and merged logs.

application/json
taskrequired
TaskNode
logsrequired
array of TaskLog

get /tasks/{id}/artifact

Download a finished task’s workspace, as a ZIP archive or a JSON file listing. The task must have succeeded. Requires read access to the task.

Path parameters

:id

A task id.

Responses

200

The task workspace.

application/zip
string (binary)
application/json
filesrequired
array of WorkspaceFile
405

The task has not succeeded yet.

406

Neither application/zip nor application/json was acceptable.

put /tasks/{id}/artifact

Upload the payload of a parseUserUpload task, in one shot or as resumable chunks (Content-Range, GCS-style). The task must be owned by the requester and be in the initializing state; ownership grants the required admin task access, and a token credential must carry the tasks:admin scope. On the final chunk the parse handler runs.

Path parameters

:id

A task id.

Request Body

required
*/*
string (binary)

Responses

201

Upload complete; the parse task was started.

206

Chunk accepted; more data expected. The Range header echoes the received range.

400

Malformed range, length mismatch, or not an upload task.

411

A Content-Length header is required.

416

Range gap; the Range header tells the client where to resume.

services

Public, CORS-enabled discovery/embedding endpoints (OpenSearch description and oEmbed provider).

get /services/opensearch.xml

Public OpenSearch 1.1 description document for browser search-engine discovery.

Responses

200

The OpenSearch description.

application/opensearchdescription+xml
<xml>string</xml>
304

Not modified (conditional request).

get /services/oembed

Public oEmbed provider. Returns an embeddable representation (an iframe) for a public scene or tag page URL.

Query parameters

:url required

The eCorpus scene or tag page URL to embed (must resolve to a publicly-accessible resource).

:format

Response format (defaults to json).

:maxwidth

Maximum embed width (defaults to 800).

:maxheight

Maximum embed height (defaults to 450).

Responses

200

The oEmbed response.

application/json
text/xml
<xml>string</xml>
400

Missing or invalid url.

404

No public embeddable resource at that URL.

501

Unsupported format.

admin

Administrative tasks routes, reserved to instance administrators.

get /admin/stats

Get server usage statistics (data footprint, process load) and build metadata. Requires the instance:read scope (mintable, for monitoring agents).

Responses

200

Server statistics.

application/json
text/plain
string

get /admin/config

Get the full instance configuration. Requires the instance:read scope.

⚠️ The JSON form exposes every configuration key, including env-only static values such as database_uri (which may embed a database password), files_dir, root_dir, etc. Handle the response as sensitive.

Responses

200

The configuration, keyed by config name.

application/json
additional properties
ConfigEntry
text/plain
string

dotenv-style KEY=”value” lines.

patch /admin/config

Update one or more runtime configuration values. Session-only: requires the non-mintable instance:write scope. Keys locked by an environment variable cannot be changed; static (env-only) keys are always locked.

Request Body

required

A map of config key to new value. Each value may be a bare scalar or an object with a value field.

application/json
additional properties
any

Responses

204

The configuration was updated.

400

Malformed body, missing value, or unknown key.

403

The key is locked (set via an environment variable).

post /admin/mail/test

Send a test email. Session-only: requires the non-mintable instance:write scope. Defaults to the requesting admin’s own address. A to address ending in @example.com short-circuits: no mail is sent and the rendered HTML is returned.

Request Body

application/json
to
string

Recipient. Defaults to the current admin’s email.

Responses

200

The send result, or (for an @example.com recipient) the rendered mail HTML.

application/json
  • message
    string
  • messageId
    string
    accepted
    array of string
    rejected
    array of string
    response
    string
400

No recipient could be determined.

get /admin/mail/render/{name}

Render an email template to a standalone HTML preview (no mail is sent). Requires the instance:read scope.

Path parameters

:name

Email template name (under emails/), e.g. test or invite.

Query parameters

:username

Recipient display name to inject (defaults to the requester’s username).

Responses

200

The rendered email preview.

text/html
string

Models

Reusable schemas referenced by the operations above.

Scene

idrequired
Uid
namerequired
string
ctimerequired
string (date-time)
mtimerequired
string (date-time)
author_idrequired
authorrequired
string
thumb
string | null

URI (in scene lists) or filename (elsewhere) of the scene’s thumbnail, if any.

tagsrequired
array of string
accessrequired
AccessType
public_accessrequired
AccessType
default_accessrequired
AccessType
archivedrequired
string (date-time) | null

Archival date, or null when the scene is live.

type
  • html
  • voyager
| null

HistoryEntry

idrequired
Uid
namerequired
string
mimerequired
string
generationrequired
number (int32)
ctimerequired
string (date-time)
authorrequired
string
author_idrequired
sizerequired
number (int32)

One generation of one file in a scene’s history.

ImportResult

namerequired
string
actionrequired
  • create
  • update
  • error
error

Present when action is error.

User

uidrequired
Uid
usernamerequired
string
email
string
levelrequired
UserLevel

Public user representation (User.safe); never includes the password.

UserLevel

  • none
  • use
  • create
  • manage
  • admin

Server-wide user level, ordered none < use < create < manage < admin. New accounts default to create.

Group

groupUidrequired
Uid
groupNamerequired
string
members
array of string

Member usernames.

scenes
array of
scenerequired
string
accessrequired
AccessType

A user group. List responses carry only groupUid/groupName; a single-group GET additionally populates members and scenes.

Tag

namerequired
string
sizerequired
number (int32)

Number of scenes carrying the tag.

TagOperation

namerequired
string
actionrequired
  • create
  • delete
scenerequired

Target scene name or id.

Session

idrequired
number (int32)
uidrequired
Uid
createdrequired
string (date-time)
expiresrequired
string (date-time)
lastSeenrequired
string (date-time)
userAgent
string | null

Token

idrequired
number (int32)
uidrequired
Uid
namerequired
string
scoperequired
array of TokenScope
client
string | null

Set when the token was issued through an OAuth client.

createdrequired
string (date-time)
expires
string (date-time) | null
lastUsed
string (date-time) | null

Personal access token metadata. The secret is only ever returned once, on creation.

TokenScope

  • all
  • corpus:read
  • corpus:write
  • scenes:read
  • scenes:write
  • scenes:admin
  • tasks:read
  • tasks:write
  • tasks:admin
  • users:read
  • users:write
  • groups:read
  • groups:write
  • groups:admin
  • instance:read
  • account:read
  • account:write

A mintable scope, of the form family:level: holding a level implies every lower one on the read ⊂ write ⊂ admin ladder, and all expands to every mintable scope. The non-mintable scopes (account:admin, users:admin, instance:write) can never be put on a token or OAuth grant — only an interactive session holds them.

OAuthClient

idrequired
number (int32)
namerequired
string
redirect_urisrequired
array of string
confidentialrequired
boolean
createdrequired
string (date-time)

OAuthGrant

client_idrequired
number (int32)
client_namerequired
string
scoperequired
array of TokenScope
createdrequired
string (date-time)
updatedrequired
string (date-time)

OAuthError

errorrequired
string
error_description
string

OAuthServerMetadata

issuerrequired
string
authorization_endpointrequired
string
token_endpointrequired
string
revocation_endpoint
string
response_types_supported
array of string
grant_types_supported
array of string
code_challenge_methods_supported
array of string
token_endpoint_auth_methods_supported
array of string
scopes_supported
array of string

AccessGrant

  • uidrequired
    Uid
    usernamerequired
    string
    accessrequired
    AccessType
  • groupUidrequired
    Uid
    groupNamerequired
    string
    accessrequired
    AccessType

An access grant on a scene, for a user or a group.

AccessGrantEdit

username
string
uid
Uid
groupName
string
groupUid
Uid
accessrequired
AccessType

Grant edit. Identify the target by username/uid (user grant) or groupName/groupUid (group grant). An access of "none" or null removes the grant.

Task

task_idrequired
number (int32)
scene_id
number (int32) | null
user_id
number (int32) | null
parent
number (int32) | null
after
array of number (int32)

Ids of prerequisite tasks.

typerequired
string

Task handler name.

ctimerequired
string (date-time)
data
additional properties
any

Task-type-specific payload.

output
additional properties
any

Handler return value once finished (shape depends on the task type).

statusrequired
TaskStatus

TaskNode

TaskStatus

  • initializing
  • pending
  • running
  • aborting
  • success
  • error

TaskLog

log_idrequired
number (int32)
task_idrequired
number (int32)
timestamprequired
string (date-time)
severityrequired
  • trace
  • debug
  • info
  • warn
  • error
  • fatal
messagerequired
string

WorkspaceFile

pathrequired
string
sizerequired
number (int32)
ctimerequired
string (date-time)
mtimerequired
string (date-time)

ServerStats

usagerequired
mtime
string (date-time)
datarequired
sizerequired
string

Total deduplicated file size in bytes, as a decimal string.

scenesrequired
number (int32)
processrequired
coresrequired
number (int32)
freememrequired
number (int32)
loadrequired
array of number
buildrequired
refrequired
string
migration_idrequired
string

ConfigEntry

valuerequired
  • string
  • number
  • boolean

Current value (typed).

lockedrequired
boolean

True when set/pinned by an environment variable.

typerequired
  • text
  • color
  • checkbox
  • number

HTML input hint.

defaultValuerequired
  • string
  • number
  • boolean

Default value (typed).

OEmbed

versionrequired
  • 1.0
typerequired
  • rich
provider_namerequired
string
provider_urlrequired
string
widthrequired
number (int32)
heightrequired
number (int32)
titlerequired
string
author_name
string
htmlrequired
string

The embeddable iframe markup.

StatusMessage

coderequired
number (int32)
messagerequired
string

Uid

integer (int64)

Numeric unique id. Applicable to users, scenes or files, but ids are not expected to be unique across namespaces. Represented as a JSON number.

AccessType

  • none
  • read
  • write
  • admin
| null

A scene access level, ordered none < read < write < admin. null is equivalent to unset.

Etag

string

Unique string describing the resource state, generally used for caching. A base64url sha256 sum is typically used but the exact format’s stability shouldn’t be relied upon.

LastModified

string (date-time)

A date’s UTC representation.

WebDAVMultistatus

responseDescription
string
responserequired

XML document returned for a PROPFIND request as defined in rfc4918. See rfc4918#xml.element.definitions.

WebDAVResponse

hrefrequired
array of string
propstat
WebDAVPropstat

Part of a webDAV multistatus XML response object.

WebDAVPropstat

statusrequired
string
prop
getlastmodified
WebDAVDate
creationdate
WebDAVDate
resourcetype

Propstat Object.

WebDAVDate

string (date-time)

A date, formatted as a GMT ISO string.