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

Requestjson
{ "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" }
200json
{ "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

200json
{ "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

StatusCodeMeaningHost should
400INVALID_BODY, UNKNOWN_PLATFORMmalformed requestfix the request
404LICENSE_NOT_FOUNDno such key for this applicationdrop the cached lease
403LICENSE_REVOKED, LICENSE_SUSPENDED, LICENSE_EXPIREDlicense not usabledrop the cached lease
403PLATFORM_NOT_ALLOWEDthe policy doesn't include this platformdrop the cached lease
409SEAT_LIMIT_REACHEDall of the license's machines are takentell the user to release one
409PLATFORM_MISMATCHthis machine activated on another platformuse the original platform
429RATE_LIMITEDtoo many requests from this IPretry 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

Requestjson
{ "instance": "ins_…", "raw_id": "rsa-n:<base64url SHA-256 of the RSA modulus>", "ttl": 3600 }
200json
{ "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/loginredirects to conFIEL Auth's sign-in page, with a state held in a short cookie
GET /v1/session/callback?code&stateexchanges 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/logoutclears 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.

RouteBody / queryDoes
GET /v1/accountaccount and seat usage
PATCH /v1/account{ seats?, name? }resize the pool (not below allocated)
GET /v1/appsapplications with license and install counts
POST /v1/apps{ name, description? }register; generates the signing key
GET /v1/apps/:idapplication, 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/revokerevoke a service token
POST /v1/apps/:id/keys/rotatepromote the standby key, retire the active key, create a new standby; 409 NO_STANDBY without one
POST /v1/apps/:id/keys/standbycreate 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/:idlicense, 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 · reinstatechange status
POST /v1/licenses/:id/offline{ fingerprint, platform, days, label? }air-gapped lease (key licences)
GET /v1/activationsinstalls
DELETE /v1/activations/:idrelease a seat
GET /v1/events?limit=audit trail
GET /v1/stats/daily?days=activity per day