curl --request POST \
--url https://api.humanos.tech/agents/self/keys/{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>"
}
'import requests
url = "https://api.humanos.tech/agents/self/keys/{did}"
payload = {
"cnf": { "jwk": {
"kty": "EC",
"crv": "P-256",
"x": "…",
"y": "…"
} },
"evidence": {
"type": "device-self",
"proof": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…"
},
"regNonce": "k3Jd0Q7bXm2sVQ8hZ1pYt4Rc",
"pop": "<string>"
}
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>'
})
};
fetch('https://api.humanos.tech/agents/self/keys/{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/{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>'
]),
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/{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}")
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/{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}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.humanos.tech/agents/self/keys/{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}"
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"
},
"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"
}Add Own Key
No API key: the agent calls this itself
The pop is signed by a key the agent already holds, over the challenge, and binds the new cnf
The new key is graded by the same evidence rules as one the organization adds, lands on the same chain, and records which key authorized it (§16.5)
curl --request POST \
--url https://api.humanos.tech/agents/self/keys/{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>"
}
'import requests
url = "https://api.humanos.tech/agents/self/keys/{did}"
payload = {
"cnf": { "jwk": {
"kty": "EC",
"crv": "P-256",
"x": "…",
"y": "…"
} },
"evidence": {
"type": "device-self",
"proof": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZpYS1yZWcrand0In0.…"
},
"regNonce": "k3Jd0Q7bXm2sVQ8hZ1pYt4Rc",
"pop": "<string>"
}
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>'
})
};
fetch('https://api.humanos.tech/agents/self/keys/{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/{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>'
]),
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/{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}")
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/{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}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.humanos.tech/agents/self/keys/{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}"
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"
},
"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 }.
Response
Returns the new key and the rung it earned.
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
Present only when the evidence could not be confirmed and a lower rung was granted: what was claimed.
device, hardware "hardware"