API Reference
Native reference for the Tricryption REST Gateway (v1.7.1) — Agentless REST access to a Tricryption key server, for third-party integrations.
For applications that link the native library instead of calling HTTP, see the SDK Reference.
These pages are generated from the committed OpenAPI 3.1 spec — 110 operations over 84 paths:
- Download
openapi.yaml— feed it to your own tooling. - New here? Start with the Integrating via the API guide, which walks a complete worked arc.
Operations by family
| Family | Operations | What it covers |
|---|---|---|
| Sessions | 15 | Obtaining, inspecting and ending a credential. |
| Data cryptography | 5 | Encrypting, decrypting and MACing data through a key server session. |
| Key lifecycle | 6 | Creating, reading, exporting and expiring keys. |
| Key ACLs | 6 | Who may reach a key, and with which rights. |
| Principals | 9 | Identities -- listing, creating, amending and removing. |
| Groups | 3 | Group membership. A group IS a principal, so it is created and listed under Principals. |
| ACL templates | 13 | ACL templates and the templated directories bound to them (ACL_MANAGEMENT template operations). |
| Roles | 5 | Roles and the operations they carry. |
| Certificates | 9 | Looking up certificate objects, and parsing submitted ones. |
| Certificate trust | 4 | Which CAs the key server trusts, and enrolling leaf certificates. |
| Trust administration | 11 | Peer key servers this key server trusts, its own trust identity, and the principal trusts that map principals to peers. |
| Code signing | 3 | The approved publishers and releases that client build admission is decided against. |
| Revocation | 5 | The key server's revocation store -- importing CRLs, reading the evidence, looking up a certificate, and the per-issuer revocation policy. |
| Server administration | 6 | Key server version, configuration and parameters. |
| Policy | 6 | The four XAuth/X.509 auto-provisioning policy parameters. |
| Audit | 2 | Reading the audit log. The one family that reaches a database rather than the key server. |
| Health | 2 | Liveness (GET /api/v1/health) and readiness (GET /api/v1/ready). |
The REST Gateway is the Tricryption product's published HTTP contract. It fronts a Tricryption key server so that an integration can use key management, data cryptography, principal and role administration, certificate trust and audit reading without embedding a native agent.
Scope of this document
This describes the surface as it is built today: 110 operations over 84 paths. The
Gateway's target is every operation the key server's TLS listener admits: each
is routed here or excluded with a published reason, and the x-tricryption-coverage
extension at the end of this document lists the exclusions and counts the routed ones.
An operation you do not find here is not necessarily one that will never exist. Do not read
the absence of a capability as a statement that the product lacks it.
Versioning and compatibility
Every path is served under the contract's major version, /api/v1/.... Nothing is
served at a bare /api/...: such a request is 404. info.version above is the
contract's own semantic version (1.0.0 was the first release; 1.1.0 added the rate and
capacity codes, Server-Timing and x-idempotent; 1.2.0 adds one exclusion to
x-tricryption-coverage, the key server's new ROTATE_SYSTEM_KEYS, and no route; 1.3.0
adds the five OpenID Connect sign-in operations and the one cookie this surface sets; 1.3.1
changes wording only; 1.3.2 changes wording only, naming the largest plaintext one encrypt
request carries; 1.4.0 adds subjectToken to POST /api/v1/session/scoped, so a registered
service can present its end user's access token instead of naming the user, and the refusals
that come with it; 1.5.0 adds POST /api/v1/keys/remove, one result per key on expire,
remove and import, 410 KEY_EXPIRED on every operation that uses a key, and deprecates the
remove field of POST /api/v1/keys/expire; 1.6.0 adds the RAW and WRAPPED formats to
POST /api/v1/keys/import -- a customer's own keys imported under hidden links the key server
mints, answered 201 -- and certificatePem, the import key, to
GET /api/v1/trust/local-certificate; 1.7.0 adds 422 DECRYPT_FAILED to
POST /api/v1/crypto/decrypt -- a ciphertext that does not decrypt under the key it names, which
was answered 403 ACCESS_DENIED -- the audit reason decryptFailed, and the 403 codes
TRANSPORT_PRINCIPAL_REQUIRED and PRINCIPAL_MISMATCH; 1.7.1 changes wording only, naming
who may read GET /api/v1/trust/local-certificate); x-tricryption-product-version names the product release that serves it. The policy is the rest of this section.
Within a major, a release only ever ADDS (a minor version): a new operation, a new
optional response field, a new optional request field. An added error status or code on
an existing operation is also a minor change -- so a client must handle a code it does
not recognise by its status class (4xx: the request; 5xx: the service) rather than
failing on it. An added enum value on a field the Gateway returns is compatible only on
a schema marked x-extensible-enum: true; every other returned enum is closed. A field a
client sends may accept a new value in a minor release: a client that never sends it is
unaffected.
Anything else is a new major at a new path prefix (/api/v2/...): removing a path,
an operation or a field; changing a type or a meaning; a new required request field; a
changed default; a changed authentication or sender-constraint requirement; removing or
renaming an error code.
Behaviour is part of the contract, not only the schemas: error codes and their
statuses, pagination, credential scope and lifetime, DPoP proofs (with the local bth
claim), and retry semantics (retryable, Retry-After). The wording of detail is not.
A replaced major is retired no sooner than 12 months after its successor is generally
available, except for an urgent security fix. Throughout that period every response of
a deprecated operation carries Deprecation (RFC 9745), Sunset (RFC 8594) once a date
is set, and Link: <...>; rel="deprecation" naming the migration note, and the operation
is marked deprecated: true here.
Response bodies carry a human-readable detail string alongside their structured fields. It
is written for the person debugging the call, and its wording is not part of the contract --
match on code, never on detail.
Rate and capacity limits
The Gateway limits traffic in two stages. Every refusal below is answered by the
Gateway itself, before anything is sent to the key server, and every one carries
Retry-After (whole seconds).
Before authentication -- a request the Gateway cannot take at all:
413 PAYLOAD_TOO_LARGE-- the request body is larger than this deployment accepts. It is refused before it is parsed; the connection is closed. Not retryable as sent.503 CAPACITY_UNAVAILABLE(scope: process) -- the Gateway is serving its maximum number of requests at once. A small reserve above that ceiling is kept forDELETE /api/v1/session,POST /api/v1/session/scoped/revoke,GET /api/v1/healthandGET /api/v1/ready, so a saturated Gateway can still end sessions and answer its probes.429 RATE_LIMITED-- too many logins (POST /api/v1/sessionand both certificate legs) from one network source. The source is the connecting address, or -- only behind a proxy the operator has listed -- the address that proxy reports.
After authentication and DPoP verification -- the request is charged to the verified identity, never to the session that carried it:
- a registered service is charged for its own certificate sessions and for every delegated session and scoped credential it minted; a password session is charged to its principal. Logging in again, or minting another credential, never buys another allowance.
- each identity has an allowance per cost class --
session(logins and minting),ordinary,enumeration(list and search operations) andaudit-- a ceiling on its requests in progress, and a service a ceiling on its live sessions. A scoped credential also has an allowance of its own beneath its service's. - any of them spent:
429 RATE_LIMITEDwithRetry-After. A429carries noscope: it is your own allowance, not a deployment ceiling. A request whose DPoP proof fails is refused for the proof and spends nothing.
The numbers are deployment configuration, not contract: they are not published here, they
differ between deployments, and several Gateway instances in front of one key server each
enforce their own. Honour Retry-After; do not tune a client to a number.
A response whose handling called the key server carries Server-Timing: ks;dur=<ms> -- the
time spent in key-server conversations for that request, in milliseconds.
Retries and idempotency
- Every attempt carries a fresh DPoP proof. A proof is accepted once (
jti); re-sending the proof of an earlier attempt is refusedPROOF_REPLAYED, never served. - A timeout does not establish that a mutation failed. The key server may have done it.
- A mutating operation is NOT idempotent unless it says
x-idempotent: true. EveryPOST,PUTandDELETEhere carries the extension. Where it istrue, repeating the request after an unknown outcome leaves the same state -- the repeat may answer differently (a404for a removal already made) but changes nothing further. Where it isfalse, a repeat can act twice (mint a second key, issue a second certificate, add a second entry): read the state back before retrying. - Retry on
429and503afterRetry-After; on any other5xxonly anx-idempotentoperation, and only after readingretryable.
Authenticating
Every operation except six requires a session, presented as
Authorization: Bearer <session id>.
No cookie carries a session or any API authority on this surface. It sets one cookie, the
OpenID Connect login transaction cookie, on GET /api/v1/auth/oidc/launch, and reads and
clears it on the provider's redirect to GET /api/v1/auth/oidc/callback/{providerId}; no other
operation sets or reads a cookie. If you are holding any other cookie from this host, it
belongs to something else. Do not
set credentials: "include" on a browser fetch: the Gateway deliberately never sends
Access-Control-Allow-Credentials, so that option turns a working request into a failing
one.
There are three ways to obtain a session, and they issue credentials with different properties:
| route | credential | who holds it |
|---|---|---|
POST /api/v1/session | password (SRP) login. As wide as everything the principal can reach. | an operator, or a backend you trust with the password |
POST /api/v1/session/certificate/challenge then POST /api/v1/session/certificate | X.509 certificate login (AM08), in two legs. | a registered service |
POST /api/v1/session/scoped | narrowed to an explicit object list, minutes-long, bound to a browser-held key. | an end user's page |
POST /api/v1/session/delegated also mints a session, acting as an externally-authenticated
(XAuth) principal -- but it is not a login and it is guarded: only an already
authenticated, registered service may call it.
Log out with DELETE /api/v1/session. It is idempotent and answers 204 for a credential that
is already gone, for a dead one, and for no credential at all.
Proving you hold your key (DPoP)
Requests on this surface are sender-constrained using DPoP, RFC 9449. A stolen bearer credential on its own is not enough to make a request.
One verifier serves two kinds of caller, which is why the standard construction was chosen
over a bespoke signed envelope. A service's enrolled certificate key is RSA, so it signs
RS256; a browser's non-extractable WebCrypto keypair is P-256, so it signs ES256.
DPoP's alg header is what lets both use one mechanism.
The constraint belongs to the SESSION, not to the route
This is the most important sentence in this section. If your session is bound to a key, every call it makes must carry a proof -- not only the call that minted it. No route on this surface is exempt for a bound session.
Which sessions are bound:
- a certificate login whose principal has a
senderKeyregistered with this Gateway -- bound to that registered key; - a scoped browser credential -- bound at mint time to the RFC 7638 thumbprint (
jkt) of the key you sent assenderKey, and it cannot be minted unbound; - a delegated session -- bound to the sender key of the service that minted it, because its holder is that service holding a second credential.
One class is not bound: a password login, because nobody proved possession of
anything at POST /api/v1/session. A proof sent on an unbound session is ignored rather than
refused, so always sending one is safe, and that is what the security block on each
operation describes.
Minting a proof
A proof is a compact JWS -- base64url(header).base64url(payload).base64url(signature) --
sent in the DPoP request header. Mint a fresh one per request.
Header:
{ "typ": "dpop+jwt", "alg": "ES256", "jwk": { "...": "your PUBLIC key, as a JWK" } }
Payload:
| claim | value |
|---|---|
htm | the HTTP method, uppercase |
htu | scheme://host/path of the request. Query and fragment are excluded, per RFC 9449 section 4.2. |
ath | base64url(SHA-256(<the bearer credential this request carries>)) |
bth | base64url(SHA-256(<the exact request body bytes>)). Hash the bytes you send, not a re-serialisation of them. Send the hash of the empty string when there is no body. |
iat | seconds since the epoch. Accepted within +/- 30 seconds of the Gateway's clock. |
jti | unique per proof. A repeat inside the acceptance window is refused. |
bth is a local extension. RFC 9449 binds the method, the URL and the token; it defines
no body claim. bth exists because binding method and URL alone leaves the body free --
an attacker holding a stolen credential and one captured proof could keep both and swap the
payload, which on this surface means swapping which principal a grant names. If you look
bth up in the RFC and cannot find it, this paragraph is why. Should a future revision of
RFC 9449 define a body claim, this one is superseded by it.
A worked browser example, about thirty lines, is in docs/GATEWAY_BROWSER_INTEGRATION.md.
When a proof is refused
Each failure has its own code, so that you are never sent to look at the wrong thing. All
are 401 except PROOF_MALFORMED (400) and PROOF_REPLAY_CACHE_FULL (503).
code | meaning |
|---|---|
PROOF_MISSING | Bearer sent, no DPoP header, on a bound session. The proof is in addition to the bearer, not instead of it. |
PROOF_MALFORMED | Not three base64url segments; or a segment is not a JSON object; or typ is not dpop+jwt; or iat/jti are the wrong type. |
PROOF_ALG_NOT_ALLOWED | alg absent, none, or outside RS256/ES256. Refused before any key material is touched. |
PROOF_KEY_MISMATCH | The jwk in the proof is not the key this session is bound to. Commonly: the page reloaded and regenerated a keypair while the old credential was still in hand. |
PROOF_SIGNATURE_REJECTED | Right key, bad signature. The signing input is the ASCII string <b64u header>.<b64u payload>, the segments exactly as sent. This is not a clock problem; clock problems have their own code. |
PROOF_BINDING_MISMATCH | htm, htu, ath or bth did not match the request. detail names which one. |
PROOF_CLOCK_SKEW | iat is more than 30 s from the Gateway's clock. Your signing is fine; synchronise the calling host's clock. |
PROOF_REPLAYED | That jti has already been used. One proof, one request. |
PROOF_REPLAY_CACHE_FULL | A deployment fact, not a caller mistake: the Gateway refuses rather than evicting, because eviction would make replay possible again. Retryable. |
SENDER_KEY_RETIRED | Your service rotated its sender key and the key this session was bound to at login is no longer honoured: its overlap (previousSenderKey.notAfter) has passed, or it was replaced with no overlap. The session is closed with this answer; log in again, which binds the current key. Not retryable. |
SERVICE_REGISTRATION_ENDED | The service registration this credential was issued under was removed or narrowed on this Gateway. The credential has ended; nothing was sent to the key server. |
The registered service's credential lifecycle
A registered service keeps its registration across a certificate renewal: the
registration is keyed on the key server's login id, and both renewal paths keep it --
POST /api/v1/certificates/renew for a certificate the key server issued, and
POST /api/v1/transport-principals/reset followed by POST /api/v1/certificates/enroll of a
replacement with the same subject for an imported one. The renewed-away certificate is then
refused at login with 401 CERTIFICATE_RETIRED.
Sender-key rotation. The operator rotates a sender key by editing the registry file: the
new key as senderKey, the old one as previousSenderKey: {key, notAfter} -- at most one
previous key, and notAfter no more than TE_GATEWAY_SENDER_KEY_OVERLAP_MAX_HOURS (default
24) after the file is loaded. The Gateway re-reads the file while it runs (no restart); a file
that fails validation is refused whole, the registry in force stays, and the refusal is
shown on GET /api/v1/ready under lifecycle.serviceRegistry. New logins bind the new key; a
session bound to the old one -- and a delegated session it minted -- keeps working until
notAfter and is then refused 401 SENDER_KEY_RETIRED.
Revocation. Everything a service holds or minted at this Gateway -- its sessions, the
delegated sessions and the scoped credentials it minted -- ends, its key-server sessions
closed, when its registration is removed or narrowed, when a call on its own session comes back
CERTIFICATE_REVOKED, and otherwise within TE_GATEWAY_REVOCATION_SWEEP_SECONDS (default
30) of the key server holding a CRL that names its certificate: the Gateway asks the key
server for each live service certificate's revocation status on that interval, through its
own identity. That interval is the revocation bound. A credential that has ended answers 401
(SESSION_NOT_FOUND, or one of the two codes above).
The one that will actually bite you is htu behind a proxy. If anything terminates TLS
in front of the Gateway, your page signs https://gw.example/api/v1/... while the Gateway
process only ever sees http://..., and every honest request mismatches. The refusal names
the claim but cannot name the cause, because from inside the process nothing looks wrong. The
fix is on the operator's side: they set TE_GATEWAY_PUBLIC_ORIGIN to the origin your page
actually addresses.
Calling from a browser (CORS)
Two deployment shapes are supported, and the Gateway prefers neither.
Shape 1 -- direct cross-origin. Your page is served from your origin and calls the Gateway at the Gateway's own origin. The operator puts your origin on an allowlist:
TE_GATEWAY_CORS_ORIGINS=https://app.example.com,https://staging.app.example.com
Comma-separated; whitespace around entries is ignored; duplicates collapse. Each entry is a
bare origin -- scheme://host with an optional :port -- with no trailing slash, no path and
no query. Matching is exact on scheme, host and port: there is no pattern matching and no
subdomain wildcard, so list every origin you use, including staging and preview hostnames.
Omit the port when it is the default for the scheme, because a browser sends
https://app.example.com and never https://app.example.com:443. Every one of these rules
is enforced at Gateway boot rather than discovered later: a malformed entry stops the
process with a diagnostic naming it.
* is not supported -- not as a default, not as a value, not behind a flag. This surface
hands out per-object keys belonging to named end users; "any origin may read the responses"
is not a configuration of that control but its removal. It is refused at boot.
Once your origin is allowed, the Gateway permits methods GET, POST, PUT, DELETE and request
headers Authorization, Content-Type, DPoP, and lets a browser cache the preflight for 10
minutes. The allowed-methods set is surface-wide and answered identically for every path;
whether a particular path takes a particular verb is still decided by the route table, and a
wrong verb comes back 405 METHOD_NOT_ALLOWED from the Gateway proper.
Omitting DPoP from a proxy's forwarded headers, or reading an older copy of the allowed
list that lacks it, makes the browser block your request before it is sent -- the same
failure mode as omitting Authorization, and harder to diagnose, because the credential is
perfectly valid and the Gateway never sees the call.
Shape 2 -- you reverse-proxy the Gateway under your own origin. Your page then calls a
relative URL, no origin is crossed, and CORS is not involved at all. This is the default:
with no allowlist configured the Gateway sends no CORS header of any kind, which is the
correct posture for this shape rather than an unfinished one. Make sure your proxy forwards
Authorization and DPoP.
A CORS refusal is close to invisible from the page -- fetch rejects with a bare TypeError
while the request often reached the Gateway and succeeded there. The Gateway logs every
refused origin server-side, by name, so ask the operator rather than guessing.
Error model
Every failure answers JSON carrying at least code and detail. code is the contract.
Some failures additionally carry retryable, reason, scope and reauth. A 503 whose
scope is session or process carries a Retry-After header.
Two conventions on this surface are worth knowing before reading the operations:
- A write is verified by re-reading, not by the call returning. Most key-server write
operations resolve without a result, so a resolved call proves only that the key server did
not refuse. Routes that write therefore re-read and compare, and answer
409with a code such asNOT_SAVEDorNOT_VERIFIEDwhen the read-back disagrees. A409on this surface generally means "nothing errored, and the state is not what you asked for" -- a different fact from a500. - An empty result and a failed read are different answers. Routes that could return an empty list distinguish "there is nothing" from "nothing was read", and never report the second as the first.