Signed-Receipt Verification Contract

Verify a signed KAIOS receipt

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

POST /api/kaios/verify

Stateless signature verification. One envelope per request. Returns a structured verdict for downstream selectors, identity panels, and CI gates — no persistence.

Method
POST
URL
https://automa-labs-ai.polsia.app/api/kaios/verify
Base
https://automa-labs-ai.polsia.app
Content-Type
application/json
Schema
kai-verify-1
Discovery
GET https://automa-labs-ai.polsia.app/api/kaios/verify

Construct and sign the payload string

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.

Endpoint behavior. It verifies the exact supplied 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.

node — construct and sign
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));

Required fields

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.
The 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.

JSON body the issuer sends

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.

envelope.json
{
  "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
    }
  }
}
TEST ONLY: This public key and signature are an isolated example fixture. Configure KAIOS_ISSUER_PUBLIC_KEY with this key only in a local/test verifier; do not use it for production receipts.

curl invocation

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.

bash — curl
VERIFY_URL="${VERIFY_URL:-http://localhost:3000/api/kaios/verify}"
curl -X POST "$VERIFY_URL" \
  -H "Content-Type: application/json" \
  -d @envelope.json
This fixture succeeds only with the matching TEST ONLY public key configured on a local/test verifier. For production receipts, sign the exact UTF-8 bytes in 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')

What the server can give back

Every terminal status the integrator needs to handle.

Responses for POST /api/kaios/verify.
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.
Troubleshoot a response code
Malformed request — 400
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.

Diagnose a verification response

Why did the endpoint return 400?

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.

Why did the endpoint return 401 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.

What does 503 Verification disabled mean?

KAIOS_ISSUER_PUBLIC_KEY is unset on the server. Ask the server operator to configure it to enable verification.

What does a 200 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.

What happens on a 200

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.

HTTP 200 — application/json
{
  "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.