Documentation

Implement UniKey Cloud

Add agent verification to any service that receives agent traffic. Forward the incoming Trust Packet to one endpoint and get a cryptographic verdict — before you execute the action.

Overview

Your service receives a request from an AI agent that carries a Trust Packet — a signed JSON object asserting identity, authority, and scope. You POST that packet to UniKey Cloud, which verifies the signature against the signer's DNS-published key, checks expiry, audience, scope, and any delegation chain, and returns a verdict. There are two endpoints:

POST /api/v1/verify-packet Verify a self-contained Trust Packet (JSON body).
POST /api/v1/verify Verify a signed HTTP request via X-UniKey-* headers.

Base URL: https://www.unikeycloud.com/api/v1

You authenticate to the API with UniKey itself — you sign your request with your domain's key (no API keys). Same primitive you're verifying; see Authentication.

Quickstart

  1. 1

    Add your domain

    In the dashboard under Domains, add the domain your service calls from (e.g. acme.com). You'll get an Ed25519 key and the exact DNS record to publish.

  2. 2

    Publish the DNS record & verify

    Add the shown TXT record at unikey._domainkey.<domain>, then click Verify. This is how the API knows the request is really you.

  3. 3

    Sign &amp; send a verification

    Sign your request with your domain key (see Authentication) and POST a Trust Packet to /api/v1/verify-packet.

  4. 4

    Act on the verdict

    Reject the action unless valid is true.

Authentication

There are no API keys. You authenticate by signing the request itself with your verified domain's Ed25519 key — the same DNS-published-key primitive you're using UniKey to verify. We resolve your key from DNS and map the signer domain to your account.

Build a canonical string and sign it, then send five headers:

canonical string (newline-joined, no trailing newline)
POST
https://www.unikeycloud.com/api/v1/verify-packet
<base64( sha256(request body) )>
<unix timestamp, seconds>
<agent email>
request headers
X-UniKey-Signer:     acme.com                       # your verified domain
X-UniKey-Timestamp:  1769450662                     # must be within 5 minutes
X-UniKey-Body-Hash:  base64(sha256(body))
X-UniKey-Signature:  base64(ed25519_sign(canonical))
X-Agent-Email:       agent@acme.com

The signed URL must exactly match the endpoint URL (scheme + host + path), and the body must be byte-identical to what you hash and send. Requests older than 5 minutes are rejected. Worked, runnable examples are below.

Publish your key

Publish your Ed25519 public key as a DNS TXT record so we (and any verifier) can resolve it — the same mechanism DKIM uses for email. The dashboard generates the key and shows you the exact record; this is what makes your signed requests authenticate. Generate a keypair with openssl genpkey -algorithm ed25519 if you bring your own.

dns
unikey._domainkey.yourdomain.com.  IN TXT  "v=DKIM1; k=ed25519; p=MCowBQYDK2VwAyEA…"

Verify a Trust Packet

Sign the request (see Authentication) and POST the packet you received to /api/v1/verify-packet. These examples are verified end-to-end:

# needs your Ed25519 key as ed25519.pem and the packet in trust-packet.json
URL="https://www.unikeycloud.com/api/v1/verify-packet"
TS=$(date +%s)
HASH=$(openssl dgst -sha256 -binary trust-packet.json | openssl base64 -A)
SIG=$(printf '%s\n%s\n%s\n%s\n%s' POST "$URL" "$HASH" "$TS" agent@acme.com \
  | openssl pkeyutl -sign -inkey ed25519.pem -rawin | openssl base64 -A)

curl "$URL" \
  -H "X-UniKey-Signer: acme.com" \
  -H "X-UniKey-Timestamp: $TS" \
  -H "X-UniKey-Body-Hash: $HASH" \
  -H "X-UniKey-Signature: $SIG" \
  -H "X-Agent-Email: agent@acme.com" \
  -H "Content-Type: application/json" \
  --data-binary @trust-packet.json

The Trust Packet body

The packet you forward looks like this (you receive it from the agent — you don't build it):

trust-packet.json
{
  "header":  { "tp_version": "1.0", "packet_id": "pkt_9a4f…", "issued_at": "2026-05-26T18:04:00Z", "expires_at": "2026-05-26T18:09:00Z", "nonce": "b7e2…" },
  "claims":  { "subject": "claude@acme.ai", "issuer": "acme.ai", "audience": "store.example", "scope": ["charge:60"] },
  "payload": { "action": "purchase_item", "params": { "item": "Air Max", "amount": 55 } },
  "signatures": [{ "algorithm": "ed25519", "signer": "acme.ai", "key_selector": "unikey", "signature": "MEUCIQ…" }]
}

Successful response

200 OK
{
  "valid": true,
  "packet_id": "pkt_9a4f…",
  "subject": "claude@acme.ai",
  "issuer": "acme.ai",
  "audience": "store.example",
  "action": "purchase_item",
  "scope": ["charge:60"],
  "signer": "acme.ai",
  "dns_hardened": false,
  "environment": "live",
  "verified_at": "2026-05-26T18:04:22Z"
}

Verify a signed request

When an agent signs an HTTP request directly (DKIM-over-HTTPS) instead of sending a packet, relay the agent's signature headers to /api/v1/verify (your own call is still signed for auth, as above):

headers
X-UniKey-Signature:  <base64 Ed25519 signature>
X-UniKey-Signer:     acme.ai
X-UniKey-Timestamp:  1769450662
X-UniKey-Body-Hash:  <base64 SHA-256 of body>
X-Agent-Email:       claude@acme.ai

Verify by email

A Trust Packet is verified the same way no matter how it reaches us — the packet's signature is the security, not the channel. So you can skip the HTTP call entirely: email a packet as JSON to your account's unique address and we verify it, record it to your ledger, and POST the result to your webhook. Find your address under Dashboard → Email.

email
To:      verify-<your-token>@inbound.unikeycloud.com
Subject: (anything)

{ "header": { ... }, "claims": { ... }, "payload": { ... }, "signatures": [ ... ] }

We POST the same JSON the API returns to your configured webhook (or to a callback_url named in the packet). Each delivery is signed so you can confirm it came from us — verify the X-UniKey-Signature header before trusting the body:

webhook
POST https://your-app.com/unikey/webhook
X-UniKey-Event:      verification
X-UniKey-Signature:  sha256=<hex HMAC-SHA256 of the raw body, keyed by your webhook secret>
Content-Type:        application/json

{ "valid": true, "packet_id": "...", "signer": "acme.ai", "environment": "live", ... }

Responses & errors

Every response includes a boolean valid. Failures return a stable error code and a human-readable message.

HTTP error Meaning
200 Verified. Check the valid field.
401 invalid_signature Request or packet signature did not verify against the DNS key.
401 expired_request Signed request is older than the 5-minute window.
400 missing_headers A required X-UniKey-* header is absent.
400 invalid_packet Malformed or missing required packet fields.
403 unknown_signer Valid signature, but the signer domain isn't a verified account domain.
403 untrusted_signer Signer is not allowed.
422 dns_lookup_failed No UniKey key found for the signer domain.
422 dns_inconsistency Resolvers disagreed — failed closed (hardening).

Libraries

Prefer to verify in-process without the hosted API? The open-source verifier libraries implement the full protocol. UniKey Cloud runs the hardened infrastructure around them — see self-hosted.

Ruby

gem "unikey"

Node

npm i @unikey/verify

Python

pip install unikey-tp

Ready to verify your first agent?

UniKey Cloud is in an invite-only pilot today. Request access and we'll get you a test key.

Request access