Overview
API keys authenticate requests to the Verification API's POST /verify endpoint. Each key is scoped to a single AI agent and requires an EIP-712 signature from the agent owner to create, rotate, or revoke.
Key Format: kya_live_v1_[a-f0-9]{64}
Warning: API keys are shown only once at creation. Store them securely. If lost, you must rotate to generate a new key.
Key Management Endpoints
All mutating key endpoints (generate, rotate, revoke) require EIP-712 signatures and are rate limited to 10 requests per hour per IP + agentId combination.
POST /keys/generate
Create a new API key for an agent.
Request:
POST https://api.kyachain.xyz/keys/generate
Content-Type: application/json
{
"agentId": "42",
"signature": "0x<130-hex-chars>",
"nonce": "1",
"timestamp": 1707408000,
"declaredEndpoint": "https://myapp.com" // optional
}| Field | Type | Required | Description |
|---|---|---|---|
agentId | string | Yes | Agent ID as numeric string |
signature | string | Yes | EIP-712 signature from agent owner |
nonce | string | Yes | Random nonce (prevents replay) |
timestamp | number | Yes | Current Unix timestamp |
declaredEndpoint | string | No | Your application's endpoint URL (used for mismatch detection in /verify) |
Response (201 Created):
{
"apiKey": "kya_live_v1_abc123...",
"agentId": "42",
"message": "API key generated successfully"
}Errors:
409 KEY_ALREADY_EXISTS: Agent already has an API key (use POST /keys/rotate instead)401 SIGNATURE_INVALID: Signature doesn't match agent owner404 AGENT_NOT_FOUND: Agent ID doesn't exist429 RATE_LIMITED: Exceeded 10 requests/hour limit
POST /keys/rotate
Rotate an existing API key (generates new key, invalidates old one immediately).
Request:
POST https://api.kyachain.xyz/keys/rotate
Content-Type: application/json
{
"agentId": "42",
"signature": "0x<130-hex-chars>",
"nonce": "2",
"timestamp": 1707408100,
"declaredEndpoint": "https://myapp.com" // optional
}Same fields as POST /keys/generate.
Response (200 OK):
{
"apiKey": "kya_live_v1_def456...",
"agentId": "42",
"message": "API key rotated successfully"
}Note: The old API key is immediately invalidated. Update your application with the new key before the old one expires.
Errors:
Same as POST /keys/generate (except KEY_ALREADY_EXISTS doesn't apply).
DELETE /keys
Revoke an API key permanently.
Request:
DELETE https://api.kyachain.xyz/keys
Content-Type: application/json
{
"agentId": "42",
"signature": "0x<130-hex-chars>",
"nonce": "3",
"timestamp": 1707408200
}| Field | Type | Required | Description |
|---|---|---|---|
agentId | string | Yes | Agent ID as numeric string |
signature | string | Yes | EIP-712 signature from agent owner |
nonce | string | Yes | Random nonce |
timestamp | number | Yes | Current Unix timestamp |
Response (200 OK):
{
"success": true,
"agentId": "42"
}Note: This endpoint is idempotent. Revoking a non-existent key returns 200.
Errors:
401 SIGNATURE_INVALID: Signature doesn't match agent owner404 AGENT_NOT_FOUND: Agent ID doesn't exist429 RATE_LIMITED: Exceeded 10 requests/hour limit
GET /keys/status
Check if an agent has an active API key (public endpoint, no authentication required).
Request:
GET https://api.kyachain.xyz/keys/status?agentId=42| Parameter | Type | Required | Description |
|---|---|---|---|
agentId | string | Yes | Agent ID as numeric string |
Response (200 OK):
{
"hasKey": true,
"agentId": "42"
}EIP-712 Signature Format
All mutating key endpoints use the same EIP-712 signature format as the /verify/signature endpoint. The signature must be created by the wallet that owns the agent NFT.
import { createWalletClient, http } from 'viem';
import { kyaChain } from './chains';
const walletClient = createWalletClient({
chain: kyaChain,
transport: http('https://rpc.kyachain.xyz'),
});
const agentId = 42n;
const nonce = BigInt(Math.floor(Math.random() * 1e18));
const timestamp = BigInt(Math.floor(Date.now() / 1000));
const signature = await walletClient.signTypedData({
domain: {
name: 'KYA Verification',
version: '1',
chainId: 8004,
verifyingContract: '0xA1393CB409E2fE5573C0840189622aA0e33947b2', // IdentityRegistry
},
types: {
Verification: [
{ name: 'agentId', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'timestamp', type: 'uint256' },
],
},
primaryType: 'Verification',
message: { agentId, nonce, timestamp },
});
// Use signature in key management request
const response = await fetch('https://api.kyachain.xyz/keys/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: agentId.toString(),
signature,
nonce: nonce.toString(),
timestamp: Number(timestamp),
declaredEndpoint: 'https://myapp.com',
}),
});
const { apiKey } = await response.json();
console.log('New API key:', apiKey);Complete Key Lifecycle Example
import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { kyaChain } from './chains';
const account = privateKeyToAccount('0x...');
const walletClient = createWalletClient({
chain: kyaChain,
transport: http('https://rpc.kyachain.xyz'),
account,
});
const agentId = 42n;
const API_BASE = 'https://api.kyachain.xyz';
// Helper to create signature
async function createSignature() {
const nonce = BigInt(Math.floor(Math.random() * 1e18));
const timestamp = BigInt(Math.floor(Date.now() / 1000));
const signature = await walletClient.signTypedData({
domain: {
name: 'KYA Verification',
version: '1',
chainId: 8004,
verifyingContract: '0xA1393CB409E2fE5573C0840189622aA0e33947b2',
},
types: {
Verification: [
{ name: 'agentId', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'timestamp', type: 'uint256' },
],
},
primaryType: 'Verification',
message: { agentId, nonce, timestamp },
});
return { signature, nonce, timestamp };
}
// 1. Check if agent has a key
const statusRes = await fetch(`${API_BASE}/keys/status?agentId=${agentId}`);
const { hasKey } = await statusRes.json();
if (!hasKey) {
// 2. Generate new key
const { signature, nonce, timestamp } = await createSignature();
const generateRes = await fetch(`${API_BASE}/keys/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: agentId.toString(),
signature,
nonce: nonce.toString(),
timestamp: Number(timestamp),
declaredEndpoint: 'https://myapp.com',
}),
});
const { apiKey } = await generateRes.json();
console.log('Generated API key:', apiKey);
// Store securely (e.g., environment variable, secret manager)
process.env.KYA_API_KEY = apiKey;
}
// 3. Use the key for verification
const verifyRes = await fetch(`${API_BASE}/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
apiKey: process.env.KYA_API_KEY,
agentId: agentId.toString(),
}),
});
const verifyData = await verifyRes.json();
console.log('Agent verified:', verifyData.valid);
// 4. Rotate key (e.g., for security incident or scheduled rotation)
const { signature: rotateSig, nonce: rotateNonce, timestamp: rotateTs } = await createSignature();
const rotateRes = await fetch(`${API_BASE}/keys/rotate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: agentId.toString(),
signature: rotateSig,
nonce: rotateNonce.toString(),
timestamp: Number(rotateTs),
}),
});
const { apiKey: newKey } = await rotateRes.json();
console.log('Rotated to new key:', newKey);
process.env.KYA_API_KEY = newKey;
// 5. Revoke key (e.g., decommissioning agent)
const { signature: revokeSig, nonce: revokeNonce, timestamp: revokeTs } = await createSignature();
const revokeRes = await fetch(`${API_BASE}/keys`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
agentId: agentId.toString(),
signature: revokeSig,
nonce: revokeNonce.toString(),
timestamp: Number(revokeTs),
}),
});
const { success } = await revokeRes.json();
console.log('Key revoked:', success);Security Best Practices
-
Store keys securely: Never commit API keys to version control. Use environment variables or secret management services.
-
Rotate regularly: Implement periodic key rotation (e.g., every 90 days) to limit exposure window.
-
Revoke on compromise: If a key is leaked, immediately call POST /keys/rotate to invalidate it.
-
Use declaredEndpoint: Set
declaredEndpointduring key generation to enable endpoint mismatch detection in /verify responses. -
Monitor rate limits: Track
X-RateLimit-Remainingheaders and implement backoff strategies. -
Validate signatures offline: For critical applications, verify EIP-712 signatures client-side before sending to the API.