SSH Sessions
The SSH Sessions resource provides read-only access to the SSH session activity recorded by Keystash's SSH Connection Logging. Each session captures who connected to a Host Server, from where, how they authenticated and how long they stayed — the same activity you can browse in the app under SSH Sessions. Access to this resource requires the SSH Sessions → View Role permission (see Roles & Permissions).
Note
These endpoints are read-only — there is no create, update or delete.
List SSH Sessions
Returns the company's SSH sessions, newest first, with optional filtering and paging. With no parameters supplied you get the most recent sessions, up to the default limit.
Path: /ssh-sessions/list
Method: GET
Authentication: Bearer Token
Request Headers
| Key | Value | Required |
|---|---|---|
Accept-Encoding |
gzip | optional |
Request Parameters
All parameters are optional query parameters. A value that fails its rule, or a parameter the endpoint does not accept, returns a 400 BadArgument naming every parameter at fault.
| Key | Data Type | Required | Notes |
|---|---|---|---|
start_date |
string | optional | Inclusive lower bound on the session start, in UTC YYYY-MM-DD HH:mm:ss. Must be supplied with end_date and must not be later than it. |
end_date |
string | optional | Inclusive upper bound on the session start, in UTC YYYY-MM-DD HH:mm:ss. Must be supplied with start_date. |
user_ids |
array of string | optional | Filter to sessions for these Keystash users, by their id, being the user.id field in a response and the id that a Users call returns. Each is 1–16 alphanumeric characters. |
server_ids |
array of string | optional | Filter by server, by its id, being the server.id field in a response. Each is 1–16 alphanumeric characters. |
server_group_ids |
array of string | optional | Filter by Server Group, by its id, being the server.server_group_id field in a response. Each is 1–16 alphanumeric characters. |
statuses |
array of string | optional | One or more of Incomplete, Failed, Active, Ended. Case-insensitive, with surrounding whitespace trimmed. |
limit |
integer | optional | Rows to return. Defaults to 250, which is also the maximum; a larger value is reduced to 250. |
offset |
integer | optional | Row offset for paging. Defaults to 0. |
Note
The array filters, being user_ids, server_ids, server_group_ids and statuses, accept both forms. Send ?statuses[]=Ended&statuses[]=Active for several values, or the plain ?statuses=Ended for one.
Sessions are recorded continuously, so the list you are paging through is being appended to while you walk it. Pin the window with start_date and end_date when you are working through a history and the set holds still underneath you. See Filtering Lists for the validation rules and Paging Through Lists for the X-Total-Count, X-Limit and X-Offset headers.
Tip
Where you need more than one page of history, filter to a closed date range rather than paging the live list. A session recorded between your first request and your second shifts every row along by one, which can push a session onto a page you have already read. A window of 2026-06-01 00:00:00 to 2026-06-30 23:59:59 returns the same set however long the walk takes.
Request Body
None.
Response Fields
A successful response is a JSON array of session objects. Each object has these fields:
| Field | Type | Notes |
|---|---|---|
id |
string | The session's Keystash id. Pass this to View SSH Session as the id parameter. |
session_id |
string | The agent/PAM session identifier the host reported. Unique per session on a given server. |
status |
string | The session status label: Incomplete, Failed, Active or Ended (or Unknown if unmapped). This is the same label accepted by the statuses filter. See Session Statuses. |
user |
object | The resolved Keystash user. The user fields are null if the user could not be resolved or was later deleted. |
user.id |
string|null | The Keystash user id. null for unresolved or root logins. Use it in the user_ids filter. |
user.name |
string|null | The user's full name (firstname lastname). |
user.email |
string|null | The user's email / Keystash username. |
user.server_username |
string|null | The Linux username the session logged in as. |
server |
object | The target server. |
server.id |
string|null | The server id. Use it in the server_ids filter. |
server.hostname |
string|null | The server hostname. |
server.server_group_id |
string|null | The server's Server Group id. Use it in the server_group_ids filter. |
server.server_group_name |
string|null | The Server Group name. |
ip_address |
string | The source IP address the user connected from. |
auth_method |
string|null | The raw authentication-method token (see Authentication Methods). unknown when not reported. |
auth_method_label |
string | The human label for auth_method. |
mfa_method |
string|null | The raw MFA-method token (see MFA Methods). |
mfa_method_label |
string | The human label for mfa_method. |
started_at |
string | UTC timestamp of the first event — the session start. |
connected_at |
string|null | UTC timestamp of connect; null if the session never connected. |
disconnected_at |
string|null | UTC timestamp of disconnect; null while the session is still Active or never completed. |
duration_seconds |
integer|null | The connect-to-disconnect duration in seconds; null while the session is Active, Failed or Incomplete. |
deny_reason |
string|null | The raw deny-reason token; null unless the session was denied (see Deny Reasons). |
deny_reason_label |
string | The human label for deny_reason (empty string when none). |
Note
A filter that matches nothing is a 200 OK with an empty array ([]), and so is an offset past the end of the results. A request with no filters against a company that has never recorded an SSH session returns 404 NotFound. Read that as "no sessions recorded yet" rather than as an error, and carry on.
Example Request
curl --location --request GET 'https://app.keystash.io/api/v1/ssh-sessions/list?statuses[]=Ended&limit=50' \
--header 'Authorization: Bearer <token>'
Walking a single day of history with a pinned window, which is the pattern to use whenever the results run to more than one page:
curl --location --request GET 'https://app.keystash.io/api/v1/ssh-sessions/list?start_date=2026-06-22%2000:00:00&end_date=2026-06-22%2023:59:59&limit=250&offset=0' \
--header 'Authorization: Bearer <token>'
Example Response
The example below shows one Ended session (with duration_seconds and disconnected_at populated) and one Active session (still connected, so those fields are null).
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Total-Count: 2
X-Limit: 50
X-Offset: 0
[
{
"id": "x0Uf09z3TYjoNVFv",
"session_id": "sshd-8f3c1a2b9d4e",
"status": "Ended",
"user": {
"id": "bkQV8I5BRcAVpg1d",
"name": "Max Smith",
"email": "max.smith@example.org",
"server_username": "maxsmith"
},
"server": {
"id": "DmcEgixUXpWVv7tk",
"hostname": "web-01.hostingprovider.net",
"server_group_id": "BD720EP87kNORRcH",
"server_group_name": "Production Servers"
},
"ip_address": "192.168.1.58",
"auth_method": "pubkey",
"auth_method_label": "Public Key",
"mfa_method": "totp_mfa",
"mfa_method_label": "TOTP",
"started_at": "2026-06-22 09:14:00",
"connected_at": "2026-06-22 09:14:02",
"disconnected_at": "2026-06-22 09:25:11",
"duration_seconds": 669,
"deny_reason": null,
"deny_reason_label": ""
},
{
"id": "p8CduXmyw6n9dzdd",
"session_id": "sshd-1b7e44c0aa21",
"status": "Active",
"user": {
"id": "tr568I5BRcAVpwx7",
"name": "Anne Teak",
"email": "anne.teak@example.org",
"server_username": "anneteak"
},
"server": {
"id": "DmcEgixUXpWVv7tk",
"hostname": "web-01.hostingprovider.net",
"server_group_id": "BD720EP87kNORRcH",
"server_group_name": "Production Servers"
},
"ip_address": "192.168.1.59",
"auth_method": "pubkey",
"auth_method_label": "Public Key",
"mfa_method": "none",
"mfa_method_label": "None",
"started_at": "2026-06-22 11:02:40",
"connected_at": "2026-06-22 11:02:41",
"disconnected_at": null,
"duration_seconds": null,
"deny_reason": null,
"deny_reason_label": ""
}
]
A Failed session looks like the object below — it never connected, so the connect, disconnect and duration fields are null, and the deny_reason/deny_reason_label pair explains why it was refused.
{
"id": "H6LPlSC081GIqzW0",
"session_id": "sshd-c92f0d7e6b15",
"status": "Failed",
"user": {
"id": "bkQV8I5BRcAVpg1d",
"name": "Max Smith",
"email": "max.smith@example.org",
"server_username": "maxsmith"
},
"server": {
"id": "DmcEgixUXpWVv7tk",
"hostname": "web-01.hostingprovider.net",
"server_group_id": "BD720EP87kNORRcH",
"server_group_name": "Production Servers"
},
"ip_address": "203.0.113.42",
"auth_method": "password",
"auth_method_label": "Password",
"mfa_method": "totp_mfa",
"mfa_method_label": "TOTP",
"started_at": "2026-06-22 08:47:13",
"connected_at": null,
"disconnected_at": null,
"duration_seconds": null,
"deny_reason": "bad_code",
"deny_reason_label": "Invalid 2FA code"
}
Example Error Response
Invalid Filter Value (400 Bad Request)
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"error": {
"code": "BadArgument",
"message": "One or more query parameters were invalid.",
"details": [
{
"field": "statuses",
"message": "Unrecognised status label. Supply one or more of the allowed values.",
"received": "Finished",
"allowed_values": ["Incomplete", "Failed", "Active", "Ended"]
}
]
}
}
Missing Permission (403 Forbidden)
HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8
{
"error": {
"code": "Forbidden",
"message": "You do not have the required permission to access this path.",
"details": [
{
"path": "/api/v1/ssh-sessions/list"
}
]
}
}
Database Error (500 Internal Server Error)
HTTP/1.1 500 Internal Server Error
Content-Type: application/json; charset=utf-8
{
"error": {
"code": "DataRetrievalError",
"message": "There was a problem retrieving the data from the database.",
"details": []
}
}
View SSH Session
Returns a single session together with its ordered authentication-event timeline.
Path: /ssh-sessions/view
Method: GET
Authentication: Bearer Token
Request Headers
| Key | Value | Required |
|---|---|---|
Accept-Encoding |
gzip | optional |
Request Parameters
| Key | Data Type | Required | Notes |
|---|---|---|---|
id |
string | required | The session id — the id field from a List SSH Sessions response. A missing or empty value returns 400 BadArgument; an unknown id returns 404 NotFound. |
Request Body
None.
Response Fields
A successful response is a single object with two fields:
| Field | Type | Notes |
|---|---|---|
session |
object | The session, with exactly the same fields as a List SSH Sessions entry. |
events |
array of object | The session's authentication events, oldest first. |
Each object in events has these fields:
| Field | Type | Notes |
|---|---|---|
id |
string | The agent-assigned unique id for this event. |
created |
string | UTC timestamp of when the event occurred, in YYYY-MM-DD HH:mm:ss second precision. Events are returned oldest first. |
event_type |
string | The raw event token: attempt, authenticate, connect, disconnect or deny (see Event Types). |
auth_method |
string|null | The raw authentication-method token for this event (see Authentication Methods). |
auth_method_label |
string | The human label for auth_method. |
mfa_method |
string|null | The raw MFA-method token for this event (see MFA Methods). |
mfa_method_label |
string | The human label for mfa_method. |
reason |
string|null | The raw deny/result reason token; populated on deny events (see Deny Reasons). |
reason_label |
string | The human label for reason. |
Example Request
curl --location --request GET 'https://app.keystash.io/api/v1/ssh-sessions/view?id=x0Uf09z3TYjoNVFv' \
--header 'Authorization: Bearer <token>'
Example Response
A clean Ended session traces from first attempt through to disconnect:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"session": {
"id": "x0Uf09z3TYjoNVFv",
"session_id": "sshd-8f3c1a2b9d4e",
"status": "Ended",
"user": {
"id": "bkQV8I5BRcAVpg1d",
"name": "Max Smith",
"email": "max.smith@example.org",
"server_username": "maxsmith"
},
"server": {
"id": "DmcEgixUXpWVv7tk",
"hostname": "web-01.hostingprovider.net",
"server_group_id": "BD720EP87kNORRcH",
"server_group_name": "Production Servers"
},
"ip_address": "192.168.1.58",
"auth_method": "pubkey",
"auth_method_label": "Public Key",
"mfa_method": "totp_mfa",
"mfa_method_label": "TOTP",
"started_at": "2026-06-22 09:14:00",
"connected_at": "2026-06-22 09:14:02",
"disconnected_at": "2026-06-22 09:25:11",
"duration_seconds": 669,
"deny_reason": null,
"deny_reason_label": ""
},
"events": [
{
"id": "a1B2c3D4e5F6g7H8",
"created": "2026-06-22 09:14:00",
"event_type": "attempt",
"auth_method": "pubkey",
"auth_method_label": "Public Key",
"mfa_method": "none",
"mfa_method_label": "None",
"reason": null,
"reason_label": ""
},
{
"id": "i9J0k1L2m3N4o5P6",
"created": "2026-06-22 09:14:01",
"event_type": "authenticate",
"auth_method": "pubkey",
"auth_method_label": "Public Key",
"mfa_method": "totp_mfa",
"mfa_method_label": "TOTP",
"reason": null,
"reason_label": ""
},
{
"id": "q7R8s9T0u1V2w3X4",
"created": "2026-06-22 09:14:02",
"event_type": "connect",
"auth_method": "pubkey",
"auth_method_label": "Public Key",
"mfa_method": "totp_mfa",
"mfa_method_label": "TOTP",
"reason": null,
"reason_label": ""
},
{
"id": "y5Z6a7B8c9D0e1F2",
"created": "2026-06-22 09:25:11",
"event_type": "disconnect",
"auth_method": "pubkey",
"auth_method_label": "Public Key",
"mfa_method": "totp_mfa",
"mfa_method_label": "TOTP",
"reason": null,
"reason_label": ""
}
]
}
A Failed session ends on a deny event whose reason/reason_label records why it was refused:
{
"session": {
"id": "H6LPlSC081GIqzW0",
"session_id": "sshd-c92f0d7e6b15",
"status": "Failed",
"user": {
"id": "bkQV8I5BRcAVpg1d",
"name": "Max Smith",
"email": "max.smith@example.org",
"server_username": "maxsmith"
},
"server": {
"id": "DmcEgixUXpWVv7tk",
"hostname": "web-01.hostingprovider.net",
"server_group_id": "BD720EP87kNORRcH",
"server_group_name": "Production Servers"
},
"ip_address": "203.0.113.42",
"auth_method": "password",
"auth_method_label": "Password",
"mfa_method": "totp_mfa",
"mfa_method_label": "TOTP",
"started_at": "2026-06-22 08:47:13",
"connected_at": null,
"disconnected_at": null,
"duration_seconds": null,
"deny_reason": "bad_code",
"deny_reason_label": "Invalid 2FA code"
},
"events": [
{
"id": "g3H4i5J6k7L8m9N0",
"created": "2026-06-22 08:47:13",
"event_type": "attempt",
"auth_method": "password",
"auth_method_label": "Password",
"mfa_method": "none",
"mfa_method_label": "None",
"reason": null,
"reason_label": ""
},
{
"id": "o1P2q3R4s5T6u7V8",
"created": "2026-06-22 08:47:15",
"event_type": "deny",
"auth_method": "password",
"auth_method_label": "Password",
"mfa_method": "totp_mfa",
"mfa_method_label": "TOTP",
"reason": "bad_code",
"reason_label": "Invalid 2FA code"
}
]
}
Example Error Response
Missing Session ID (400 Bad Request)
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"error": {
"code": "BadArgument",
"message": "A valid session id is required.",
"details": []
}
}
Session Not Found (404 Not Found)
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
{
"error": {
"code": "NotFound",
"message": "Your request was successfully processed but there was no data to be returned.",
"details": []
}
}
Understanding the Values
Both endpoints use the same set of consistent labels for statuses, methods, events and deny reasons. The tables below explain what each one means. They mirror the labels shown in the app's SSH Sessions view.
Session Statuses
The status response field returns the label string, and the statuses filter accepts those same label strings (case-insensitive).
| Label | Meaning |
|---|---|
| Incomplete | Only attempt or authenticate events were seen — the session never connected. |
| Failed | A deny event occurred. |
| Active | Connected, with no disconnect yet. |
| Ended | A disconnect was received (or the session was swept after 72 hours stale-Active — see SSH Sessions). |
Authentication Methods
These apply to the auth_method / auth_method_label fields.
| Token | Label |
|---|---|
pubkey |
Public Key |
password |
Password |
keyboard-interactive |
Keyboard Interactive |
gssapi |
GSSAPI |
hostbased |
Host Based |
unknown |
Not Reported |
MFA Methods
These apply to the mfa_method / mfa_method_label fields.
| Token | Label |
|---|---|
none |
None |
totp_mfa |
TOTP |
totp_mfa_scratch |
TOTP Scratch Code |
Event Types
These apply to the events[].event_type field. The API emits the raw token only; the meanings are below.
| Token | Meaning |
|---|---|
attempt |
An authentication attempt was made. |
authenticate |
Authentication succeeded. |
connect |
The session connected. |
disconnect |
The session disconnected. |
deny |
The session was denied (see reason). |
Deny Reasons
These apply to the deny_reason / reason tokens and their *_label fields. Any token not listed below is humanised automatically — underscores become spaces and the result is title-cased.
| Token | Label |
|---|---|
not_authorized |
Not authorized |
unenrolled_denied |
Denied - User not enrolled in 2FA |
not_enrolled |
Not enrolled in 2FA |
bad_code |
Invalid 2FA code |
rate_limited |
Rate limited |
replay |
2FA code already used |
attempts_exhausted |
Too many failed attempts |
mfa_method_unknown |
Unknown MFA method |
agent_unavailable_deny |
Agent unavailable — denied |
agent_unavailable_allow |
Agent unavailable — allowed |
Related Documentation
- SSH Sessions — viewing, filtering and exporting sessions in the app.
- SSH Connection Logging — how the Keystash Agent captures sessions and how to enable logging per Server Group.
- Roles & Permissions — assigning the SSH Sessions → View permission that these endpoints require.
- Authentication — obtaining the Bearer token used in the examples above.
- Introduction — the API base URL, rate limits, dates and times and the standard error envelope.