Skip to content

View certificate history across all users (auditor-only)

GET
/api/admin/certificates/history
curl --request GET \
--url 'https://example.com/api/admin/certificates/history?limit=25&q=alice&type=user&status=live' \
--header 'X-Ssoossh-Key-Secret: <X-Ssoossh-Key-Secret>'

Returns issued certificates across all users, useful for audits, incident review, and tracking “who issued this?”. Supports searching over key ID, principals, serial number, fingerprint, and owner username/email, filtering by type and expiration status. Newest first, with cursor-based pagination (not offset): pass the previous page’s next_cursor as after.

after
string

Certificate ID to start after (cursor): the previous page’s next_cursor

limit
integer

Number of results (default 25, max 100)

Example

25
q
string

Search term (max 200 chars)

Example

alice
type
string

Filter by certificate type (user/service/pam/console)

Example

user
status
string

Filter by expiration (live/expired)

Example

live

Certificates, newest first, with the total and the cursor for the next page

Media typeapplication/json
object
data
required
object
certificates
required
Array<object>
object
critical_options

CriticalOptions are the options fixed into the certificate (force-command, source-address). Kept separate from Extensions because sshd treats them differently: it rejects a certificate carrying a critical option it does not understand, where an unknown extension is ignored. Populated by the detail endpoint alone, same as Extensions.

object
key
additional properties
string
decided_accept_language
string
decided_at
string
decided_by_email
string
decided_by_groups
Array<string>
decided_by_other_accounts
Array<string>
decided_by_outcome
string
decided_by_service_accounts
Array<string>
decided_by_subject
string
decided_by_username
string
decided_forwarded_for
string
decided_granted_options

Options are the certificate options fixed at approval, already narrowed by server config: what a redemption actually grants, not what the client asked for.

object
extensions
required
Array<string>
force_command
string
no_touch_required
required
boolean
source_addresses
Array<string>
decided_policy_explanation
string
decided_principals

The decision’s content and reasoning; see RequestDetailResponse. Populated on the detail endpoint only.

Array<string>
decided_source_ip
string
decided_user_agent
string
enrollment_id

EnrollmentID is the service code this certificate was redeemed from, so the UI can link to it. Present only on a service certificate.

string
expires_at
required
string
extensions

Extensions are the SSH certificate extensions the certificate was signed with (permit-pty, permit-agent-forwarding, …), decoded from the audit row’s JSON column rather than passed through as a string: the browser should not have to parse JSON out of JSON.

Populated by the detail endpoint alone. A list row does not display them, and carrying them on every row of a hundred-row page would be payload nobody reads.

Array<string>
id
required
string
issued_at
required
string
key_id
required
string
principals
required
string
public_key_fingerprint
required
string
reported_client
string
reported_hostname
string
reported_machine_id
string
reported_pam_service
string
reported_process
string
reported_remote_host
string
reported_requesting_user
string
reported_tty
string
reported_username

What asked for this certificate, as the requester reported it, taken from the decision record’s snapshot rather than from the request – see model.CertificateRequestDecision. Without these the history could say who approved a certificate and what it granted but not which machine or command it was for, which is the question an incident review actually starts from.

Every one is self-reported by an unauthenticated caller and must be rendered as a claim, exactly as the approval page renders it (see RequestDetailResponse). The one field here the server established itself is DecidedSourceIP above, and that is the approver’s, not the requester’s.

ReportedUsername and ReportedHostname are the “user@host” that asked: the PAM account and machine for a pam or console certificate, the local client’s for a user one. The rest are PAM and console only, and empty on a user certificate, which has no service or terminal to report.

The pair is on a list row too, because it is what a history row leads with: the row’s subject is where the certificate was fetched from, and the approver’s identity is the same value on every row of a person’s own history. So is ReportedService, which a PAM row names (sudo, su, sshd). The remaining six stay detail-only – six more strings on every row is payload nobody reads.

string
retrieved_at

RetrievedAt is when that redemption happened. Distinct from IssuedAt only by the width of the signing round trip, and carried because the retrieval log is timestamped by it — matching a certificate to a line in that log means comparing like with like.

string
retrieved_source_ip

RetrievedSourceIP is the address the service retrieve call came from — the machine the unattended job actually ran on. Present only on a service certificate.

It answers a different question from DecidedSourceIP below, which for a service certificate is the approver’s browser at enrollment time and is therefore identical across every certificate the code mints. This one is this certificate’s own origin.

string
serial_number
required

SerialNumber is a decimal string, not a JSON number. Serials are 63 bits of randomness (internal/serial), so all but a vanishing fraction exceed JavaScript’s Number.MAX_SAFE_INTEGER (2^53-1) and a browser parsing one as a number silently rounds it – 3260700569889958163 reads back as 3260700569889958400. On an audit record that is a wrong answer, and an unsearchable one: the serial an operator reads off ssh-keygen -L never matches what is on screen.

Both tags are needed. json:",string" decides what Go writes; tstype decides what tygo tells the browser to expect, which it otherwise infers from the Go type and gets wrong.

string
type
required

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.

string
Allowed values: user service pam console
next_cursor

NextCursor is the after for the following page: the id of this page’s last certificate, absent on the last page.

string
total
required

Total is how many certificates match the filters, across all pages.

integer
error
string

Example

{
"data": {
"certificates": [
{
"serial_number": "0",
"type": "user"
}
]
}
}

Invalid parameters, an after that names no certificate, or offset

Media typeapplication/json
object
data

Data is always null on an error response.

error
required

Error is a human-readable message. Branch on the HTTP status rather than this string; it is for humans and logs.

string
error_code
required

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.

string

Example

{
"error": "certificate request \"9f1c…\" not found",
"error_code": "not_found"
}

Not authenticated

Media typeapplication/json
object
data

Data is always null on an error response.

error
required

Error is a human-readable message. Branch on the HTTP status rather than this string; it is for humans and logs.

string
error_code
required

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.

string

Example

{
"error": "certificate request \"9f1c…\" not found",
"error_code": "not_found"
}

Not authorized as auditor

Media typeapplication/json
object
data

Data is always null on an error response.

error
required

Error is a human-readable message. Branch on the HTTP status rather than this string; it is for humans and logs.

string
error_code
required

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.

string

Example

{
"error": "certificate request \"9f1c…\" not found",
"error_code": "not_found"
}