Verifies an Ed25519 signature over the supplied payload_string and returns a structured verdict
(valid, issuer, issued_at, disclosed_fields). The endpoint verifies the exact UTF-8 bytes supplied in
payload_string byte-for-byte, then requires its parsed JSON value to equal payload. JSON
key order and whitespace may vary as long as the parsed values match; invalid JSON or a mismatch returns 400. This is the
backend the cryptographic-receipt sample panel on /kaios
will consume, and the first reusable building block of the privacy-preserving agent
authentication layer.
← Return to the verification quickstart
/api/kaios/verifyStateless signature verification. One envelope per request. Returns a structured verdict for downstream selectors, identity panels, and CI gates — no persistence.
In this example, the issuer builds the structured payload, serializes it with
JSON.stringify, and signs the resulting string's exact UTF-8 bytes with Ed25519.
That serialization is an example choice; the verifier does not require a canonical JSON form.
payload_string bytes, parses that JSON,
and requires its value to equal payload. It does not require canonical JSON formatting, so whitespace and
object key order may vary. Invalid JSON or a mismatch returns 400.
Load an issuer-provided Ed25519 private key that matches the verifier's configured public key. This Node example reads a PEM key file as one possible input; key delivery and storage belong to the issuer.
const crypto = require('node:crypto');
const fs = require('node:fs');
const payload = {
issuer: 'kaios.automa8.ai',
issued_at: '2026-07-24T12:00:00Z',
disclosed_fields: { task: 'undercollateralized-lending', loan_size_usd: 75000 },
};
const payloadString = JSON.stringify(payload);
const privateKey = crypto.createPrivateKey(fs.readFileSync('issuer-private-key.pem'));
const signature = crypto.sign(
null,
Buffer.from(payloadString, 'utf8'),
privateKey
).toString('hex'); // lowercase hexadecimal
const envelope = { signature, payload_string: payloadString, payload };
console.log(JSON.stringify(envelope, null, 2));
All three fields are required. Any missing or invalid field produces a 400 with the offending fields listed in errors.
| Field | Type | Description |
|---|---|---|
| signature | string (hex) | Ed25519 signature over `payload_string`, decoded from hex. Exactly 128 hex chars (64 bytes); Node hex encoding produces lowercase characters. |
| payload_string | string | A non-empty string whose exact UTF-8 bytes are verified as supplied. It must contain valid JSON whose parsed value equals `payload`; invalid JSON or a mismatch returns 400. |
| payload | object | Structured form of the receipt. Required fields inside: non-empty `issuer` string, valid ISO-8601 `issued_at` timestamp, and object-valued `disclosed_fields` (not null or an array). It must equal the parsed JSON in `payload_string`; `ciphertext_bundle` is optional in the response but must also match when present. |
payload object should include issuer (string), issued_at
(ISO-8601 string), and disclosed_fields (object). The optional ciphertext_bundle
field is opaque to the verifier — carry encrypted bytes if you want the issuer to be able to
disclose them privately on demand.
This request body is the signed TEST ONLY fixture from discovery. Its lowercase 128-character Ed25519 signature covers the exact UTF-8 bytes in payload_string.
{
"signature": "a2a840955190e68e1801646322b7abfdd54fb70b30ac9e23a4d1d777331e3225b20abee94c6e6846718cfe4f846ee16b51e450ae6fc7f9189004fcb4de74da01",
"payload_string": "{\"issuer\":\"kaios.automa8.ai\",\"issued_at\":\"2026-07-24T12:00:00Z\",\"disclosed_fields\":{\"task\":\"undercollateralized-lending\",\"loan_size_usd\":75000}}",
"payload": {
"issuer": "kaios.automa8.ai",
"issued_at": "2026-07-24T12:00:00Z",
"disclosed_fields": {
"task": "undercollateralized-lending",
"loan_size_usd": 75000
}
}
}
This command submits the signed TEST ONLY fixture to a local verifier by default. It succeeds when that verifier has KAIOS_ISSUER_PUBLIC_KEY set to the matching TEST ONLY public key shown by discovery.
VERIFY_URL="${VERIFY_URL:-http://localhost:3000/api/kaios/verify}"
curl -X POST "$VERIFY_URL" \
-H "Content-Type: application/json" \
-d @envelope.json
payload_string with the issuer's own private key. To generate the
signature in Node:
crypto.sign(null, Buffer.from(payload_string, 'utf8'), privateKey).toString('hex')
Every terminal status the integrator needs to handle.
| Status | Body | When |
|---|---|---|
| 200 | {"ok":true,"valid":true,"issuer":"string","issued_at":"ISO-8601 string","disclosed_fields":"object"} |
The signature verifies over the supplied payload_string bytes and its parsed JSON equals payload. |
| 400 | {"ok":false,"errors":["string"]} |
Validation error — the envelope or a receipt field is missing or malformed, payload_string contains invalid JSON or does not match payload, a value is invalid, or the issuer public key could not be parsed. |
| 401 | {"ok":false,"error":"Invalid signature"} |
Signature did not match the supplied payload_string against KAIOS_ISSUER_PUBLIC_KEY. |
| 503 | {"ok":false,"error":"Verification disabled: KAIOS_ISSUER_PUBLIC_KEY not configured"} |
Server-side verification disabled: KAIOS_ISSUER_PUBLIC_KEY env var is not set. |
POST /api/kaios/verify
Content-Type: application/json
{
"signature": "abc",
"payload_string": "{\"disclosed_fields\":{\"loan_size_usd\":75000,\"task\":\"undercollateralized-lending\"},\"issued_at\":\"2026-07-24T12:00:00Z\",\"issuer\":\"kaios.automa8.ai\"}",
"payload": {
"disclosed_fields": { "loan_size_usd": 75000, "task": "undercollateralized-lending" },
"issued_at": "2026-07-24T12:00:00Z",
"issuer": "kaios.automa8.ai"
}
}
HTTP 400
{
"ok": false,
"errors": ["signature: must be exactly 128 hexadecimal characters (Ed25519)"]
}
This "abc" request is intentionally malformed to demonstrate the HTTP 400 response. The signed example envelope above is a separate, valid TEST ONLY fixture.
The errors array identifies malformed fields. Check that signature is exactly 128 hexadecimal
characters, payload_string is non-empty valid JSON whose parsed value matches payload, and
payload includes a non-empty issuer,
a valid ISO-8601 issued_at, and object-valued disclosed_fields. If an error names public-key parsing, ask the server operator to check that
KAIOS_ISSUER_PUBLIC_KEY is configured in PEM SPKI format.
Invalid signature?
The signature did not verify against the configured KAIOS_ISSUER_PUBLIC_KEY for the exact UTF-8 bytes
in payload_string. Check that the submitted string is the one that was signed and that its signing
private key matches the configured public key.
Verification disabled mean?
KAIOS_ISSUER_PUBLIC_KEY is unset on the server. Ask the server operator to configure it to enable
verification.
valid: true response confirm?
The signature verifies over the exact UTF-8 bytes in payload_string, and its parsed JSON value equals
the structured payload. The returned issuer, issued_at, and
disclosed_fields therefore come from the same content authenticated by the signature.
valid: true means the signature verifies over the exact UTF-8 bytes in payload_string
and the parsed JSON value equals the structured payload. The verifier does not canonicalize JSON,
so whitespace and object key order do not affect that value comparison. The optional ciphertext_bundle
must also match when present, though it is not included in the success response.
The endpoint is stateless: it does not persist anything and does not touch
kaios_telemetry. The same contract — including the example envelope and the
response shapes — is published as plain JSON at
https://automa-labs-ai.polsia.app/api/kaios/verify, so an integrator
can pull the spec straight off the live endpoint rather than caching this page.
{
"ok": true,
"valid": true,
"issuer": "kaios.automa8.ai",
"issued_at": "2026-07-24T12:00:00Z",
"disclosed_fields": {
"task": "undercollateralized-lending",
"loan_size_usd": 75000
}
}
| Field | Description |
|---|---|
| ok | True when the verification request completes successfully. |
| valid | True when the signature verifies over the exact UTF-8 bytes in payload_string and its parsed JSON equals payload. |
| issuer | Copied from the structured request payload.issuer. |
| issued_at | Copied from the structured request payload.issued_at. |
| disclosed_fields | Copied from the structured request payload.disclosed_fields. |