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.
Responses
200
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
requiredThe 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)
HTTP Status code.
- messagerequired
- string
text/plain
text/html
401
Generic HTTP error response whose content depends on the request’s Accept header.
application/json
- coderequired
- number (int32)
HTTP Status code.
- messagerequired
- string
text/plain
text/html
429
Too many login attempts.
get /auth/login/{username}/link
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. string
|
Responses
200
The signed login link.
text/plain
post /auth/login/{username}/link
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
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
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
requiredapplication/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)
HTTP Status code.
- messagerequired
- string
text/plain
text/html
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
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
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
requiredapplication/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
approvedeny
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
requiredapplication/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
requiredapplication/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
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
requiredapplication/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
- OAuthClient
- client_secret
- string | null
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
401
Generic HTTP error response whose content depends on the request’s Accept header.
application/json
- coderequired
- number (int32)
HTTP Status code.
- messagerequired
- string
text/plain
text/html
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. |
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 array of Uid |
|||||||||
:name
|
Scene names to get. If provided, returns exactly those scenes and ignores other search parameters. array of string |
|||||||||
: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. string
Examples
|
|||||||||
: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 |
|||||||||
:type
|
Restrict results to a given scene type.
|
|||||||||
:limit
|
Limit the number of returned results. Defaults to 10, with a hard maximum of 100. number (int32)
|
|||||||||
:offset
|
Skip the first n results. Use with number (int32)
|
|||||||||
:orderBy
|
Field to sort by. Defaults to
|
|||||||||
:orderDirection
|
Switch between ascending and descending order.
|
|||||||||
:archived
|
Filter on archival state. Defaults to
|
|||||||||
:format
|
Force the response representation, bypassing
|
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
application/zip
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
requiredA zip file containing an archived scene hierarchy.
application/zip
Responses
200
The import result, one entry per scene found in the archive.
application/json
400
Generic HTTP error response whose content depends on the request’s Accept header.
application/json
- coderequired
- number (int32)
HTTP Status code.
- messagerequired
- string
text/plain
text/html
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
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). string
|
Request Body
requiredA .glb model file.
model/gltf-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)
HTTP Status code.
- messagerequired
- string
text/plain
text/html
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
requiredapplication/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 boolean
|
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. |
*/*
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.
*/*
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). number (int32)
|
:offset
|
Skip the first n entries. number (int32)
|
:orderDirection
|
Ordering by time (default
|
Responses
200
The scene’s history.
Headers
X-Total-Count
|
Total number of history entries for this scene. number (int32)
|
application/json
text/plain
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
requiredThe 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
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 |
Responses
200
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.
*/*
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
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
requiredA single operation or an array of operations.
application/json
- TagOperation
- array of TagOperation
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
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). number (int32)
|
:offset
|
Skip the first n results. number (int32)
|
Responses
200
An array of all users registered on this server.
application/json
401
Generic HTTP error response whose content depends on the request’s Accept header.
application/json
- coderequired
- number (int32)
HTTP Status code.
- messagerequired
- string
text/plain
text/html
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
requiredThe user to create.
application/json
- usernamerequired
- string
- emailrequired
- string
- passwordrequired
- string
At least 8 characters.
- level
- UserLevel
- lang
enfr
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)
HTTP Status code.
- messagerequired
- string
text/plain
text/html
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
requiredapplication/json
- username
- string
- 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
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
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
post /groups
Create a group, optionally with an initial member list. Requires the groups:admin scope (the manage level and above).
Request Body
requiredapplication/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
requiredapplication/json
- typerequired
- string
A known task handler name.
- data
- additional properties
- any
Task-type-specific payload.
- status
initializingpending
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)
HTTP Status code.
- messagerequired
- string
text/plain
text/html
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
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
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
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*/*
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). string
|
:format
|
Response format (defaults to
|
:maxwidth
|
Maximum embed width (defaults to 800). number (int32)
|
:maxheight
|
Maximum embed height (defaults to 450). number (int32)
|
Responses
200
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
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
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
requiredA 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 |
Query parameters
:username
|
Recipient display name to inject (defaults to the requester’s username). string
|
Responses
200
The rendered email preview.
text/html
Models
Reusable schemas referenced by the operations above.
Scene
- idrequired
- Uid
- namerequired
- string
- ctimerequired
- string (date-time)
- mtimerequired
- string (date-time)
- author_idrequired
- Uid
- null
- 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
htmlvoyager
HistoryEntry
ImportResult
- namerequired
- string
- actionrequired
createupdateerror
- error
Present when
actioniserror.
User
UserLevel
noneusecreatemanageadmin
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
createdelete
- scenerequired
- string
- Uid
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
allcorpus:readcorpus:writescenes:readscenes:writescenes:admintasks:readtasks:writetasks:adminusers:readusers:writegroups:readgroups:writegroups:admininstance:readaccount:readaccount: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
initializingpendingrunningabortingsuccesserror
TaskLog
- log_idrequired
- number (int32)
- task_idrequired
- number (int32)
- timestamprequired
- string (date-time)
- severityrequired
tracedebuginfowarnerrorfatal
- 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
textcolorcheckboxnumber
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
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
nonereadwriteadmin
A scene access level, ordered none < read < write < admin. null is equivalent to unset.
Etag
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
A date’s UTC representation.
WebDAVMultistatus
- responseDescription
- string
- responserequired
- array of WebDAVResponse
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
A date, formatted as a GMT ISO string.