Using the API
eCorpus provides a comprehensive API that covers the needs of DPO Voyager while adding user management interfaces, access rights control, organization of scenes into collections, and more…
Authentication
eCorpus has two distinct notions of authority that combine on every request:
- a server-wide user level, and
- a per-scene access level.
Note: HTTP Basic authentication with a username and password was dropped in eCorpus v0.3.0.
Authorization: Basic …is only used to authenticate a registered OAuth client onPOST /auth/oauth/token. If your instance still runs v0.2.x, see the legacy authentication guide.
User levels
Every account has exactly one level. They are ordered — a higher level includes the abilities of the lower ones:
| Level | Meaning |
|---|---|
none |
Quarantine level: the account may sign in, but holds no account:* scope, so it cannot mint or revoke its own credentials. The interface never assigns it. Anonymous requests carry no account at all. |
use |
Authenticated user. Can consume scenes they are granted access to, but cannot create scenes. |
create |
Default level of a newly-created account. Can create scenes. |
manage |
Can additionally manage groups. |
admin |
Instance administrator. Implicitly has admin access over every scene and can reach the /admin routes. |
Per-scene access levels
Independently of their level, a user is granted an access level over each scene, ordered
none < read < write < admin. The effective access on a scene is the maximum of:
- the user’s explicit per-user grant (see
PATCH /auth/access/{scene}), - any grant inherited from a group the user belongs to,
- the scene’s
default_access(applies to any logged-in user; capped atwrite), - the scene’s
public_access(applies to everyone including anonymous visitors; capped atread), - the administrator override (
adminusers always getadmin).
An anonymous visitor only ever gets a scene’s public_access.
Authenticating a request
A request is identified, in order of precedence:
- Bearer token — send
Authorization: Bearer ec_…. Used by scripts, services and CLIs. A token that is presented but invalid/revoked is a hard401(it never silently falls back to anonymous). - Session cookie — the browser login path. The
sessioncookie only carries an opaque session id; the identity (and the account’s current level) is re-read server-side on every request, so logouts, password changes and level changes take effect immediately. Sessions last 31 days and auto-renew (sliding window).
Create a token from the web interface (or with POST /auth/tokens from a logged-in session), then use it as a
Bearer token:
curl -XGET -H "Authorization: Bearer ec_xxxxxxxx" https://ecorpus.holusion.com/[...]
You can also open a browser-style session and reuse its cookie, though that will create unrestricted access credentials stored as clear text on your disk:
# Log in, saving the session cookie to a jar
curl -c cookies.txt -XPOST https://ecorpus.holusion.com/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"<username>","password":"<password>"}'
# Reuse the cookie on subsequent requests
curl -b cookies.txt -XGET https://ecorpus.holusion.com/[...]
Personal access tokens
A logged-in user mints a token with POST /auth/tokens, from an interactive session only — a token can
never create another token, even an all-scoped one. You choose a name, a set of scopes and an optional
expiry; the ec_… secret is returned once and never stored server-side. A token can never do more
than its owner’s current level allows, and its scopes further cap it.
A scope is written <family>:<level>, and holding a level implies every lower one
(read ⊂ write ⊂ admin):
| Scope | Grants |
|---|---|
all |
Every mintable scope — shorthand for the whole table below. |
corpus:read |
The identity baseline, “a recognized user of this instance”. Carried by every token whatever it was minted with. |
corpus:write |
Add scenes to the collection (scene creation, archive import). |
scenes:read / scenes:write / scenes:admin |
Caps the level obtainable on the scenes the ACL already grants (visibility is unchanged). |
tasks:read / tasks:write / tasks:admin |
The /tasks API. |
users:read / users:write |
Administration of other accounts: inventory, provisioning, revoking their tokens. |
groups:read / groups:admin |
Group inventory and membership. |
instance:read |
Instance statistics and configuration inventory (the monitoring use case). |
account:read / account:write |
List and revoke your own sessions, tokens and OAuth grants. |
Three scopes are non-mintable: account:admin (minting credentials, changing your own password or email),
users:admin (login links, creating administrators) and instance:write (configuration rewrite, OAuth client
registry). No token — not even an all-scoped one — ever carries them; they require an interactive session.
List and revoke your tokens with GET/DELETE /auth/tokens. Anyone holding a token can revoke it through
POST /auth/oauth/revoke.
OAuth2 (authorization code + PKCE)
For third-party applications. An administrator registers a client (POST /auth/oauth/clients) with one or
more redirect URIs; confidential clients receive a secret, public clients (CLIs, SPAs) rely on
PKCE (Proof Key for Code Exchange) only.
The flow is a standard authorization-code grant with mandatory PKCE (S256):
- Send the user to
GET /auth/oauth/authorizewithclient_id, an exactredirect_uri,response_type=code, an explicitscope, and acode_challenge(+code_challenge_method=S256). - The user logs in (a session is required) and approves on the consent page (
POST /auth/oauth/authorize). The approval is persisted as a grant, so later requests covered by an existing grant are issued silently — convenient for renewals.prompt=noneprobes without UI,prompt=consentforces the page. - The app exchanges the single-use
codeatPOST /auth/oauth/tokenwith itscode_verifier, receiving a 30-day Bearer access token.
Users review the applications they approved with GET /auth/oauth/grants and revoke one with
DELETE /auth/oauth/grants/{clientId} — which also revokes every token that client holds for them. Server
metadata is discoverable at /.well-known/oauth-authorization-server.
Sessions & CSRF
Users list their active sessions (GET /auth/sessions) and revoke any by id; a password change evicts all of
an account’s sessions. Unsafe methods on cookie-authenticated requests are protected against CSRF using
Sec-Fetch-Site/Origin checks; Bearer-token and anonymous requests are exempt (a header does not travel
cross-site on its own).
The individual /auth routes are documented in the auth section of the API reference.
For the design rationale behind this model (scope vocabulary, guard middleware, status-code contract), see the
Identity & Access Control design document.
Scenes Organisation
Files orgnisation :
├── foo/
│ ├── scene.svx.json
│ ├── scene-image-thumb.jpg
│ ├── models/
│ │ └── foo.glb
│ └── articles/
│ └── foo-FR.html
└── bar/
├── scene.svx.json
├── scene-image-thumb.jpg
├── models/
│ └── bar.glb
└── articles/
└── bar-FR.html
To retrieve a model:
curl -XGET -H "Authorization: Bearer ec_xxxxxxxx" https://ecorpus.holusion.com/scenes/foo/models/foo.glb
The verbs GET PUT MOVE DELETE MKCOL and PROPFIND are supported, with behavior generally conforming to the specification. However, please note: This is a partial implementation of the specification (COPY and LOCK/UNLOCK are not implemented).
Exporting data
Retreving a model :
curl -XGET -H "Authorization: Bearer ${TOKEN}" https://${HOSTNAME}/scenes/foo/models/foo.glb
Exporting one or more scene :
curl -XGET https://${HOSTNAME}/scenes?name=${NAME}&format=zip
You can add as many name="..." parameters as you need, separated by & characters.
Importing data
Import zip scene or collection of scenes from a eCorpus instance.
curl -XPOST https://${HOSTNAME}/scenes --data-binary "@${ZIP_FILE}" -H "Authorization: Bearer ${TOKEN}" | jq .
The token must carry both corpus:write and scenes:write (or be all-scoped). Each scene in the archive is
then checked individually: overwriting an existing scene needs write access on it, and creating a new one needs
at least the create level.
The request returns a (potentially very large) JSON object describing the result. You can filter only failure by running jq .fail or if you don’t have jq installed you can skip it and use the curl -s --fail -o /dev/null -w "%{http_code}".
REST API
The REST API is documented via an OpenAPI v3.2.0 schema, which can be downloaded here: openapi.yml.