curl --request POST \
--url https://api.humanos.tech/agents/self/keys/rotate/{did} \
--header 'Content-Type: application/json' \
--data '
{
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "…",
"y": "…"
}
},
"evidence": {
"type": "device-self",
"proof": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…"
},
"regNonce": "k3Jd0Q7bXm2sVQ8hZ1pYt4Rc",
"pop": "<string>",
"predecessorId": "did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1"
}
'import requests
url = "https://api.humanos.tech/agents/self/keys/rotate/{did}"
payload = {
"cnf": { "jwk": {
"kty": "EC",
"crv": "P-256",
"x": "…",
"y": "…"
} },
"evidence": {
"type": "device-self",
"proof": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…"
},
"regNonce": "k3Jd0Q7bXm2sVQ8hZ1pYt4Rc",
"pop": "<string>",
"predecessorId": "did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
cnf: {jwk: {kty: 'EC', crv: 'P-256', x: '…', y: '…'}},
evidence: {
type: 'device-self',
proof: 'eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…'
},
regNonce: 'k3Jd0Q7bXm2sVQ8hZ1pYt4Rc',
pop: '<string>',
predecessorId: 'did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1'
})
};
fetch('https://api.humanos.tech/agents/self/keys/rotate/{did}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.humanos.tech/agents/self/keys/rotate/{did}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'cnf' => [
'jwk' => [
'kty' => 'EC',
'crv' => 'P-256',
'x' => '…',
'y' => '…'
]
],
'evidence' => [
'type' => 'device-self',
'proof' => 'eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…'
],
'regNonce' => 'k3Jd0Q7bXm2sVQ8hZ1pYt4Rc',
'pop' => '<string>',
'predecessorId' => 'did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.humanos.tech/agents/self/keys/rotate/{did}"
payload := strings.NewReader("{\n \"cnf\": {\n \"jwk\": {\n \"kty\": \"EC\",\n \"crv\": \"P-256\",\n \"x\": \"…\",\n \"y\": \"…\"\n }\n },\n \"evidence\": {\n \"type\": \"device-self\",\n \"proof\": \"eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…\"\n },\n \"regNonce\": \"k3Jd0Q7bXm2sVQ8hZ1pYt4Rc\",\n \"pop\": \"<string>\",\n \"predecessorId\": \"did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.humanos.tech/agents/self/keys/rotate/{did}")
.header("Content-Type", "application/json")
.body("{\n \"cnf\": {\n \"jwk\": {\n \"kty\": \"EC\",\n \"crv\": \"P-256\",\n \"x\": \"…\",\n \"y\": \"…\"\n }\n },\n \"evidence\": {\n \"type\": \"device-self\",\n \"proof\": \"eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…\"\n },\n \"regNonce\": \"k3Jd0Q7bXm2sVQ8hZ1pYt4Rc\",\n \"pop\": \"<string>\",\n \"predecessorId\": \"did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.humanos.tech/agents/self/keys/rotate/{did}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"cnf\": {\n \"jwk\": {\n \"kty\": \"EC\",\n \"crv\": \"P-256\",\n \"x\": \"…\",\n \"y\": \"…\"\n }\n },\n \"evidence\": {\n \"type\": \"device-self\",\n \"proof\": \"eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…\"\n },\n \"regNonce\": \"k3Jd0Q7bXm2sVQ8hZ1pYt4Rc\",\n \"pop\": \"<string>\",\n \"predecessorId\": \"did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1\"\n}"
response = http.request(request)
puts response.read_body{
"keyId": "did:web:humanos.tech:agent:2f8b6c41-9d37-4e5a-8b02-6c1d9e7a4f35#key-2",
"granted": {
"assurance": "hardware",
"binding": "attested"
},
"predecessorId": "did:web:humanos.tech:agent:2f8b6c41-9d37-4e5a-8b02-6c1d9e7a4f35#key-1",
"degradedFrom": "hardware"
}{
"statusCode": 400,
"message": "pop_invalid: registration evidence refused (v0.3 §16.5)",
"error": "Bad Request"
}{
"statusCode": 403,
"message": "self-service PoP refused: agent_not_bound",
"error": "Forbidden"
}{
"statusCode": 404,
"message": "agent not found",
"error": "Not Found"
}Rotate Own Key
No API key: the agent calls this itself
As Add Own Key, but only the predecessor may sign the pop, which binds predecessorId. The predecessor is labelled rotated and its revocation booked at the window end (v0.3 §7)
Revocation itself is never self-service: a stolen key must not revoke its siblings
curl --request POST \
--url https://api.humanos.tech/agents/self/keys/rotate/{did} \
--header 'Content-Type: application/json' \
--data '
{
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "…",
"y": "…"
}
},
"evidence": {
"type": "device-self",
"proof": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…"
},
"regNonce": "k3Jd0Q7bXm2sVQ8hZ1pYt4Rc",
"pop": "<string>",
"predecessorId": "did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1"
}
'import requests
url = "https://api.humanos.tech/agents/self/keys/rotate/{did}"
payload = {
"cnf": { "jwk": {
"kty": "EC",
"crv": "P-256",
"x": "…",
"y": "…"
} },
"evidence": {
"type": "device-self",
"proof": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…"
},
"regNonce": "k3Jd0Q7bXm2sVQ8hZ1pYt4Rc",
"pop": "<string>",
"predecessorId": "did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
cnf: {jwk: {kty: 'EC', crv: 'P-256', x: '…', y: '…'}},
evidence: {
type: 'device-self',
proof: 'eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…'
},
regNonce: 'k3Jd0Q7bXm2sVQ8hZ1pYt4Rc',
pop: '<string>',
predecessorId: 'did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1'
})
};
fetch('https://api.humanos.tech/agents/self/keys/rotate/{did}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.humanos.tech/agents/self/keys/rotate/{did}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'cnf' => [
'jwk' => [
'kty' => 'EC',
'crv' => 'P-256',
'x' => '…',
'y' => '…'
]
],
'evidence' => [
'type' => 'device-self',
'proof' => 'eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…'
],
'regNonce' => 'k3Jd0Q7bXm2sVQ8hZ1pYt4Rc',
'pop' => '<string>',
'predecessorId' => 'did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.humanos.tech/agents/self/keys/rotate/{did}"
payload := strings.NewReader("{\n \"cnf\": {\n \"jwk\": {\n \"kty\": \"EC\",\n \"crv\": \"P-256\",\n \"x\": \"…\",\n \"y\": \"…\"\n }\n },\n \"evidence\": {\n \"type\": \"device-self\",\n \"proof\": \"eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…\"\n },\n \"regNonce\": \"k3Jd0Q7bXm2sVQ8hZ1pYt4Rc\",\n \"pop\": \"<string>\",\n \"predecessorId\": \"did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.humanos.tech/agents/self/keys/rotate/{did}")
.header("Content-Type", "application/json")
.body("{\n \"cnf\": {\n \"jwk\": {\n \"kty\": \"EC\",\n \"crv\": \"P-256\",\n \"x\": \"…\",\n \"y\": \"…\"\n }\n },\n \"evidence\": {\n \"type\": \"device-self\",\n \"proof\": \"eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…\"\n },\n \"regNonce\": \"k3Jd0Q7bXm2sVQ8hZ1pYt4Rc\",\n \"pop\": \"<string>\",\n \"predecessorId\": \"did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.humanos.tech/agents/self/keys/rotate/{did}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"cnf\": {\n \"jwk\": {\n \"kty\": \"EC\",\n \"crv\": \"P-256\",\n \"x\": \"…\",\n \"y\": \"…\"\n }\n },\n \"evidence\": {\n \"type\": \"device-self\",\n \"proof\": \"eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…\"\n },\n \"regNonce\": \"k3Jd0Q7bXm2sVQ8hZ1pYt4Rc\",\n \"pop\": \"<string>\",\n \"predecessorId\": \"did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1\"\n}"
response = http.request(request)
puts response.read_body{
"keyId": "did:web:humanos.tech:agent:2f8b6c41-9d37-4e5a-8b02-6c1d9e7a4f35#key-2",
"granted": {
"assurance": "hardware",
"binding": "attested"
},
"predecessorId": "did:web:humanos.tech:agent:2f8b6c41-9d37-4e5a-8b02-6c1d9e7a4f35#key-1",
"degradedFrom": "hardware"
}{
"statusCode": 400,
"message": "pop_invalid: registration evidence refused (v0.3 §16.5)",
"error": "Bad Request"
}{
"statusCode": 403,
"message": "self-service PoP refused: agent_not_bound",
"error": "Forbidden"
}{
"statusCode": 404,
"message": "agent not found",
"error": "Not Found"
}Headers
Pin request, response, and webhook shapes to a specific dated API version (YYYY-MM-DD). Omit to use the version pinned to your API key (set when the key is created; new keys default to the latest version). New integrations should target the latest version.
^\d{4}-\d{2}-\d{2}$"2026-10-16"
Path Parameters
The agent's DID, URL-encoded.
"did:web:humanos.tech:agent:2f8b6c41-9d37-4e5a-8b02-6c1d9e7a4f35"
Body
The agent's public key, as a P-256 JWK. Carried BESIDE the evidence, never inside it: evidence proves where a key lives and does not transport it — for an attestation the key is derived from the signed document and cross-checked against this one.
{
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "…",
"y": "…"
}
}
Confinement evidence (v0.3 §16.5). Every key you hold proves it is held: proof is a via-reg+jwt signed by the key over { cnf: { jkt }, aud, nonce, iat } — aud and nonce from POST /agents/register-challenge. device-self (+ proof) grants pop/self; custody (+ proof) pop/custodial; attestation-eat and webauthn chained to a pinned root grant device or hardware and need no proof. Evidence that cannot be checked degrades — an unanchored quote to pop/self, then with proof. Evidence that is wrong is refused with its code; none is refused.
{
"type": "device-self",
"proof": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…"
}
The single-use nonce from POST /agents/register-challenge.
"k3Jd0Q7bXm2sVQ8hZ1pYt4Rc"
A via-pop+jwt signed by an EXISTING usable key of this agent: aud the platform DID, nonce the registration challenge, action_hash = digestSRI(JCS({ mandateId: "self:", action: "key_add", params: { cnf } })) — so the PoP binds THIS new key. A rotation signs action: "key_rotate" and params: { cnf, predecessorId }.
The key to rotate from, which must sign pop. Bound into the PoP, so a proof for one rotation cannot rotate another key.
"did:web:humanos.tech:agent:3f2a9c1e-7b4d-4e8a-9f60-2d1c5b7a8e90#key-1"
Response
Returns the successor key, the rung it earned, and the predecessor.
The key the change concerns, what a mandate pins (D7).
"did:web:humanos.tech:agent:2f8b6c41-9d37-4e5a-8b02-6c1d9e7a4f35#key-2"
The rung the evidence earned after verification.
Show child attributes
Show child attributes
The key rotated from, now labelled rotated. It stays usable until its rotation window ends.
"did:web:humanos.tech:agent:2f8b6c41-9d37-4e5a-8b02-6c1d9e7a4f35#key-1"
Present only when the evidence could not be confirmed and a lower rung was granted: what was claimed.
device, hardware "hardware"