GentID Documentation
GentID is an open, federated identity protocol for AI agents. Organizations issue identities under their own domains from their own nodes; anyone verifies them with pure cryptography, with no central registry and no call to gentid.com required. The full RFC lives at gentid.com/spec, with published conformance test vectors.
These docs cover the protocol (the default path), self-hosting your own issuer node, the hosted API (a managed node, anchored to your domain), the SDK, and migration from registry-era v1.
Base URL
api.gentid.com/v1
Auth
API Key (Bearer)
Format
JSON
#Quick Start
Become an identity issuer for your own domain in three commands. Keys are generated locally and never transmitted.
# 1. Anchor your domain (prints the DNS TXT record to publish)
npx @gentid/cli init --domain your-domain.com
npx @gentid/cli check-dns
# 2. Issue a delegation and an agent under it
gentid delegate create --name ops --scopes "booking:*" --ceiling USD,500,2000,10000
gentid agent issue --name rebooker-7 --under ops --scopes booking:rebook
# 3. Serve — now anyone on the internet can verify your agents
gentid start # /.well-known/gentid.json, /v1/agents/{id}, /v1/revocationsDon't want to run a node? The hosted API gives you a managed node that is still anchored to your domain: one TXT record, no lock-in.
#Protocol
The protocol is the product. Everything below is defined normatively in the specification; this section is the practical tour. The reference verifier is @gentid/core: zero dependencies, zero network calls, and a published conformance suite any third party can implement against.
#Verifying requests
Agents attach two headers: GentID-Envelope (a signed, timestamped, nonced wrapper binding the request body) and GentID-Bundle(the certificate chain). Verification resolves the agent's domain anchors from DNS + .well-known, then checks the chain locally:
import { gentidAuth } from '@gentid/auth/express';
app.use('/api', gentidAuth()); // one line, as always
app.post('/api/book', (req, res) => {
const { id, domain, grants, assurance } = req.gentid;
// id → "gentic:agent:delta.com:ops:rebooker-7"
// assurance → 1 = domain-verified (2 = org-verified, 3 = transaction-proven)
// Verified against delta.com's own keys. gentid.com was never contacted.
res.json({ ok: true, bookedBy: id });
});Revocation freshness is class-based: pass gentidAuth({ operationClass: 'financial' }) for money-moving routes and stale or unknown revocation state becomes a hard deny, and there is deliberately no flag to bypass it.
#Identifiers & anchoring
Identities are domain-anchored and self-describing: gentic:agent:<domain>[:path…]:<name>. The path mirrors the delegation chain, so the identifier itself tells you who could have issued it. Trust roots are published in DNS (_gentid.<domain> TXT) and a signed /.well-known/gentid.json. Every identifier also projects to a did:web DID for interop.
gentic:agent:delta.com:ops:rebooker-7 ⇄ did:web:delta.com:gentid:ops:rebooker-7 _gentid.delta.com. IN TXT "v=gentid1; k=ed25519; p=<base64url pubkey>"
#Mandates & HTTP 402
A mandate (gentid.mandate.v1) is a signed spending authorization issued through the same delegation chain, where limits and scopes can only narrow. Verification proves the mandate is authentic; whether funds exist and budgets have room is enforced by a regulated settlement institution the issuing org designates. Verified ≠ enforceable: GentID never holds balances.
// Service side: charge for an action
res.status(402).set(paymentRequiredHeaders({
amount: 12.50, currency: 'USD',
payee: 'acme-travel.com',
enforcersAccepted: ['atheries.com'],
}));
// Agent side: escrow with the enforcer, then retry — one helper
const resp = await fetchWith402(request, { mandate, enforcers, reference });#Authentication
All authenticated endpoints require an API key. Generate one from the API Keys page in your dashboard. Pass it as a Bearer token in the Authorization header.
Authorization: Bearer gid_live_xxxxxxxxxxxx
Keys are prefixed with gid_live_ for production and gid_test_ for sandbox environments.
#Agents
An agent is a named, cryptographic identity. When created, it receives a unique, self-describing id of the form agent:<issuer-host>:<org>.<name> (see Agent id format), a public key stored on GentID servers, and a private key returned once, so store it securely. Agents created before this format existed keep their original gentic:agent: id, and both are accepted everywhere an agent id is expected.
#Create an agent
Creates a new agent identity and generates an Ed25519 keypair. The privateKey is returned only once. It is not stored and cannot be recovered.
namerequiredstring
Human-readable name for the agent. Max 128 chars.
const res = await fetch("https://api.gentid.com/v1/agents", {
method: "POST",
headers: {
"Authorization": "Bearer gid_live_xxxxxxxxxxxx",
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "payments-agent" }),
});
const agent = await res.json();
// {
// id: "gentic:agent:a3f9d2c1e8b4",
// name: "payments-agent",
// owner: "acme-corp",
// publicKey: "MCowBwYDK2VdAxEA...",
// privateKey: "MC4CAQAwBwYDK2Vd...", ← shown once only
// status: "active",
// createdAt: "2025-01-15T10:30:00.000Z"
// }#List agents
Returns a paginated list of agents belonging to your organization.
limitinteger
Number of results to return. Default 50, max 100.
offsetinteger
Offset for pagination. Default 0.
const res = await fetch("https://api.gentid.com/v1/agents?limit=20&offset=0", {
headers: { "Authorization": "Bearer gid_live_xxxxxxxxxxxx" },
});
const { agents, total } = await res.json();#Get an agent
curl https://api.gentid.com/v1/agents/gentic:agent:a3f9d2c1e8b4 \ -H "Authorization: Bearer gid_live_xxxxxxxxxxxx"
#Revoke & suspend
Agents can be suspended (temporarily disabled, re-activatable) or revoked (permanently invalidated). Revocation cannot be undone.
POST /agents/:id/suspend
POST /agents/:id/reactivate
POST /agents/:id/revoke
# Suspend an agent curl -X POST https://api.gentid.com/v1/agents/gentic:agent:a3f9d2/suspend \ -H "Authorization: Bearer gid_live_xxxxxxxxxxxx" # Revoke permanently curl -X POST https://api.gentid.com/v1/agents/gentic:agent:a3f9d2/revoke \ -H "Authorization: Bearer gid_live_xxxxxxxxxxxx"
#Signatures
Signatures are the core of GentID. Your agent signs messages locally with its Ed25519 private key, then you log the signature with GentID so it can be independently audited and verified.
#Log a signature
agentIdrequiredstring
The agent's full GentID identifier.
messagerequiredstring
The plaintext or JSON payload that was signed.
signaturerequiredstring
The Ed25519 signature encoded as base64.
// Sign locally with the agent's private key
import { sign } from "@noble/ed25519";
import { sha256 } from "@noble/hashes/sha256";
const message = "approve-payment-12345";
const msgHash = sha256(Buffer.from(message));
const sig = await sign(msgHash, privateKeyBytes);
const signature = Buffer.from(sig).toString("base64");
// Log to GentID
await fetch("https://api.gentid.com/v1/signatures", {
method: "POST",
headers: {
"Authorization": "Bearer gid_live_xxxxxxxxxxxx",
"Content-Type": "application/json",
},
body: JSON.stringify({
agentId: "gentic:agent:a3f9d2",
message,
signature,
}),
});#Verify a signature
Verifies a signature against the agent's stored public key. Returns valid: true or valid: false. No authentication required: anyone can verify.
curl -X POST https://api.gentid.com/v1/signatures/verify \
-H "Content-Type: application/json" \
-d '{
"agentId": "gentic:agent:a3f9d2",
"message": "approve-payment-12345",
"signature": "base64sig..."
}'
# → { "valid": true, "agentId": "gentic:agent:a3f9d2", "status": "active" }#List signatures
curl "https://api.gentid.com/v1/signatures/gentic:agent:a3f9d2?limit=50" \ -H "Authorization: Bearer gid_live_xxxxxxxxxxxx"
#Verification
GentID provides a public verification endpoint that anyone can use to check an agent's identity - no API key required. This is the foundation of interoperability.
#Public agent lookup
Returns the agent's public identity: name, public key, status, owner org, and issuance date. No authentication required. Use this to verify an agent before trusting it.
curl https://api.gentid.com/v1/verification/lookup/gentic:agent:a3f9d2
# Response:
# {
# "id": "gentic:agent:a3f9d2c1e8b4",
# "name": "payments-agent",
# "status": "active",
# "publicKey": "MCowBwYDK2VdAxEA...",
# "algorithm": "Ed25519",
# "owner": "acme-corp",
# "issuedAt": "2025-01-15T10:30:00.000Z"
# }#Request verification
Request a formal verification for an agent. Supports domain, email, and manual verification types.
curl -X POST https://api.gentid.com/v1/verification \
-H "Authorization: Bearer gid_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"agentId":"gentic:agent:a3f9d2","type":"domain"}'#Discovery & Federation
GentID is a protocol, not just this hosted API. Any organization can run its own GentID-compatible server, and agents issued by different servers can still verify each other, with no shared database or central registry required.
#Agent id format
New agents get a self-describing id of the form agent:<issuer-host>:<org-slug>.<agent-slug>, for example agent:api.gentid.com:acme-corp.payments-bot, or, on a self-hosted instance, agent:identity.apple.com:apple.travel-assistant. The issuer host is derivable from the id alone, which is what makes discovery possible without looking anything up first. Agents created before this format existed keep their original gentic:agent:<hex> id indefinitely, and both formats are accepted everywhere an agent id is expected.
#Discovery document
Every GentID-compatible server publishes a discovery document at its domain root, modeled on OIDC discovery. No authentication required.
curl https://api.gentid.com/.well-known/gentid-configuration
# Response:
# {
# "issuer": "https://api.gentid.com",
# "jwks_uri": "https://api.gentid.com/.well-known/jwks.json",
# "verification_endpoint": "https://api.gentid.com/api/v1/verification/lookup",
# "token_verification_endpoint": "https://api.gentid.com/api/v1/verification/verify-token",
# "protocol_version": "1.0"
# }#JWKS & token verification
Permission tokens are signed with the issuing instance's Ed25519 key (EdDSA), published at jwks_uri. A verifier can check a token's signature directly against the issuer's public key, with no callback to the issuing server needed once the (cacheable) discovery document and JWKS are fetched.
curl https://api.gentid.com/.well-known/jwks.json
# Response:
# { "keys": [{ "kty": "OKP", "crv": "Ed25519", "x": "...", "kid": "...", "alg": "EdDSA", "use": "sig" }] }@gentid/auth and @gentid/sdkdo this resolution automatically: when you don't pass an explicit apiUrl, they resolve the issuer from the token's agent id and verify locally. Legacy (gentic:agent:) tokens, or any resolution failure, fall back to calling the verify-token endpoint directly, exactly as in prior versions.
#Errors & codes
GentID uses standard HTTP status codes. Error responses always include a machine-readable code field.
{
"message": "Agent not found",
"code": "AGENT_NOT_FOUND"
}| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | Invalid request body or parameters |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 403 | FORBIDDEN | Action not allowed for this key |
| 403 | NOT_VERIFIED | Account not verified. Complete verification first |
| 404 | AGENT_NOT_FOUND | Agent does not exist |
| 409 | CONFLICT | Resource already exists |
| 429 | RATE_LIMITED | Too many requests. Back off and retry |
| 500 | INTERNAL_ERROR | Something went wrong on our side |
#Rate limits
The API is rate-limited per API key. Limits vary by plan:
| Plan | Requests / min | Verifications / month |
|---|---|---|
| Free | 60 | 1,000 |
| Pro | 300 | 100,000 |
| Enterprise | ∞ | Unlimited |
When rate-limited, the response includes Retry-After and X-RateLimit-Reset headers.
#TypeScript SDK
Install the official GentID SDK for Node.js (18+):
npm install @gentid/sdk
import { GentIDClient } from "@gentid/sdk";
const gentid = new GentIDClient({
apiKey: process.env.GENTID_API_KEY!, // gid_live_...
// baseUrl defaults to "https://api.gentid.com/v1"
});
// Create an agent identity
const agent = await gentid.createAgent({ name: "payments-bot", owner: "acme-corp" });
// ⚠ agent.privateKey is returned ONCE. Store it in your secrets vault
// Sign any action
const { signature } = await gentid.signMessage(agent.id, "approve-tx-99");
// Verify from anywhere. No API key needed
const { valid } = await gentid.verifySignature(agent.id, "approve-tx-99", signature);
console.log(valid); // true
// Public identity lookup. No API key needed
const identity = await gentid.lookupAgent(agent.id);
// { id, name, status, publicKey, algorithm, owner, issuedAt }#Webhooks
GentID sends signed HTTP POST requests to your registered endpoints when key events occur. Register and manage endpoints from the Webhooks page in your dashboard.
#Events
| Event | Fired when |
|---|---|
agent.created | A new agent identity is registered |
agent.updated | An agent is suspended, reactivated, or revoked |
signature.logged | An agent signs a message |
verification.updated | A verification request is approved or rejected |
#Payload format
{
"event": "agent.created",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"id": "gentic:agent:a3f9d2c1e8b4",
"name": "payments-bot",
"owner": "acme-corp",
"status": "active"
}
}#Verifying signatures
Every request includes an X-GentID-Signature header. Verify it using the signing secret shown when you add an endpoint:
import crypto from "crypto";
function verifyWebhook(secret: string, rawBody: string, sigHeader: string): boolean {
const [tPart, v1Part] = sigHeader.split(",");
const timestamp = tPart.replace("t=", "");
const expected = v1Part.replace("v1=", "");
const sig = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
// Express example
app.post("/webhooks/gentid", express.raw({ type: "application/json" }), (req, res) => {
const valid = verifyWebhook(
process.env.GENTID_WEBHOOK_SECRET!,
req.body.toString(),
req.headers["x-gentid-signature"] as string,
);
if (!valid) return res.status(401).send("Invalid signature");
const { event, data } = JSON.parse(req.body.toString());
// handle event...
res.status(200).send("OK");
});#Agent Gateway
@gentid/auth is a drop-in middleware package that lets any Node.js, Next.js, or Cloudflare Worker site accept AI agents as a new class of authenticated user, with no rebuilding required.
npm install @gentid/auth
Your server reads the token from Authorization: GentID <token> or X-GentID-Token, calls the GentID verify endpoint, and populates req.agent with the decoded identity and permissions.
#Express / Node.js
import { gentidAuth } from '@gentid/auth/express';
app.use('/api', gentidAuth());
app.post('/api/book', (req, res) => {
const agent = req.agent!;
// agent.agentId, agent.owner, agent.permissions…
res.json({ ok: true, bookedBy: agent.agentName });
});#Next.js
Wrap individual route handlers:
// app/api/book/route.ts
import { withGentidAuth } from '@gentid/auth/next';
export const POST = withGentidAuth(async (req, agent) => {
return Response.json({ bookedBy: agent.agentName, perms: agent.permissions });
});Or protect entire route groups via middleware.ts:
// middleware.ts
import { createGentidMiddleware } from '@gentid/auth/next';
export default createGentidMiddleware({ required: true });
export const config = { matcher: '/api/agent/:path*' };
// Then in a route handler:
import { getAgentFromHeaders } from '@gentid/auth/next';
const agent = getAgentFromHeaders(req.headers); // AgentContext | null#Cloudflare Worker
import { withGentidAuth } from '@gentid/auth/cloudflare';
export default {
fetch: withGentidAuth(async (request, agent, env, ctx) => {
return Response.json({ agent: agent.agentName, perms: agent.permissions });
}),
};Full integration guide with token flow, badge embed, and trust badge is available in the Integrations page of your dashboard.
#Self-hosting
GentID is open source under the MIT license. You can run the entire stack on your own infrastructure. The managed cloud at api.gentid.com is optional, and every self-hosted instance still verifies agents from any other GentID-compatible server via discovery.
There are three pieces, each its own repository, and you choose which ones to host:
- Web console: the Next.js dashboard and admin UI (this app).
- API server: issues identities, signs and verifies tokens, serves the
/api/v1surface. - @gentid/auth SDK: drop-in middleware your own apps embed; point it at your API.
#Web console
The console is a standard Next.js app. It reaches your API server through two server-side settings. It never exposes the API base to the browser. All portal calls go through same-origin /api/* route handlers that proxy to API_BASE_URL.
API_BASE_URLrequiredstring
Base URL of your GentID API, including the /api/v1 path. Defaults to http://localhost:1100/api/v1.
JWT_SECRETrequiredstring
Secret used to validate the gid_session cookie. Must be identical to the secret your API server signs session tokens with, or every login will be rejected.
# .env.local API_BASE_URL=https://api.your-domain.com/api/v1 JWT_SECRET=your-shared-secret-at-least-32-characters
git clone https://github.com/GentricAI/gentid-sdk.git cd gentid-sdk npm install npm run build npm start # serves the console on http://localhost:3000
#Point the SDK at your deployment
@gentid/auth defaults to https://api.gentid.com only as a last resort. Pass apiUrl to always target your own server explicitly, or leave it unset and let the middleware resolve the correct issuer automatically from each token's agent id via discovery, verifying locally against that issuer's published JWKS. The same behavior applies to the Next.js and Cloudflare entry points.
import { gentidAuth } from '@gentid/auth/express';
// Explicit — always verifies against your own server:
app.use('/api', gentidAuth({
apiUrl: 'https://api.your-domain.com',
}));
// Or omit apiUrl and let it resolve per-token via discovery:
app.use('/api', gentidAuth());#API server
The API server is the source of truth. It issues Ed25519 keypairs, signs permission tokens, and serves every endpoint documented above under /api/v1, plus the discovery documents at /.well-known. Two requirements when you run your own:
- It must expose the
/api/v1surface (auth, portal, signatures, verification) on the host your console'sAPI_BASE_URLpoints to. - It must sign session tokens with the same
JWT_SECRETthe console uses, so the console's middleware can validate logins.
The server lives at github.com/010101G/gentid-express, with the full environment variable reference, a Dockerfile, and a docker compose up path in its own README. Run npm run generate-signing-key once to create the Ed25519 key that signs permission tokens and gets published at /.well-known/jwks.json, then start the server and point your console at it.
#Migration from v1
Registry-era identities (gentic:agent:a3f9d2… hash IDs) and the REST verify endpoint keep working for a 12-month deprecation window, backed by the legacy node anchored at legacy.gentid.com. Nothing breaks on protocol day one.
To move an agent to a domain-anchored identity:
gentid migrate --from gentic:agent:a3f9d2e8b1c4 \ --to ops:rebooker-7 # new id under YOUR verified domain # → re-issues the certificate through your delegation chain and emits a # signed alias record (gentid.alias.v1) linking old id → new id, so # historical signatures and audit logs remain verifiable forever.
The v1 SDK surface (@gentid/sdk) is preserved as a thin wrapper over the protocol packages, with deprecation warnings pointing at the v2 equivalents. Verifiers presented with a legacy signature can resolve the alias record and report the new identity.
Ready to build?
Create your account and issue your first agent identity in minutes.
