Reference
API reference
One origin, https://sigil.enigmahq.sh. JSON in and out. The SDKs wrap the client API, so you only call it directly from a host the SDKs don't cover.
Client API
For key licences. Called by your product from inside a customer's install. No account is needed: the license key is the credential. CORS is *, and requests are rate-limited per IP (30 a minute).
POST /v1/client/activate
{ "app": "app_…", "key": "SGL-…", "fingerprint": "<43-char sigil-fp:v1>", "platform": "server",
"label": "engine-host-01", "sdk_version": "rust-0.2.0", "app_version": "1.4.0" }{ "lease": "eyJhbGciOiJFZERTQSIs…",
"activation": { "id": "act_…", "created": true },
"license": { "id": "lic_…", "name": "conFIEL Engine (prod)", "entitlements": ["cosign"],
"expires_at": null, "max_machines": 1, "machines_used": 1 },
"lease_expires_at": 1791234567, "refresh_after": 1790629767 }Idempotent per (license, fingerprint): a machine that already holds a seat gets it renewed, not duplicated. Renew at refresh_after.
POST /v1/client/deactivate
{ "app", "key", "fingerprint" } → { "ok": true }, or 404 ACTIVATION_NOT_FOUND.
GET /v1/client/apps/:app/keys
{ "app": "app_…", "keys": [ { "kid": "key_…", "kty": "OKP", "crv": "Ed25519", "x": "<base64url, 32 bytes>" } ] }The public keyset to compile in: the active key, the standby key and any retired keys.
Errors
| Status | Code | Meaning | Host should |
|---|---|---|---|
| 400 | INVALID_BODY, UNKNOWN_PLATFORM | malformed request | fix the request |
| 404 | LICENSE_NOT_FOUND | no such key for this application | drop the cached lease |
| 403 | LICENSE_REVOKED, LICENSE_SUSPENDED, LICENSE_EXPIRED | license not usable | drop the cached lease |
| 403 | PLATFORM_NOT_ALLOWED | the policy doesn't include this platform | drop the cached lease |
| 409 | SEAT_LIMIT_REACHED | all of the license's machines are taken | tell the user to release one |
| 409 | PLATFORM_MISMATCH | this machine activated on another platform | use the original platform |
| 429 | RATE_LIMITED | too many requests from this IP | retry later; keep the lease |
Every refusal is recorded as an activation denied event in the portal, with its reason.
Service API
For instance licences: your own backend mints a lease each time your module needs one. Server to server, with a service token from the application's page: Authorization: Bearer sgs_…. Tokens are scoped to one application and to leases:issue and/or licenses:revoke.
POST /v1/service/leases
{ "instance": "ins_…", "raw_id": "rsa-n:<base64url SHA-256 of the RSA modulus>", "ttl": 3600 }{ "lease": "eyJhbGciOiJFZERTQSIs…", "lease_expires_at": 1791234567,
"instance": { "id": "ins_…", "name": "extension", "platform": "chrome-extension" },
"entitlements": ["deal", "sign"] }The lease is bound to raw_id, which your module recomputes from what it is about to use. ttl can shorten the instance's longest lease, never lengthen it. Sigil keeps a daily count per instance, not a row per lease. Errors: 401 UNAUTHENTICATED, 403 SCOPE_MISSING, 404 INSTANCE_NOT_FOUND, 403 LICENSE_REVOKED · LICENSE_SUSPENDED · LICENSE_EXPIRED.
POST /v1/service/licenses/:id/revoke
Your backend's off switch: every later lease request for the licence is refused. Leases already minted run until they lapse.
Sign-in
The portal is a relying party of conFIEL Auth (an authorization-code flow):
| Route | |
|---|---|
GET /v1/session/login | redirects to conFIEL Auth's sign-in page, with a state held in a short cookie |
GET /v1/session/callback?code&state | exchanges the code for an ID token and sets sigil_session (HttpOnly, SameSite=Lax, 8 h) |
GET /v1/session | { signedIn: true, user, account }, or { signedIn: false } |
POST /v1/session/logout | clears the session |
Scripts and CI send the same ID token as Authorization: Bearer …. A cookie-authenticated request that changes something must come from Sigil's own origin, so cross-site requests get 403 CROSS_ORIGIN.
Admin API
Everything the portal does. Each route is scoped to the signed-in account, and a row belonging to another account is a 404.
| Route | Body / query | Does |
|---|---|---|
GET /v1/account | account and seat usage | |
PATCH /v1/account | { seats?, name? } | resize the pool (not below allocated) |
GET /v1/apps | applications with license and install counts | |
POST /v1/apps | { name, description? } | register; generates the signing key |
GET /v1/apps/:id | application, policies, keys, service tokens (by hint), stats | |
POST /v1/apps/:id/tokens | { name, scopes[] } | create a service token; the response holds token, once |
POST /v1/apps/:id/tokens/:tid/revoke | revoke a service token | |
POST /v1/apps/:id/keys/rotate | promote the standby key, retire the active key, create a new standby; 409 NO_STANDBY without one | |
POST /v1/apps/:id/keys/standby | create the standby key, for an application that has none; 409 STANDBY_EXISTS | |
POST /v1/apps/:id/policies | { name, entitlements[], platforms[], max_machines, lease_ttl_days, grace_days, license_days? } | create a policy |
GET /v1/licenses | ?app= | licenses |
POST /v1/licenses | { app, policy, name, email?, max_machines?, metadata? } | issue a key licence; the response holds key, once. 409 SEAT_POOL_EXHAUSTED if the pool is full |
POST /v1/licenses | { kind: "instances", app, name, email?, days?, instances?: [{ name, platform, entitlements[], lease_ttl?, grace? }] } | issue an instance licence: no key, no seats. Seconds; lease 1 h and grace 0 by default |
GET /v1/licenses/:id | license, policy or instances (with 7-day lease counts), activations, history | |
POST /v1/licenses/:id/instances | { name, platform, entitlements[], lease_ttl?, grace? } | add an instance |
POST /v1/licenses/:id/revoke · suspend · reinstate | change status | |
POST /v1/licenses/:id/offline | { fingerprint, platform, days, label? } | air-gapped lease (key licences) |
GET /v1/activations | installs | |
DELETE /v1/activations/:id | release a seat | |
GET /v1/events | ?limit= | audit trail |
GET /v1/stats/daily | ?days= | activity per day |