KeycyteHelp CenterKeycyte PAM

Keycyte PAM 3.2 · Administrator Guide

Secrets API Reference

This reference describes the two endpoints of the Secrets API (version 1.0.0), the request signature, and the error responses. All requests go to your Keycyte PAM server over HTTPS, for example https://pam.example.com.

MethodPathPurpose
GET/api/secrets/accountsList the accounts the key may read, without secret values.
POST/api/secrets/fetchRead one secret of one account.

Request Signature

Every request needs two headers, and both must be listed in the signature:

Date: Sat, 30 Aug 2026 17:04:47 GMT
Authorization: Signature keyId="<key-id>",algorithm="hmac-sha256",headers="(request-target) date",signature="<base64-signature>"

The signature is the Base64-encoded HMAC-SHA256 of the signing string, computed with the key secret. The signing string joins the listed headers with a newline (\n):

(request-target): post /api/secrets/fetch
date: Sat, 30 Aug 2026 17:04:47 GMT
  • The method in (request-target) is lowercase; the path is the full path, including any query string.
  • Only hmac-sha256 is accepted; RSA and ECDSA are rejected.
  • Date must be within about 300 seconds of the server time.

GET /api/secrets/accounts

Returns every account in the caller’s scope, sorted by name and then by accountId. Use it to look accounts up by name instead of hard-coding numeric IDs. The list is also the exact boundary of the key: it shows what a leak of the key would expose.

The response never contains password, privatekey or passphrase.

Response 200

FieldTypeDescription
accountsarrayAccounts in scope (see below).
countintegerNumber of entries in accounts.

Each entry of accounts:

FieldTypeDescription
accountIdintegerNumeric ID of the account.
namestringAccount name. Pass it as name to /api/secrets/fetch.
usernamestringLogin name of the account. Not treated as a secret.
domainstringDomain of the account. Omitted when empty.
typestringAccount type, for example linux, windows, mysql.
descriptionstringAdministrator’s note. Omitted when empty.

An empty list is a normal result: the identity holds no password roles, or none of them is inside its schedule window now.

{
  "accounts": [
    { "accountId": 6365, "name": "app-db", "username": "appuser", "type": "mysql" }
  ],
  "count": 1
}

POST /api/secrets/fetch

Returns one secret value of one account. The account is identified in the request body, not in the URL, so that it does not appear in access logs.

Request body

Send exactly one of accountId or name.

FieldTypeDescription
accountIdintegerNumeric ID of the account. Stable for the life of the account, but changes if the account is deleted and created again.
namestringAccount name. Matched case-insensitively, with surrounding spaces ignored, and only within the caller’s scope.
fieldstringSecret to read: password (default), privatekey or passphrase. For example, an API key is stored in password and a TOTP seed in privatekey.
reasonstringOptional text recorded in the audit log, up to 256 bytes. It never changes the result.

Response 200

FieldTypeDescription
accountIdintegerAccount that was read. Check it when you asked by name.
fieldstringField that was returned, in lowercase.
valuestringThe secret, in clear text.

Warning

Treat the response as sensitive. Do not log it, cache it on disk, or include it in error reports.

Error Responses

StatusBodyMeaning
400{"message": "..."}Neither or both of accountId and name were sent, field is not one of the three names, or the requested field is empty on the account.
401{"error": "unauthorized"}The signature is missing, malformed, expired, or does not match an active key. The body does not say which; the server log holds the reason.
403{"error": "forbidden"} or {"message": "this credential cannot read secrets"}The call came from an IP address outside the key’s allow-list, or the key has no owning user.
404{"message": "secret not found"}No secret was returned. The same response covers a missing account, an account outside the caller’s scope, and a name that matches nothing the caller can see.
409 with Retry-After{"message": "account password is being rotated: ..."}The account’s password is being changed now. Wait the number of seconds in Retry-After and retry. If the condition persists for several minutes, the rotation could not save the new password and an administrator must resolve it; raise an alarm instead of retrying forever.
409 without Retry-After{"message": "account name is ambiguous: ..."}name matches more than one account in the caller’s scope. Use accountId, or rename one of the accounts.
500{"message": "..."}Server error.

Note

Errors from the authentication layer (401, and 403 for IP restrictions) use the error field; errors from the endpoints use the message field. Read both in your client.

Example

The following shell script reads the password of the account named app-db. Replace the placeholders with your values.

KEY_ID="<key-id>"
KEY_SECRET="<key-secret>"
HOST="pam.example.com"
PATH_="/api/secrets/fetch"
DATE="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S GMT')"

SIGNING_STRING="$(printf '(request-target): post %s\ndate: %s' "$PATH_" "$DATE")"
SIGNATURE="$(printf '%s' "$SIGNING_STRING" | openssl dgst -sha256 -hmac "$KEY_SECRET" -binary | base64)"

curl -s "https://$HOST$PATH_" \
  -H "Date: $DATE" \
  -H "Authorization: Signature keyId=\"$KEY_ID\",algorithm=\"hmac-sha256\",headers=\"(request-target) date\",signature=\"$SIGNATURE\"" \
  -H "Content-Type: application/json" \
  -d '{"name": "app-db", "field": "password", "reason": "nightly backup job"}'

A successful call returns:

{ "accountId": 6365, "field": "password", "value": "<secret-value>" }

The HMAC key is the key secret exactly as it was shown when the key was issued (used as a text string, not decoded).

Page updated: November 1, 2026 Send feedback
Search