The caller's denied certificate requests
const url = 'https://example.com/api/decisions/denied';const options = {method: 'GET', headers: {'X-Ssoossh-Key-Secret': '<X-Ssoossh-Key-Secret>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://example.com/api/decisions/denied \ --header 'X-Ssoossh-Key-Secret: <X-Ssoossh-Key-Secret>'The half of a decision history that issues nothing. /api/certs reads the certificates table, and a denial never produces a row in it, so denials are served here and the client interleaves the two by time.
Scoped by the caller’s own decision records, with no parameter to widen it. Uses cursor-based pagination (not offset).
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”Decision ID to start after (cursor)
Maximum denials to return (default 25, max 100)
Search over request ID and the reported host context
Filter by certificate type (user/service/pam/console)
Live or expired; a denial is neither, so either returns nothing
Responses
Section titled “Responses”Denials, newest first, with cursor for next page
object
object
object
CertificateRequestID is the request that was refused.
DecidedSourceIP is the address the denial was made from – the decider’s browser, server-observed, not anything the requester claimed.
ID is the decision’s id, not a certificate’s – there is no certificate. It is what the audit event for this denial carries.
The rest of the compact host-context snapshot the decision row carries, and claims in exactly the same way: PAMService, TTY and RemoteHost are what a PAM or console request said it was doing, and Client is what a user request reported instead, since it has no PAM service or terminal.
Present because “I refused a sudo on rack07 from 10.1.2.9” is a memory a month later and “I refused request 4f2a” is not. All of it is copied onto the decision at decision time, so unlike the request’s own columns it cannot go blank later.
ReportedUsername and ReportedHostname are the “user@host” the request claimed for itself, self-reported by an unauthenticated caller and never verified. They are here because they are what makes a denial identifiable a month later: “I refused a console login on rack07” is a memory, “I refused request 4f2a” is not.
Type is what was asked for. Empty when the request row behind the decision is gone: the decisions table is the permanent one by design, so the client must render a row whose type it does not know.
Example
{ "data": { "denials": [ { "type": "user" } ] }}Invalid limit or cursor
object
Data is always null on an error response.
Error is a human-readable message. Branch on the HTTP status rather than this string; it is for humans and logs.
ErrorCode is a machine-readable error classifier. One of the error_code constants from internal/apitypes; use this to decide whether to retry or branch on the failure mode.
Example
{ "error": "certificate request \"9f1c…\" not found", "error_code": "not_found"}No valid session
object
Data is always null on an error response.
Error is a human-readable message. Branch on the HTTP status rather than this string; it is for humans and logs.
ErrorCode is a machine-readable error classifier. One of the error_code constants from internal/apitypes; use this to decide whether to retry or branch on the failure mode.
Example
{ "error": "certificate request \"9f1c…\" not found", "error_code": "not_found"}