{"openapi":"3.0.0","info":{"title":"IdClawPassport API","version":"0.9.6","description":"API for AI agent identities backed by RODiT tokens on NEAR, including facial token_id encoding. Canonical OpenAPI document: `GET /openapi.json`.\n\n**Errors:** Application and nginx edge failures use `{ error: { code, message, details? }, requestId, timestamp }` (see `components.schemas.ErrorResponse`). Server and upstream failures return stable machine codes with generic `message` text; correlate with `requestId` in structured logs. Clients may send `X-Request-Id` or `X-Correlation-Id`; the edge forwards `X-Request-Id` to the API.","x-policies":{"termsOfService":"/.well-known/terms-of-service","privacyPolicy":"/.well-known/privacy-policy","dataRetention":"/.well-known/data-retention","whyidentyclaw":"/.well-known/why-identyclaw"},"contact":{"name":"IdentyClaw Support","email":"support@identyclaw.com","url":"https://identyclaw.com/contact"},"license":{"name":"Proprietary","url":"https://identyclaw.com/license"},"x-publicRateLimit":{"comment":"Public route rate limits are enforced at the nginx edge, not in the Node app. The limit key is the direct TCP peer `$remote_addr` (not `X-Forwarded-For`). Use `set_real_ip_from` / `real_ip_header` only when a trusted upstream is documented.","rateLimitKey":"$remote_addr","sustainedRequestsPerMinutePerIp":1101,"burstPerIp":6101,"configFiles":["nginx/nginx.development.conf","nginx/nginx.main.conf"]},"x-edgeGateway":{"comment":"Responses below may be returned by nginx before the Node app (see nginx @request_error and @handle_error).","requestIdHeaders":["X-Request-Id","X-Correlation-Id"],"errors":[{"status":400,"code":"REQUEST_HEADER_TOO_LARGE","message":"Request headers or payload exceed size limits"},{"status":502,"code":"UPSTREAM_UNAVAILABLE","message":"The API gateway could not reach the application server"},{"status":504,"code":"UPSTREAM_UNAVAILABLE","message":"The API gateway could not reach the application server"}]}},"servers":[{"url":"/","description":"Current server"}],"security":[],"components":{"parameters":{"XRequestId":{"name":"X-Request-Id","in":"header","required":false,"schema":{"type":"string"},"description":"Optional client-supplied correlation id (ULID recommended). Echoed on error responses; nginx forwards it to the API when present, otherwise generates one."},"XCorrelationId":{"name":"X-Correlation-Id","in":"header","required":false,"schema":{"type":"string"},"description":"Fallback correlation header when `X-Request-Id` is omitted."}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"RODiT-JWT","description":"RODiT-issued JWT access token validated by @rodit/rodit-auth-be (RoditClient.authenticate), obtained via the RODiT login flow (e.g. /api/login). This is not an API key."}},"schemas":{"TokenId":{"type":"string","pattern":"^[a-z]{12}$","description":"Facial token_id encoding: 12 lowercase letters a-z (11 category indices + 1 checksum)."},"DidVerificationMethod":{"type":"object","properties":{"id":{"type":"string","description":"Identifier for the verification method fragment."},"type":{"type":"string","description":"Verification method type, e.g. Ed25519VerificationKey2020."},"controller":{"type":"string","description":"Controller DID for the verification method."},"publicKeyBase58":{"type":"string","description":"Base58-encoded Ed25519 public key."}},"required":["id","type","controller","publicKeyBase58"]},"DidService":{"type":"object","properties":{"id":{"type":"string","description":"Service fragment identifier."},"type":{"type":"string","description":"Service type identifier."},"serviceEndpoint":{"description":"Service endpoint location or descriptor.","oneOf":[{"type":"string"},{"type":"object"}]}},"required":["id","type","serviceEndpoint"]},"DidDocument":{"type":"object","properties":{"@context":{"type":"array","description":"Ordered list of JSON-LD contexts used in the DID document.","items":{"oneOf":[{"type":"string"},{"type":"object"}]}},"id":{"type":"string","description":"Primary DID identifier."},"alsoKnownAs":{"type":"array","items":{"type":"string"},"description":"Alternate DID identifiers for the same subject."},"controller":{"type":"string","description":"Controller account for the DID document."},"verificationMethod":{"type":"array","items":{"$ref":"#/components/schemas/DidVerificationMethod"},"description":"Verification methods available on the DID document."},"authentication":{"type":"array","items":{"type":"string"}},"assertionMethod":{"type":"array","items":{"type":"string"}},"service":{"type":"array","items":{"$ref":"#/components/schemas/DidService"},"description":"Service endpoints advertised in the DID document."},"requestId":{"type":"string","description":"Request correlation identifier returned by the API."}},"required":["@context","id","controller","verificationMethod","authentication","assertionMethod","service","requestId"]},"ErrorResponse":{"type":"object","description":"Unified error response structure (error-handling-standard.md). All application and documented nginx edge errors use this envelope. Server/upstream failures use stable `code` values and generic `message` text; raw upstream exception text is logged with `requestId`, not returned to clients.","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable error code (SCREAMING_SNAKE_CASE). Examples: HOLA_* validation; LOGIN_* / RODIT_* auth; WEBHOOK_SIGNATURE_INVALID; RATE_LIMIT_EXCEEDED; REQUEST_HEADER_TOO_LARGE / UPSTREAM_UNAVAILABLE (nginx edge); SIGNCLIENT_*; MCP_LIST_FAILED / MCP_RESOURCE_*; INVALID_DID / DID_RESOLUTION_FAILED / DID_NOT_FOUND; SESSIONS_RETRIEVE_FAILED / SESSION_CLEANUP_FAILED / SESSION_TERMINATION_FAILED; SERVICE_UNAVAILABLE."},"message":{"type":"string","description":"Human-readable error message safe for clients (generic for server/upstream failures)"},"details":{"type":"object","additionalProperties":true,"nullable":true,"description":"Optional client-safe context (validation hints, resource ids). Omits raw upstream exception messages."}},"required":["code","message"]},"requestId":{"type":"string","description":"Unique request identifier for tracing (ULID format)"},"timestamp":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when error occurred"}},"required":["error","requestId","timestamp"],"example":{"error":{"code":"HOLA_VALIDATION_FAILED","message":"Token ID must be exactly 12 lowercase letters"},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}}},"examples":{"ErrorInvalidDid":{"summary":"Invalid DID or token","value":{"error":{"code":"INVALID_DID","message":"Invalid DID or token identifier"},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}},"ErrorDidResolutionFailed":{"summary":"DID resolution server error","value":{"error":{"code":"DID_RESOLUTION_FAILED","message":"Failed to resolve DID document"},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}},"ErrorSignclientFailed":{"summary":"Signclient upstream failure","value":{"error":{"code":"SIGNCLIENT_FAILED","message":"Failed to sign client request"},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}},"ErrorSignclientFeeMismatch":{"summary":"Minting fee mismatch","value":{"error":{"code":"SIGNCLIENT_FEE_MISMATCH","message":"Minting fee mismatch","details":{"message":"The minting fee provided does not match the server's calculation","clientFee":"0.05","serverFee":"0.10"}},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}},"ErrorMcpListFailed":{"summary":"MCP resource list failure","value":{"error":{"code":"MCP_LIST_FAILED","message":"Failed to list resources"},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}},"ErrorMcpResourceNotFound":{"summary":"MCP resource not found","value":{"error":{"code":"MCP_RESOURCE_NOT_FOUND","message":"Resource not found","details":{"uri":"openapi:swagger"}},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}},"ErrorSessionsRetrieveFailed":{"summary":"Session list failure","value":{"error":{"code":"SESSIONS_RETRIEVE_FAILED","message":"Failed to retrieve sessions"},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}},"ErrorEdgeRequestTooLarge":{"summary":"Nginx request/header size limit","value":{"error":{"code":"REQUEST_HEADER_TOO_LARGE","message":"Request headers or payload exceed size limits"},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}},"ErrorEdgeUpstreamUnavailable":{"summary":"Nginx upstream unreachable","value":{"error":{"code":"UPSTREAM_UNAVAILABLE","message":"The API gateway could not reach the application server"},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}}}},"paths":{"/health":{"get":{"summary":"Health check","responses":{"200":{"description":"Service health status with dependency checks.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["healthy","degraded"]},"degraded":{"type":"boolean"},"checks":{"type":"object","additionalProperties":true},"service":{"type":"string"},"timestamp":{"type":"string","format":"date-time"},"instance":{"type":"object","properties":{"hostname":{"type":"string"},"pid":{"type":"integer"}},"required":["hostname","pid"]}},"required":["status","degraded","checks","service","timestamp","instance"]}}}}},"operationId":"get_health"}},"/":{"get":{"summary":"Root API discovery endpoint with enrollment information","description":"Returns API discovery information from the root path (`/`), including enrollment URL, documentation links, and available endpoints grouped by access level. No `/.well-known/api` alias is provided.","tags":["Discovery"],"responses":{"200":{"description":"API discovery information","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"API name"},"version":{"type":"string","description":"API version"},"enrollment":{"type":"object","properties":{"url":{"type":"string","description":"URL for purchasing RODiT tokens"},"guide":{"type":"string","description":"Well-known enrollment guide path"},"quickStart":{"type":"string","description":"Quick-start enrollment HTML guide path"},"badge":{"type":"string","description":"Enrollment badge/call-to-action text"}},"required":["url","guide","quickStart","badge"]},"documentation":{"type":"object","properties":{"swagger":{"type":"string","description":"Swagger specification URL"},"openapi":{"type":"string","description":"OpenAPI specification URL"},"html":{"type":"string","description":"HTML documentation URL"}}},"endpoints":{"type":"object","properties":{"public":{"type":"array","items":{"type":"string"},"description":"Publicly accessible endpoints"},"authenticated":{"type":"array","items":{"type":"string"},"description":"Endpoints requiring authentication"},"privileged":{"type":"array","items":{"type":"string"},"description":"Endpoints requiring admin privileges"}}},"requestId":{"type":"string","description":"Request correlation identifier"},"timestamp":{"type":"string","format":"date-time","description":"Response timestamp"},"wellKnown":{"type":"object","properties":{"enrollment":{"type":"string"},"termsOfService":{"type":"string"},"privacyPolicy":{"type":"string"},"dataRetention":{"type":"string"},"did":{"type":"string"}},"required":["enrollment","termsOfService","privacyPolicy","dataRetention","did"]},"mcp":{"type":"object","properties":{"endpoint":{"type":"string","description":"MCP streamable HTTP endpoint path"},"resources":{"type":"string","description":"MCP resource listing endpoint"},"schema":{"type":"string","description":"MCP schema endpoint"},"discovery":{"type":"string","description":"Well-known MCP discovery metadata endpoint"},"tools":{"type":"array","description":"Canonical MCP tool names supported by this server","items":{"type":"string"}}},"required":["endpoint","resources","schema","discovery","tools"]}},"required":["name","version","enrollment","documentation","endpoints","wellKnown","mcp","requestId","timestamp"]}}}}},"operationId":"get_root"}},"/.well-known/enrollment":{"get":{"summary":"Enrollment information and guidance","description":"Provides enrollment information including onboarding steps and support details.","tags":["Discovery"],"responses":{"200":{"description":"Enrollment information","content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","description":"Enrollment page title"},"enrollment":{"type":"object","properties":{"url":{"type":"string","description":"Purchase URL"},"description":{"type":"string","description":"Enrollment description"}}},"enrollmentSteps":{"type":"array","items":{"type":"object","properties":{"step":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string"},"details":{"type":"object"}}}},"support":{"type":"object","properties":{"faq":{"type":"string"},"contact":{"type":"string"},"documentation":{"type":"string"},"examples":{"type":"string"}}},"requestId":{"type":"string","description":"Request correlation identifier"},"timestamp":{"type":"string","format":"date-time","description":"Response timestamp"}},"required":["title","enrollment","enrollmentSteps","support","requestId","timestamp"]}}}}},"operationId":"get_well_known_enrollment"}},"/.well-known/mcp":{"get":{"summary":"MCP discovery metadata","description":"Returns canonical MCP discovery metadata including transport endpoint (`/mcp`), REST resource access paths (`/api/mcp/resources`, `/api/mcp/resource/{uri}`), supported MCP tools (`list_resources`, `get_resource`), registration naming hints, and `httpAccessForAgents` guidance for curl/shell agents (do not probe GET `/mcp` without an MCP client).","tags":["Discovery"],"responses":{"200":{"description":"MCP discovery metadata.","content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"transport":{"type":"object","properties":{"protocol":{"type":"string"},"endpoint":{"type":"string"},"absoluteEndpoint":{"type":["string","null"]}},"required":["protocol","endpoint","absoluteEndpoint"]},"resources":{"type":"object","properties":{"list":{"type":"string"},"getByUri":{"type":"string"},"examples":{"type":"array","items":{"type":"string"}}},"required":["list","getByUri","examples"]},"tools":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"}},"required":["name","description"]}},"registration":{"type":"object","properties":{"recommendedServerName":{"type":"string"},"aliases":{"type":"array","items":{"type":"string"}}},"required":["recommendedServerName","aliases"]},"docs":{"type":"object","properties":{"mcpResources":{"type":"string"},"enrollmentGuide":{"type":"string"},"openapi":{"type":"string"}},"required":["mcpResources","enrollmentGuide","openapi"]},"httpAccessForAgents":{"type":"object","description":"REST paths for curl, shell, and agents without an MCP Streamable HTTP client.","properties":{"discovery":{"type":"string"},"list":{"type":"string"},"getByUri":{"type":"string"},"health":{"type":"string"},"examples":{"type":"array","items":{"type":"string"}}}},"doNotUseRawHttpGetOn":{"type":"string","description":"Transport path that returns MCP_TRANSPORT_SESSION_REQUIRED when probed with curl or bare GET."},"transportNote":{"type":"string","description":"Explains that Mcp-Session-Id is transport-scoped, not API login."},"requestId":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}},"required":["title","transport","resources","tools","registration","docs","requestId","timestamp"]}}}}},"operationId":"get_well_known_mcp"}},"/openapi.json":{"get":{"summary":"Canonical OpenAPI specification","description":"Returns the canonical OpenAPI specification for this API. Use this endpoint for schema-driven tooling and client generation.","tags":["Discovery"],"responses":{"200":{"description":"OpenAPI specification","content":{"application/json":{"schema":{"type":"object","description":"OpenAPI 3.0 specification"}}}},"500":{"description":"Failed to load OpenAPI specification","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_openapi_json"}},"/api/v1/openapi.json":{"get":{"summary":"Legacy OpenAPI alias","description":"Deprecated compatibility endpoint. Redirects to `/openapi.json`.","deprecated":true,"tags":["Discovery"],"responses":{"301":{"description":"Redirect to /openapi.json"}},"operationId":"get_api_v1_openapi_json"}},"/docs/enrollment":{"get":{"summary":"Removed enrollment docs endpoint","description":"This endpoint has been removed and always returns `410 ENDPOINT_REMOVED`.","deprecated":true,"tags":["Discovery"],"responses":{"410":{"description":"Endpoint removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_docs_enrollment_removed"}},"/api/holanonce16ts":{"get":{"summary":"Get timestamp and nonce for HOLA handshake","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Returns a timestamp and nonce hex that can be directly concatenated into HOLA handshake messages.","content":{"application/json":{"schema":{"type":"object","properties":{"noncetsHex":{"type":"string","description":"32 uppercase hex characters (16 bytes) for the nonce component of HOLA handshake. This is the hex-encoded random nonce, already normalized to uppercase. Use this value directly when constructing HOLA messages. Do not attempt to parse or extract from a combined format."},"timestamp":{"type":"string","format":"date-time","description":"Server ISO-8601 timestamp when the nonce was generated."},"length":{"type":"integer","description":"Number of random bytes used to generate the nonce (always 16)."},"algorithm":{"type":"string","description":"Algorithm used to generate the nonce."},"requestId":{"type":"string","description":"Request correlation identifier."}},"required":["noncetsHex","timestamp","requestId"]}}}},"401":{"description":"Unauthorized - JWT token is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Agent discovery failed due to upstream service errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"discoveryFailed":{"summary":"Public agent listing failed","value":{"error":{"code":"AGENT_DISCOVERY_FAILED","message":"Unable to fetch agent list"},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-04-15T08:21:45.000Z"}}}}}}},"operationId":"get_api_holanonce16ts"}},"/api/login/timestamp":{"get":{"summary":"Get timestamp for AI agent login","description":"Public endpoint for AI agents to obtain synchronized timestamp and ISO string needed to construct /api/login requests. Both values are generated from the same moment to ensure signature verification succeeds.","tags":["Authentication"],"responses":{"200":{"description":"Authentication parameters for constructing login requests.","content":{"application/json":{"schema":{"type":"object","properties":{"timestamp":{"type":"integer","description":"Unix timestamp in seconds for /api/login payload."},"timestamp_iso":{"type":"string","format":"date-time","description":"ISO 8601 timestamp for signing the login message."},"requestId":{"type":"string","description":"Request correlation identifier."}},"required":["timestamp","timestamp_iso","requestId"]}}}},"429":{"description":"Too Many Requests - public route rate limit exceeded at the nginx edge (1101 requests per minute per `$remote_addr` sustained, burst 6101). Limits apply only to public URIs listed in nginx `public_rate_limit_key`; see `info.x-publicRateLimit`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"rateLimit":{"summary":"Rate limit exceeded","value":{"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Exceeded anonymous auth-params rate limit"},"requestId":"01HX9WZ7F7B5DDXPKRZC4J8M0X","timestamp":"2026-04-15T08:20:00.000Z"}}}}}},"500":{"description":"Failed to generate authentication parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"generationError":{"summary":"Entropy generator failure","value":{"error":{"code":"AGENT_AUTH_PARAMS_FAILED","message":"Random generator unavailable"},"requestId":"01HX9WZ8TF5X1Q4P3Q2D0A9RZT","timestamp":"2026-04-15T08:20:02.000Z"}}}}}}},"operationId":"get_api_login_timestamp"}},"/api/agents":{"get":{"summary":"List RODiT token holders with facial descriptions","description":"Returns a paginated list of all RODiT token holders with their facial descriptions decoded from token IDs. This is a public endpoint for agent discovery. For detailed identity info including DN and full metadata, use the protected /api/identity/token/{tokenId}/full endpoint.\n\n**Query parameters**: Only **`limit`** and **`cursor`** are supported. Extra parameters (e.g. `owner`) are **ignored** — do not use this endpoint to look up “your” token by NEAR account; save the **`token_id`** returned when the RODiT was minted.\n\n**Current Capabilities**: Browse agents using pagination (`limit` and `cursor`).\n\n**Planned Features**: Search and owner filtering may be added later.\n\n⚠️ **DISCLAIMER**: The creature field and other agent metadata are self-declared by the agent. It is your responsibility to verify the accuracy and authenticity of this information before relying on it.","tags":["Agent"],"parameters":[{"name":"limit","in":"query","description":"Maximum number of agents to return (default: 20, max: 100)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","description":"Pagination cursor for fetching the next page","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of agents with facial descriptions","content":{"application/json":{"schema":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"object","properties":{"tokenId":{"type":"string","description":"The 12-character facial token ID"},"creature":{"type":"string","nullable":true,"description":"Creature/profession/species from the agent's DN metadata"},"face":{"type":"object","nullable":true,"description":"Facial description decoded from tokenId","properties":{"checksumValid":{"type":"boolean","description":"Whether the token ID checksum is valid"},"categories":{"type":"object","description":"Facial feature categories with their values"}}}}}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for fetching the next page, null if no more results"},"requestId":{"type":"string","description":"Request correlation identifier"},"disclaimer":{"type":"string","description":"Warning that creature field and agent metadata are self-declared and should be verified"}},"required":["agents","requestId","disclaimer"]}}}},"400":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Failed to retrieve agents","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_agents"}},"/api/me/identity":{"get":{"summary":"Get authenticated agent's own DN and facial description","description":"Returns the caller's own Distinguished Name (DN) and facial description based on their authenticated RODiT token. This endpoint is for self-identification only - it derives the caller's tokenId from the JWT sub field. For looking up other agents, use GET /api/identity/token/{tokenId}/full.","tags":["Identity"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Authenticated agent's own DN and facial description.","content":{"application/json":{"schema":{"type":"object","properties":{"tokenId":{"$ref":"#/components/schemas/TokenId"},"dn":{"type":"object","nullable":true,"description":"Parsed Distinguished Name information from token metadata."},"face":{"type":"object","nullable":true,"description":"Facial description decoded from tokenId."},"notAfter":{"type":"string","nullable":true,"description":"Token expiration timestamp (ISO8601 format). Null if token has no expiration."},"metadata":{"type":"object","nullable":true,"description":"Full token metadata including openapijson_url, not_after, not_before, max_requests, webhook_url, userselected_dn, and userselected_dn_info."},"requestId":{"type":"string"}},"required":["tokenId","dn","face","notAfter","metadata","requestId"]}}}},"400":{"description":"IdentityUnavailable when sub is missing or unparsable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missingSub":{"summary":"JWT sub missing","value":{"error":{"code":"IDENTITY_SUB_MISSING","message":"JWT payload is missing required sub field for identity derivation"},"requestId":"01HX7HAGTDEJQ8G2KJ8JHB6Q1F","timestamp":"2026-04-15T07:45:00.000Z"}}}}}},"404":{"description":"IdentityNotFound when NEAR token does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missingToken":{"summary":"Token missing","value":{"error":{"code":"IDENTITY_NOT_FOUND","message":"RODiT identity not found","details":{"tokenId":"pkcnjdbdefcp"}},"requestId":"01HX7HANWC0VCG6F1D2Z1KQ7W4","timestamp":"2026-04-15T07:46:00.000Z"}}}}}},"500":{"description":"Identity lookup failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_me_identity"}},"/api/identity/verify":{"post":{"summary":"Verify a peer's HOLA using Ed25519 and a Morse-compatible canonical message","description":"Peer-verification endpoint for HOLA messages received from another agent. Use `/api/identity/verify` for peer verification; use `/api/testhola` for self-testing your own outbound HOLA generation.\n\n**Fail-closed early validation (HTTP 400):** Uses the same format/checksum parser as `/api/testhola`. Rejects before peer signature/token checks when:\n- Payload envelope is invalid (`hola` must be a string, not a nested JSON object; `reasonCode: payload_is_json_object`)\n- Transport-shape lint fails (`unix_millis_timestamp`, `signature_not_base32`, `placeholder_signature`)\n- HOLA line format or checksum is invalid (`stage: format_checksum_and_payload_validation`, `reasonCode: invalid_format` or `checksum_invalid`)\n\nAll early failures return `400` with `code: HOLA_VALIDATION_FAILED` and `error.details.reasonCode` (same contract as `/api/testhola`).\n\n**Peer verification outcome (HTTP 200):** When format and checksum pass, returns `verified` plus per-check booleans. Timestamp freshness, nonce replay, token existence/activity, and Ed25519 signature outcomes are reported on the 200 body via `failureReasons` (e.g. `timestamp_stale_or_future`, `nonce_replay`, `token_missing`, `signature_invalid`) — not as `invalid_format` or `checksum_invalid`.\n\nFor self-correction workflows and richer diagnostics/examples, validate sender-side payloads with `/api/testhola`.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hola":{"type":"string","maxLength":512,"description":"Request field containing the HOLA handshake line exchanged between AI agents. CASE-INSENSITIVE: The protocol accepts any case (e.g., 'hola', 'HOLA', 'Hola'). For verification, the server canonicalizes the full signed payload to uppercase before Ed25519 and checksum validation. Token IDs are then normalized to lowercase only for blockchain lookup. STANDARD FORMAT: HOLA/<recipient>/<tokenId>/<ISO8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/<base32-ed25519-signature>/<checksum>. SUBAGENT FORMAT (delegated signers): HOLA/<recipient>/<delegateID>/<issuer_tokenId>/<publicKey-base32>/<ISO8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/<base32-ed25519-signature>/<checksum>. Base32 variant is RFC 4648 Base32 with alphabet A-Z2-7; uppercase canonical form is used and '=' padding is optional on input. For standard format, the Ed25519 signature is computed over HOLA/<recipient>/<tokenId>/<ISO8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/. For subagent format, the signature is computed over HOLA/<recipient>/<delegateID>/<issuer_tokenId>/<publicKey-base32>/<ISO8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/. The checksum is a single letter from ABCDEFGHJKMNPQRSTUVWXYZ (23 letters; omits I, L, O). Checksum algorithm: sum UTF-16 code units (JavaScript String semantics; ASCII-only prefixes match byte sums) over HOLA/<recipient>/.../<base32-ed25519-signature>/ including trailing slash (everything before this checksum field), modulo 23, index into that alphabet (NOT MD5/SHA). Recipient defaults to MUNDO if not specified. Note: noncets-hex is 32 uppercase hex characters from /api/holanonce16ts (hex-encoded, uppercase). delegateID is the hashOrDelegateId from /api/isauthorizedsigner (DID, name, or BLAKE3 hash). Maximum length: 512 characters. See /api/isauthorizedsigner for subagent authorization validation.","example":"HOLA/MUNDO/aaaaaaaaaaaa/2026-04-04T10:10:00Z/4F9A3C7E2D1B9A4C8E7F6A5B4C3D2E1F/API.IDENTYCLAW.COM/N3FZ5KQ8LH2BSM1XY/C"},"constraints":{"type":"object","properties":{"maxAgeMs":{"type":"integer","description":"Maximum allowed age in milliseconds for the hola. Defaults to 300000."}}}},"required":["hola"]}}}},"responses":{"200":{"description":"Verification result with detailed checks.","content":{"application/json":{"schema":{"type":"object","properties":{"verified":{"type":"boolean","description":"True when all checks pass and the peer hola is accepted."},"peerTokenId":{"type":"string","description":"token_id extracted from the peer hola. For subagent format, this is the issuerTokenId."},"destinatary":{"type":"string","description":"Recipient field extracted from the HOLA message."},"isSubagentFormat":{"type":"boolean","description":"True when the HOLA message uses the subagent format (11 fields) instead of standard format (8 fields). Only present when true."},"delegateId":{"type":"string","description":"The delegateID from the subagent HOLA message. Only present when isSubagentFormat is true."},"issuerTokenId":{"type":"string","description":"The parent agent's token ID who authorized this subagent. Only present when isSubagentFormat is true."},"checks":{"type":"object","description":"Detailed boolean flags indicating which verification stages passed.","properties":{"tokenExists":{"type":"boolean","description":"True when the peer token exists on NEAR."},"tokenActive":{"type":"boolean","description":"True when the token is considered active for verification. Currently false when token is expired based on metadata.not_after."},"timestampFresh":{"type":"boolean","description":"True when the timestamp is within the accepted age window."},"nonceReplaySafe":{"type":"boolean","description":"True when the nonce has not already been accepted for the same sender token within the active timestamp window."},"signatureValid":{"type":"boolean","description":"True when the Ed25519 signature matches the canonical message."},"checksumValid":{"type":"boolean","description":"True when the checksum matches the canonical hola payload. Always true on HTTP 200; checksum mismatch returns HTTP 400 with reasonCode checksum_invalid before this response."}},"required":["tokenExists","tokenActive","timestampFresh","nonceReplaySafe","signatureValid","checksumValid"]},"failureReasons":{"type":"array","description":"List of machine-readable codes describing why peer verification failed (timestamp, nonce, token, signature). Empty when verified is true. Format and checksum faults are not listed here; they fail closed with HTTP 400.","items":{"type":"string","enum":["timestamp_stale_or_future","nonce_replay","token_missing","token_expired","signature_invalid","public_key_unavailable","public_key_error","subagent_public_key_invalid_length","subagent_public_key_decode_failed"]}},"failureDetails":{"type":"array","description":"Human-readable descriptions for each failureReasons entry.","items":{"type":"object","properties":{"reasonCode":{"type":"string"},"description":{"type":"string"}},"required":["reasonCode","description"]}},"signatureVerificationImplemented":{"type":"boolean","description":"Signals that signature verification is active for this server implementation."},"warnings":{"type":"array","description":"Optional warning entries for suspicious but non-fatal conditions such as recipient mismatch.","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"requestId":{"type":"string","description":"Request correlation identifier."}},"required":["verified","peerTokenId","checks","failureReasons","failureDetails","signatureVerificationImplemented","requestId"]}}}},"400":{"description":"Fail-closed HOLA validation failure: payload envelope, transport-shape lint, invalid format, bad checksum, invalid nonce hex, or invalid constraints. Returns error.details.stage and error.details.reasonCode (aligned with /api/testhola for format/checksum). Timestamp-only faults after parsing may use HOLA_TIMESTAMP_INVALID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalidFormat":{"summary":"Invalid HOLA format (non-HOLA string)","value":{"error":{"code":"HOLA_VALIDATION_FAILED","message":"Unsupported protocol; expected HOLA prefix","details":{"valid":false,"stage":"format_checksum_and_payload_validation","reasonCode":"invalid_format","checks":{"formatValid":false,"checksumValid":false,"timestampValid":false,"noncetsValid":false},"selfTest":"POST /api/testhola for full diagnostics, documentation links, and corrected examples."}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-05-21T07:50:53.322Z"}},"checksumInvalid":{"summary":"Checksum invalid","value":{"error":{"code":"HOLA_VALIDATION_FAILED","message":"Invalid checksum: expected A, got B","details":{"valid":false,"stage":"format_checksum_and_payload_validation","reasonCode":"checksum_invalid","checks":{"formatValid":true,"checksumValid":false,"timestampValid":true,"noncetsValid":true},"selfTest":"POST /api/testhola for full diagnostics, documentation links, and corrected examples."}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-05-21T07:50:53.322Z"}},"payloadEnvelope":{"summary":"Payload envelope (hola must be a string)","value":{"error":{"code":"HOLA_VALIDATION_FAILED","message":"Property \"hola\" must be a string containing the full HOLA line, not a JSON object.","details":{"reasonCode":"payload_is_json_object","stage":"payload_envelope_validation","field":"hola","acceptedFields":["hola"],"preferredField":"hola"}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-05-21T07:50:53.322Z"}}}}}},"415":{"description":"UnsupportedMediaType - Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_identity_verify"}},"/api/isauthorizedsigner":{"post":{"summary":"Verify if a public key is authorized to sign on behalf of an IdentyClaw Passport holder","description":"Validates whether a given Ed25519 public key has been authorized by an IdentyClaw Passport holder to act as a delegated signer. This is the primary mechanism for subagent authorization in OpenClaw. Subagents generate their own Ed25519 keypairs and request authorization from their parent agent. The parent signs the delegation record (tokenId:delegateId:timestamp:publicKey) using its RODiT private key, which is then verified by this endpoint. This enables hierarchical agent organizations where subagents can prove they're authorized by a specific parent without needing their own RODiT tokens.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tokenId":{"type":"string","pattern":"^[a-z]{12}$","description":"The IdentyClaw Passport token ID (12 lowercase letters) of the passport holder (owner) authorizing the signer. The endpoint retrieves this owner's public key from NEAR blockchain to verify the signature."},"base64HashOrDelegateSignerId":{"type":"string","minLength":1,"maxLength":128,"description":"Unique identifier or hash for the delegated signer being authorized. Can be a DID, name, BLAKE3 hash, or any text identifier (1-128 characters). Used to identify which specific delegated signer is being authorized. If using a hash, canonicalization is the agent's responsibility."},"unixTimestamp":{"type":"integer","minimum":0,"description":"Unix timestamp (seconds since epoch) when the authorization was granted. Used to prevent replay attacks and establish temporal context."},"publicKey":{"type":"string","description":"Base64url-encoded Ed25519 public key (32 bytes when decoded) of the delegated signer (the entity being authorized to sign on behalf of the tokenId owner). This is the key we are validating as authorized."},"signature":{"type":"string","description":"Base64url-encoded Ed25519 signature (64 bytes when decoded) created by the tokenId owner's private key, proving they authorized this delegated signer's public key. The signature is computed over the message: tokenId:base64HashOrDelegateSignerId:unixTimestamp:publicKey"}},"required":["tokenId","base64HashOrDelegateSignerId","unixTimestamp","publicKey","signature"]}}}},"responses":{"200":{"description":"Authorization check completed. Returns detailed verification results regardless of authorization status.","content":{"application/json":{"schema":{"type":"object","properties":{"authorized":{"type":"boolean","description":"True if the public key is authorized by the passport holder; false otherwise."},"tokenId":{"type":"string","description":"The token ID that was checked."},"base64HashOrDelegateSignerId":{"type":"string","description":"The hash or delegated signer ID that was checked."},"checks":{"type":"object","description":"Detailed boolean flags indicating which verification stages passed.","properties":{"tokenExists":{"type":"boolean","description":"True when the passport token exists on NEAR."},"tokenActive":{"type":"boolean","description":"True when the token is considered active (not revoked/expired). NOTE: Currently a planned feature - returns true if token exists. Full implementation pending contract metadata support."},"publicKeyAuthorized":{"type":"boolean","description":"True when the Ed25519 signature is valid and proves the public key is authorized by the passport holder."}},"required":["tokenExists","tokenActive","publicKeyAuthorized"]},"failureReasons":{"type":"array","description":"List of machine-readable codes describing why authorization failed. Empty when authorized is true.","items":{"type":"string","enum":["token_owner_missing","owner_public_key_unavailable","signature_missing_or_invalid","signature_verification_failed","public_key_error"]}},"requestId":{"type":"string","description":"Request correlation identifier."}},"required":["authorized","tokenId","checks","failureReasons","requestId"]}}}},"400":{"description":"Invalid request - missing or malformed parameters (tokenId, unixTimestamp, publicKey, or signature).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - JWT token is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"NotFound - The specified tokenId does not exist on the blockchain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"UnsupportedMediaType - Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_isauthorizedsigner"}},"/api/login":{"post":{"summary":"RODiT client login to obtain a JWT access token","description":"Delegates to `RoditClient#login_client` (@rodit/rodit-auth-be): Express handler that reads `req.body` after JSON parsing. Signed payload is `identifier + timestamp_iso` (concatenated, no separator) where `identifier` is the chosen `roditid` or `accountid`. Server must have own RODiT config initialized (`getConfigOwnRodit`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","description":"Login with `roditid`; signature in `base64url_signature`.","required":["roditid","base64url_signature"],"properties":{"roditid":{"type":"string","description":"12-letter on-chain `token_id`; must match the identifier used when signing."},"timestamp":{"type":"integer","description":"Unix seconds from the same GET /api/login/timestamp challenge pair used to build the signing payload."},"base64url_signature":{"type":"string","description":"Ed25519 signature (base64url) over UTF-8 `roditid + timestamp_iso`."}},"additionalProperties":false},{"type":"object","description":"Login with `roditid`; signature alias `roditid_base64url_signature` (wire-compatible with login_server).","required":["roditid","roditid_base64url_signature"],"properties":{"roditid":{"type":"string","description":"12-letter on-chain `token_id`."},"timestamp":{"type":"integer","description":"Unix seconds from the same GET /api/login/timestamp challenge pair used to build the signing payload."},"roditid_base64url_signature":{"type":"string","description":"Same bytes as `base64url_signature`; do not send both signature fields non-empty."}},"additionalProperties":false},{"type":"object","description":"Login with NEAR implicit `accountid` (64-char hex); signature in `base64url_signature`.","required":["accountid","base64url_signature"],"properties":{"accountid":{"type":"string","description":"64-char lowercase hex NEAR implicit account id; signed message is `accountid + timestamp_iso`."},"timestamp":{"type":"integer","description":"Unix seconds from the same GET /api/login/timestamp challenge pair used to build the signing payload."},"base64url_signature":{"type":"string","description":"Ed25519 signature (base64url) over UTF-8 `accountid + timestamp_iso`."}},"additionalProperties":false},{"type":"object","description":"Login with NEAR implicit `accountid`; signature alias `roditid_base64url_signature`.","required":["accountid","roditid_base64url_signature"],"properties":{"accountid":{"type":"string","description":"64-char lowercase hex NEAR implicit account id."},"timestamp":{"type":"integer","description":"Unix seconds from the same GET /api/login/timestamp challenge pair used to build the signing payload."},"roditid_base64url_signature":{"type":"string","description":"Same bytes as `base64url_signature`; do not send both signature fields non-empty."}},"additionalProperties":false}],"description":"Exactly one of `roditid` or `accountid` after trim (not both, not neither). Exactly one of `base64url_signature` or `roditid_base64url_signature`. Provide exactly one timestamp format (`timestamp` or `timestamp_iso`) from the same GET /api/login/timestamp challenge pair used for signing. Deprecated keys `signature`, `account_id` → 400 `LOGIN_PAYLOAD_DEPRECATED`. Ambiguous timestamp/identifier/signature fields → 400 with codes such as `LOGIN_TIMESTAMP_AMBIGUOUS`, `INVALID_LOGIN_TIMESTAMP`, `LOGIN_IDENTIFIER_AMBIGUOUS`, `MISSING_LOGIN_IDENTIFIER`, `MISSING_BASE64URL_SIGNATURE`."}}}},"responses":{"200":{"description":"Login successful. Body includes `jwt_token` and `requestId`; header `New-Token` repeats the JWT.","headers":{"New-Token":{"description":"Same JWT string as `jwt_token` in the response body.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["jwt_token","requestId"],"properties":{"jwt_token":{"type":"string","description":"Bearer JWT for subsequent API calls."},"requestId":{"type":"string","description":"Request correlation id from the auth layer."}},"additionalProperties":true}}}},"400":{"description":"Validation error (payload shape, ambiguous identifiers, deprecated keys, etc.).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Credential verification failed when `SECURITY_OPTIONS.SILENT_LOGIN_FAILURES` is false. Typical `error.code` values include `LOGIN_CHALLENGE_TIMESTAMP_INVALID` (login challenge Unix `timestamp` too far in the future vs server), `LOGIN_BASE64URL_SIGNATURE_INVALID` (base64url Ed25519 over UTF-8 identifier + canonical `timestamp_iso` from GET /api/login/timestamp did not verify), `RODIT_NOT_FOUND`, `RODIT_MISSING_METADATA`, `RODIT_NOT_LIVE`, `RODIT_REVOKED`, `RODIT_FAMILY_MISMATCH`, `SMART_CONTRACT_NOT_TRUSTED`, `SERVER_CONFIG_INCOMPLETE`, `LOGIN_MODE_POLICY_REJECTED_*`, `LOGIN_ERROR`, `INVALID_CREDENTIALS`. Response uses unified `error` object with optional `error.details.failureReason` mirroring `error.code`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"loginChallengeTimestamp":{"summary":"Login challenge timestamp invalid","value":{"error":{"code":"LOGIN_CHALLENGE_TIMESTAMP_INVALID","message":"Error 102: Login attempt failed: Login challenge timestamp invalid: the Unix `timestamp` from your POST body is too far in the future relative to server time (use the `timestamp` from the same GET /api/login/timestamp response as your login signing payload; check clock skew).","details":{"failureReason":"LOGIN_CHALLENGE_TIMESTAMP_INVALID","failureMessage":"Login challenge timestamp invalid: the Unix `timestamp` from your POST body is too far in the future relative to server time (use the `timestamp` from the same GET /api/login/timestamp response as your login signing payload; check clock skew)."}},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-05-10T12:00:00.000Z"}},"loginBase64urlSignature":{"summary":"Login base64url signature invalid","value":{"error":{"code":"LOGIN_BASE64URL_SIGNATURE_INVALID","message":"Error 102: Login attempt failed: Login base64url signature invalid: Ed25519 verification failed for the base64url_signature over UTF-8 (roditid or accountid) + canonical timestamp_iso from the login challenge (GET /api/login/timestamp). Wrong key, wrong payload, or wrong encoding (must be base64url, not standard base64).","details":{"failureReason":"LOGIN_BASE64URL_SIGNATURE_INVALID","failureMessage":"Login base64url signature invalid: Ed25519 verification failed for the base64url_signature over UTF-8 (roditid or accountid) + canonical timestamp_iso from the login challenge (GET /api/login/timestamp). Wrong key, wrong payload, or wrong encoding (must be base64url, not standard base64)."}},"requestId":"01HX9X0T9CS1EM0WQ7R6F5B2VY","timestamp":"2026-05-10T12:00:00.000Z"}}}}}},"415":{"description":"UnsupportedMediaType - Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error during login.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Authentication service unavailable (e.g. RoditClient not initialized on this instance).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_login"}},"/api/logout":{"post":{"summary":"RODiT client logout to invalidate the current session","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Logout successful; response body is produced by the RODiT SDK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"415":{"description":"UnsupportedMediaType - Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Logout failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Authentication service unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_logout"}},"/api/signclient":{"post":{"summary":"Request minting of a client RODiT via SignPortal","description":"Frontend calls this endpoint with tamperproofed values and a locally computed minting fee. The server validates permissions against its own permissioned_routes, recomputes the minting fee, and then delegates to SignPortal to mint a new client RODiT token. Optionally, caller can specify a token_id to use for signing; SignPortal will validate the token.\n\n**Errors:** Validation failures use `SIGNCLIENT_*` codes with client-safe `details` where helpful. HTTP 500 responses use generic messages (`SIGNCLIENT_FAILED`); upstream/SignPortal detail is logged with `requestId`, not returned in the body.","parameters":[{"$ref":"#/components/parameters/XRequestId"},{"$ref":"#/components/parameters/XCorrelationId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tobesignedValues":{"type":"object","description":"Tamperproof payload describing the client RODiT to mint (includes permissioned_routes, max_requests, maxrq_window, not_after, serviceprovider_signature, etc.)."},"mintingfee":{"type":"string","description":"Total minting fee computed by the client; must match the server's own calculation or the request will be rejected."},"token_id":{"type":"string","pattern":"^[bcdfhjkmnpqrtvwxy]{12}$","description":"Optional: Caller-provided token ID (12 characters from safe charset). If not provided, SignPortal generates one. Must be valid facial encoding with correct checksum."},"tier":{"type":"string","enum":["personal","enterprise","collectible"],"description":"Optional: Pricing tier for fee calculation. If provided, the server uses tier-specific rate limits and fee structures instead of the formula-based calculation. Tiers: personal (48 requests/60s, variable fee), enterprise (4,999 requests/60s, 1,806 NEAR/year), collectible (496 requests/60s, 1,000 NEAR one-time, immortal)."}},"required":["tobesignedValues","mintingfee"]}}}},"responses":{"200":{"description":"Client RODiT minted successfully; response body is the SignPortal result including token_id, signatures, and all token metadata.","content":{"application/json":{"schema":{"type":"object","properties":{"fee_data":{"type":"object","properties":{"token_id":{"type":"string"},"mintingfee":{"type":"string"},"mintingfeeaccount":{"type":"string"}},"required":["token_id","mintingfee","mintingfeeaccount"]}},"required":["fee_data"],"additionalProperties":true}}}},"201":{"description":"Client RODiT minted successfully. Response includes SignPortal result (token_id, signatures, metadata) plus fee_data object containing the exact fee values that were signed (token_id, mintingfee, mintingfeeaccount). Frontend uses fee_data to reconstruct and verify the fee_signature_base64url.","content":{"application/json":{"schema":{"type":"object","properties":{"fee_data":{"type":"object","properties":{"token_id":{"type":"string"},"mintingfee":{"type":"string"},"mintingfeeaccount":{"type":"string"}},"required":["token_id","mintingfee","mintingfeeaccount"]}},"required":["fee_data"],"additionalProperties":true}}}},"400":{"description":"Validation error (`SIGNCLIENT_*`), including missing fields, invalid permissioned_routes, fee mismatch (`SIGNCLIENT_FEE_MISMATCH`), fee params (`SIGNCLIENT_FEE_PARAMS_INVALID`), or invalid token_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"feeMismatch":{"$ref":"#/components/examples/ErrorSignclientFeeMismatch"}}}}},"401":{"description":"Unauthorized - SignPortal authentication failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Privileged operations cannot be granted to client tokens (`SIGNCLIENT_PRIVILEGED_OPERATIONS_DENIED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"UnsupportedMediaType - Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server or SignPortal error while attempting to mint the client token (`SIGNCLIENT_FAILED` or `SIGNCLIENT_INVALID_REQUEST` for validation-shaped failures). Response message is generic; use `requestId` in logs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"signclientFailed":{"$ref":"#/components/examples/ErrorSignclientFailed"}}}}},"502":{"description":"Nginx edge: application upstream unavailable (`UPSTREAM_UNAVAILABLE`). See `info.x-edgeGateway`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"upstreamUnavailable":{"$ref":"#/components/examples/ErrorEdgeUpstreamUnavailable"}}}}}},"operationId":"post_api_signclient"}},"/api/mcp/resources":{"get":{"summary":"List available MCP resources (including OpenAPI schema)","description":"Returns a list of all available MCP resources including: OpenAPI schema, skills documentation, enrollment guide, troubleshooting guide, value proposition (why-IdentyClaw), policy documents, NEAR onboarding guide, and JSON-LD contexts. Use /api/mcp/resource/{uri} to retrieve specific resources.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of available MCP resources.","content":{"application/json":{"schema":{"type":"object","properties":{"resources":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"}},"required":["uri","name","type"]}},"nextCursor":{"type":"string","nullable":true},"requestId":{"type":"string"}},"required":["resources","requestId"]}}}},"400":{"description":"Invalid parameter - limit must be a numeric value.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Failed to list resources (`MCP_LIST_FAILED`). Generic message only; use `requestId` in logs for detail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"listFailed":{"$ref":"#/components/examples/ErrorMcpListFailed"}}}}}},"operationId":"get_api_mcp_resources"}},"/api/mcp/resource/{uri}":{"get":{"summary":"Retrieve a specific MCP resource by URI","description":"Fetches a specific resource by its URI. Available resources include: openapi:swagger, config:default, skills:skills, readme:main, health:status (local health check), guide:api, guide:enrollment, guide:why-IdentyClaw, guide:troubleshooting, guide:subagents, policy:terms, policy:privacy, policy:data-retention, policy:service-info, onboarding:near, jsonld:context, jsonld:contract-metadata, did:resolve:{tokenId}, token:facial-categories.","parameters":[{"name":"uri","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Requested resource returned.","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string"},"content":{},"requestId":{"type":"string"}},"required":["type","content","requestId"]}}}},"404":{"description":"Resource not found (`MCP_RESOURCE_NOT_FOUND`). `details.uri` identifies the requested resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"notFound":{"$ref":"#/components/examples/ErrorMcpResourceNotFound"}}}}},"500":{"description":"Failed to get resource (`MCP_RESOURCE_FAILED`). Generic message only; use `requestId` in logs (not a 404).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_mcp_resource_by_uri"}},"/api/mcp/schema":{"get":{"summary":"Return the OpenAPI schema used by this server","description":"Returns the OpenAPI 3.0 schema directly with required fields: openapi, info, paths, and components.","responses":{"200":{"description":"OpenAPI 3.0 schema document.","content":{"application/json":{"schema":{"type":"object","required":["openapi","info","paths","components"],"properties":{"openapi":{"type":"string"},"info":{"type":"object"},"paths":{"type":"object"},"components":{"type":"object"}}}}}}},"operationId":"get_api_mcp_schema"}},"/api/metrics":{"get":{"summary":"Get current performance metrics for this IDClawserver instance (admin only)","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Aggregated request and session metrics, including request counts and active sessions.","content":{"application/json":{"schema":{"type":"object","properties":{"requestCount":{"type":"integer"},"errorCount":{"type":"integer"},"requestsPerMinute":{"type":"number"},"currentLoadLevel":{"type":"string"},"requests":{"type":"object","properties":{"total":{"type":"integer"},"errors":{"type":"integer"},"perMinute":{"type":"number"}},"required":["total","errors","perMinute"]},"sessions":{"type":"object","properties":{"active":{"type":"integer"},"active_count":{"type":"integer"},"total":{"type":"integer"}},"required":["active","active_count","total"]},"active":{"type":"integer"},"endpointMetrics":{"type":"object","additionalProperties":true},"metrics":{"type":"object","additionalProperties":true},"timestamp":{"type":"string","format":"date-time"},"requestId":{"type":"string"},"sessions.active":{"type":"integer"},"sessions.active_count":{"type":"integer"},"sessionDebug":{"type":"object","additionalProperties":true}},"required":["requestId","timestamp","requests","sessions","active"]}}}},"401":{"description":"Unauthorized - JWT token is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Failed to retrieve metrics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_metrics"}},"/api/metrics/system":{"get":{"summary":"Get system resource metrics (CPU, memory, etc.) (admin only)","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Current system metrics as reported by the performance service.","content":{"application/json":{"schema":{"type":"object","properties":{"metrics":{"type":"object","additionalProperties":true},"timestamp":{"type":"integer","description":"Unix timestamp in milliseconds."},"requestId":{"type":"string"}},"required":["metrics","timestamp","requestId"]}}}},"401":{"description":"Unauthorized - JWT token is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Failed to retrieve system metrics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_metrics_system"}},"/api/metrics/reset":{"post":{"summary":"Reset performance metrics counters (admin only)","description":"Resets in-memory performance counters such as request counts and error counts. Caller must have appropriate admin permissions via RODiT permissioned_routes.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Metrics reset successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"timestamp":{"type":"integer","description":"Unix timestamp in milliseconds."},"requestId":{"type":"string"}},"required":["message","timestamp","requestId"]}}}},"403":{"description":"Permission denied (admin permission required).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"UnsupportedMediaType - Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Failed to reset metrics.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_metrics_reset"}},"/api/metrics/debug":{"get":{"summary":"Debug endpoint for metrics subsystem (admin only)","description":"Returns a snapshot of performance metrics and client information for debugging.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Debug information for metrics subsystem.","content":{"application/json":{"schema":{"type":"object","properties":{"debug":{"type":"object","properties":{"hasRoditClient":{"type":"boolean"},"clientType":{"type":"string"},"hasPerformanceService":{"type":"boolean"},"performanceServiceType":{"type":"string"},"metricsSnapshot":{"type":"object","additionalProperties":true},"timestamp":{"type":"integer","description":"Unix timestamp in milliseconds."},"requestProcessingTime":{"type":"integer"}},"required":["hasRoditClient","clientType","hasPerformanceService","performanceServiceType","metricsSnapshot","timestamp","requestProcessingTime"]},"requestId":{"type":"string"}},"required":["debug","requestId"]}}}},"401":{"description":"Unauthorized - JWT token is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Failed to retrieve debug information.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_metrics_debug"}},"/api/sessions/list_all":{"get":{"summary":"List all active sessions (admin)","description":"Returns a list of active sessions managed by the RODiT SessionManager. Intended for administrative tooling.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"List of active sessions with basic metadata.","content":{"application/json":{"schema":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"roditId":{"type":"string"},"ownerId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"lastAccessedAt":{"type":"string","format":"date-time"},"status":{"type":"string"}},"required":["id","roditId","ownerId","createdAt","expiresAt","lastAccessedAt","status"]}},"count":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"}},"required":["sessions","count","timestamp"]}}}},"401":{"description":"Unauthorized - JWT token is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Failed to retrieve sessions (`SESSIONS_RETRIEVE_FAILED`). Generic message only; use `requestId` in logs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"retrieveFailed":{"$ref":"#/components/examples/ErrorSessionsRetrieveFailed"}}}}}},"operationId":"get_api_sessions_list_all"}},"/api/sessions/cleanup":{"post":{"summary":"Cleanup expired sessions (admin)","description":"Triggers cleanup of expired sessions and returns cleanup statistics.","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Session cleanup completed with statistics.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"stats":{"type":"object","properties":{"removedCount":{"type":"integer"},"activeSessions":{"type":"integer"},"totalSessions":{"type":"integer"},"cleanupResult":{"type":"object","additionalProperties":true}},"required":["removedCount","activeSessions","totalSessions","cleanupResult"]},"requestId":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}},"required":["success","message","stats","requestId","timestamp"]}}}},"415":{"description":"UnsupportedMediaType - Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Session cleanup failed (`SESSION_CLEANUP_FAILED`). Generic message only; use `requestId` in logs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_sessions_cleanup"}},"/api/sessions/revoke":{"post":{"summary":"Revoke a specific session (admin)","description":"Terminates a specific session by ID. Caller must have permission to this endpoint in their RODiT permissioned_routes.","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"sessionId":{"type":"string","description":"Identifier of the session to revoke."},"reason":{"type":"string","description":"Optional human-readable reason for revocation."}},"required":["sessionId"]}}}},"responses":{"200":{"description":"Session terminated successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"sessionId":{"type":"string"},"reason":{"type":"string"},"timestamp":{"type":"string","format":"date-time"}},"required":["message","sessionId","reason","timestamp"]}}}},"400":{"description":"Missing or invalid sessionId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Session not found or already terminated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"UnsupportedMediaType - Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Failed to terminate session (`SESSION_TERMINATION_FAILED`). Generic message only; `details.sessionId` may be present. Use `requestId` in logs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_sessions_revoke"}},"/mcp":{"get":{"summary":"Streamable MCP transport endpoint","description":"Streamable HTTP endpoint for Model Context Protocol clients. This is the MCP transport path (served from `/mcp`), distinct from the REST discovery/resource endpoints under `/api/mcp/*`.\n\n**Accept Header Requirement**: GET requests MUST include `text/event-stream` in the Accept header (e.g., `Accept: text/event-stream` or `Accept: text/event-stream, application/json`). Requests without this will receive HTTP 406 Not Acceptable.","tags":["MCP"],"parameters":[{"name":"Accept","in":"header","required":true,"schema":{"type":"string","example":"text/event-stream"},"description":"MUST include text/event-stream as a supported content type per MCP Streamable HTTP specification."}],"responses":{"200":{"description":"MCP response payload. Shape depends on the MCP request method and transport exchange."},"400":{"description":"Missing or invalid MCP transport session (`MCP_TRANSPORT_SESSION_REQUIRED`). See `GET /.well-known/mcp` for REST discovery paths.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"406":{"description":"Not Acceptable: Client must include text/event-stream in the Accept header.","content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string","example":"2.0"},"error":{"type":"object","properties":{"code":{"type":"integer","example":-32000},"message":{"type":"string","example":"Not Acceptable: Client must accept text/event-stream"}}},"id":{"type":"null"}}}}}},"415":{"description":"Unsupported Media Type: Invalid Content-Type header."},"426":{"description":"Upgrade Required: Protocol upgrade needed."},"500":{"description":"Server-side MCP transport error."}},"operationId":"get_mcp_streamable_transport"},"post":{"summary":"Streamable MCP transport endpoint","description":"Streamable HTTP endpoint for Model Context Protocol clients. This is the MCP transport path (served from `/mcp`), distinct from the REST discovery/resource endpoints under `/api/mcp/*`.\n\n**Accept Header Requirement**: POST requests MUST include BOTH `application/json` AND `text/event-stream` in the Accept header (e.g., `Accept: application/json, text/event-stream`). Requests without both will receive HTTP 406 Not Acceptable.\n\n**Content-Type Requirement**: POST requests MUST use `Content-Type: application/json`.","tags":["MCP"],"parameters":[{"name":"Accept","in":"header","required":true,"schema":{"type":"string","example":"application/json, text/event-stream"},"description":"MUST include both application/json and text/event-stream as supported content types per MCP Streamable HTTP specification."},{"name":"Content-Type","in":"header","required":true,"schema":{"type":"string","example":"application/json"},"description":"MUST be application/json."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"MCP JSON-RPC request payload"}}}},"responses":{"200":{"description":"MCP response payload. Shape depends on the MCP request method and transport exchange."},"400":{"description":"Invalid MCP request payload or protocol framing."},"406":{"description":"Not Acceptable: Client must include both application/json and text/event-stream in the Accept header.","content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string","example":"2.0"},"error":{"type":"object","properties":{"code":{"type":"integer","example":-32000},"message":{"type":"string","example":"Not Acceptable: Client must accept both application/json and text/event-stream"}}},"id":{"type":"null"}}}}}},"415":{"description":"Unsupported Media Type: Content-Type must be application/json.","content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string","example":"2.0"},"error":{"type":"object","properties":{"code":{"type":"integer","example":-32000},"message":{"type":"string","example":"Unsupported Media Type: Content-Type must be application/json"}}},"id":{"type":"null"}}}}}},"426":{"description":"Upgrade Required: Protocol upgrade needed."},"500":{"description":"Server-side MCP transport error."}},"operationId":"post_mcp_streamable_transport"}},"/api/identity/token/{tokenId}/full":{"get":{"summary":"Get full identity with DN and facial encoding","description":"Returns the combined Distinguished Name (DN) and facial description for a token. Combines data from both /dn and /face endpoints in a single response.\n\n⚠️ **DISCLAIMER**: The DN metadata including creature, name, contact URI, address, and other attributes are self-declared by the agent. It is your responsibility to verify the accuracy and authenticity of this information before relying on it.","tags":["Identity"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"tokenId","in":"path","required":true,"schema":{"type":"string"},"description":"RODiT token identifier."}],"responses":{"200":{"description":"Full identity with DN and facial encoding.","content":{"application/json":{"schema":{"type":"object","properties":{"tokenId":{"type":"string","description":"The requested token ID."},"dn":{"type":"object","nullable":true,"description":"Parsed Distinguished Name information.","properties":{"raw":{"type":"string","description":"Raw DN string from token metadata."},"nameNotSharedWithFamily":{"type":"string","nullable":true},"nameSharedWithFamily":{"type":"string","nullable":true},"displayName":{"type":"string","nullable":true},"contactUri":{"type":"string","nullable":true,"description":"Contact URI in format scheme:authority:identifier"},"taxResidence":{"type":"string","nullable":true},"inceptDateTime":{"type":"string","nullable":true},"inceptPlace":{"type":"string","nullable":true},"taxPayerCode":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"creature":{"type":"string","nullable":true},"avatarUrl":{"type":"string","nullable":true},"emojiUrl":{"type":"string","nullable":true},"allAttributes":{"type":"object","description":"All DN attributes as key-value pairs"}}},"face":{"type":"object","nullable":true,"description":"Facial description decoded from tokenId.","properties":{"checksumValid":{"type":"boolean"},"categories":{"type":"object","properties":{"skin_tones":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"regional_bone_structure":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"face_shape":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"age_related":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"eyes":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"eyebrow_style":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"overall_structure":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"nose":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"lips":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"skin_conditions":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}},"hair_color":{"type":"object","properties":{"value":{"type":"string"},"index":{"type":"integer"},"letter":{"type":"string"}}}}}}},"requestId":{"type":"string","description":"Request correlation identifier."},"disclaimer":{"type":"string","description":"Warning that DN metadata including creature, name, contact URI, and other attributes are self-declared and should be verified"}}}}}},"404":{"description":"Token not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Failed to retrieve identity.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"get_api_identity_token_by_tokenId_full"}},"/.well-known/terms-of-service":{"get":{"summary":"Terms of Service","description":"Returns the Terms of Service document for the IdentyClaw API. Supports content negotiation: text/markdown (default) or text/html for browser viewing.","tags":["Policies"],"responses":{"200":{"description":"Terms of Service document.","content":{"text/markdown":{"schema":{"type":"string"}},"text/html":{"schema":{"type":"string"}}}}},"operationId":"get_well_known_terms_of_service"}},"/.well-known/privacy-policy":{"get":{"summary":"Privacy Policy","description":"Returns the Privacy Policy document describing how user data is collected, used, and protected.","tags":["Policies"],"responses":{"200":{"description":"Privacy Policy document.","content":{"text/markdown":{"schema":{"type":"string"}},"text/html":{"schema":{"type":"string"}}}}},"operationId":"get_well_known_privacy_policy"}},"/.well-known/data-retention":{"get":{"summary":"Data Retention Policy","description":"Returns the Data Retention Policy describing how long different types of data are stored and deletion procedures.","tags":["Policies"],"responses":{"200":{"description":"Data Retention Policy document.","content":{"text/markdown":{"schema":{"type":"string"}},"text/html":{"schema":{"type":"string"}}}}},"operationId":"get_well_known_data_retention"}},"/.well-known/why-identyclaw":{"get":{"summary":"Why IdentyClaw - Value Proposition","description":"Returns the value proposition document explaining how IdentyClaw enables AI-to-agent interaction, including endpoint benefits, use cases, security advantages, and complete agent communication flows.","tags":["Policies"],"responses":{"200":{"description":"Why IdentyClaw value proposition document.","content":{"text/markdown":{"schema":{"type":"string"}},"text/html":{"schema":{"type":"string"}}}}},"operationId":"get_well_known_why_identyclaw"}},"/.well-known/did/rodit/{tokenId}":{"get":{"summary":"Resolve did:rodit DID document","description":"EXPERIMENTAL: Returns the DID document for a specific RODiT token identifier. This endpoint requires authentication because DID resolution is a paid service. This endpoint is experimental and may change in future versions.","tags":["DID"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"tokenId","in":"path","required":true,"schema":{"type":"string"},"description":"RODiT token identifier."}],"responses":{"200":{"description":"DID document for the requested token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DidDocument"}}}},"400":{"description":"Invalid DID or token identifier (`INVALID_DID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalidDid":{"$ref":"#/components/examples/ErrorInvalidDid"}}}}},"404":{"description":"Token not found (`DID_NOT_FOUND`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"DID resolution failed (`DID_RESOLUTION_FAILED`). Generic message only; use `requestId` in logs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"resolutionFailed":{"$ref":"#/components/examples/ErrorDidResolutionFailed"}}}}}},"operationId":"get_well_known_did_rodit_by_tokenId"}},"/.well-known/did/resolve":{"get":{"summary":"Resolve DID string","description":"EXPERIMENTAL: Resolves either did:rodit or did:web identifiers into DID documents. This endpoint is experimental and may change in future versions.","tags":["DID"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"did","in":"query","required":true,"schema":{"type":"string"},"description":"DID string to resolve (did:rodit or did:web forms)."}],"responses":{"200":{"description":"Resolved DID document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DidDocument"}}}},"400":{"description":"Invalid DID supplied (`INVALID_DID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalidDid":{"$ref":"#/components/examples/ErrorInvalidDid"}}}}},"404":{"description":"Token not found for the supplied DID (`DID_NOT_FOUND`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"DID resolution failed (`DID_RESOLUTION_FAILED`). Generic message only; use `requestId` in logs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"resolutionFailed":{"$ref":"#/components/examples/ErrorDidResolutionFailed"}}}}}},"operationId":"get_well_known_did_resolve"}},"/.well-known/did/web/token/{tokenId}":{"get":{"summary":"Resolve did:web DID document","description":"Returns the did:web representation for a token hosted by this service, providing interoperability with web DID resolvers.","tags":["DID"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"tokenId","in":"path","required":true,"schema":{"type":"string"},"description":"RODiT token identifier."}],"responses":{"200":{"description":"did:web DID document for the requested token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DidDocument"}}}},"400":{"description":"Invalid DID or token identifier (`INVALID_DID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalidDid":{"$ref":"#/components/examples/ErrorInvalidDid"}}}}},"404":{"description":"Token not found (`DID_NOT_FOUND`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"DID resolution failed (`DID_RESOLUTION_FAILED`). Generic message only; use `requestId` in logs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"resolutionFailed":{"$ref":"#/components/examples/ErrorDidResolutionFailed"}}}}}},"operationId":"get_well_known_did_web_token_by_tokenId"}},"/.well-known/did/web/token/{tokenId}/did.json":{"get":{"summary":"Resolve did:web JSON DID document","description":"EXPERIMENTAL: Returns the JSON DID document commonly served at did.json endpoints for did:web identifiers. This endpoint is experimental and may change in future versions.","tags":["DID"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"tokenId","in":"path","required":true,"schema":{"type":"string"},"description":"RODiT token identifier."}],"responses":{"200":{"description":"did:web DID document for the requested token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DidDocument"}}}},"400":{"description":"Invalid DID or token identifier (`INVALID_DID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalidDid":{"$ref":"#/components/examples/ErrorInvalidDid"}}}}},"404":{"description":"Token not found (`DID_NOT_FOUND`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"DID resolution failed (`DID_RESOLUTION_FAILED`). Generic message only; use `requestId` in logs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"resolutionFailed":{"$ref":"#/components/examples/ErrorDidResolutionFailed"}}}}}},"operationId":"get_well_known_did_web_token_by_tokenId_did_json"}},"/api/testhola":{"post":{"summary":"Test HOLA message validation and respond with server HOLA","description":"Self-testing endpoint to validate YOUR HOLA message generation before sending to peers. Use `/api/testhola` for self-testing; use `/api/identity/verify` for peer verification.\n\n**Authentication Scope:** `/api/testhola` is JWT-protected as an API endpoint, but HOLA validation outcomes are based on the submitted HOLA fields and cryptographic checks (not caller-token equality).\n\n**When to Use:**\n- `/api/testhola`: Self-testing to validate YOUR HOLA generation before sending to peers\n- `/api/identity/verify`: Peer verification to verify HOLA received from ANOTHER agent\n\nValidation is fail-closed and covers: format sanity, checksum, timestamp freshness, nonce replay, sender token existence, token expiration (`not_after`), blockchain/public-key availability, and Ed25519 signature verification.\n\nSupports both formats:\n- Standard: HOLA/<recipient>/<tokenId>/<ISO8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/<base32-signature>/<checksum>\n- Subagent: HOLA/<recipient>/<delegateID>/<issuer_tokenId>/<publicKey-base32>/<ISO8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/<base32-signature>/<checksum>\n\nOn rejection, the response includes machine-readable error details under `error.details` with:\n- `stage`: validation stage that failed\n- `reasonCode`: stable failure classifier for automated clients/tests\n- `hint`: remediation hint for common failure modes\n\nEarly shape-lint stages (before full parsing) may set `reasonCode` to: `payload_is_json_object`, `unix_millis_timestamp`, `signature_not_base32`, `placeholder_signature`. Each error includes `details.documentation` (relative reference paths) and `details.example` (a corrected snippet for the offending field) so agents can self-correct without external lookup.\n\n**Webhook Emission Behavior:** The server emits `/api/testhola` webhook events to the authenticated peer's configured webhook URL at `/hooks/wake` and `/hooks/agent` only when `WEBHOOK_TEST_ENABLED=true`.\n\nEmitted events:\n- `testhola_validation_failed`\n- `testhola_response_failed`\n- `testhola_validation_success`","tags":["Testing"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hola":{"type":"string","description":"Request field for the HOLA message to validate. Supports standard and subagent formats. Standard: HOLA/<recipient>/<tokenId>/<ISO8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/<base32-signature>/<checksum>. Subagent: HOLA/<recipient>/<delegateID>/<issuer_tokenId>/<publicKey-base32>/<ISO8601-timestamp>/<noncets-hex>/API.IDENTYCLAW.COM/<base32-signature>/<checksum>. Base32 variant is RFC 4648 Base32 (A-Z2-7, uppercase canonical form, optional '=' padding on input). Checksum is one letter from ABCDEFGHJKMNPQRSTUVWXYZ (mod 23 over canonical prefix + signature + '/').","example":"HOLA/MUNDO/pkcnjdbdefcp/2026-04-14T14:00:00.000Z/4F9A3C7E2D1B9A4C8E7F6A5B4C3D2E1F/API.IDENTYCLAW.COM/MEQW4YLTORUW63THMV2GC3DBNVRWQ/R"}},"required":["hola"]}}}},"responses":{"200":{"description":"HOLA message is valid. Server responds with its own valid HOLA.","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean","description":"Whether the client's HOLA message is valid.","example":true},"peerTokenId":{"type":"string","description":"The token ID from the client's HOLA message."},"destinatary":{"type":"string","description":"Recipient extracted from the client's HOLA message."},"peerVerified":{"type":"boolean","description":"Whether the client's identity was verified.","example":true},"hola":{"type":"string","description":"The server's valid HOLA response signed with roditclient's key pair."},"serverTokenId":{"type":"string","description":"The server's token ID used in the response HOLA."},"serverDestinatary":{"type":"string","description":"Recipient used by the server in its response HOLA."},"serverTimestamp":{"type":"string","format":"date-time","description":"The ISO 8601 timestamp used in the server's HOLA response."},"checks":{"type":"object","properties":{"formatValid":{"type":"boolean"},"checksumValid":{"type":"boolean"},"timestampValid":{"type":"boolean"},"timestampFresh":{"type":"boolean"},"noncetsValid":{"type":"boolean"},"nonceReplaySafe":{"type":"boolean"},"tokenExists":{"type":"boolean"},"tokenActive":{"type":"boolean"},"signatureValid":{"type":"boolean"},"publicKeyAvailable":{"type":"boolean"}}},"requestId":{"type":"string","description":"Request correlation identifier."},"warnings":{"type":"array","description":"Optional warning entries for suspicious but non-fatal conditions such as recipient mismatch.","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}}}}}},"400":{"description":"HOLA message is invalid, stale, or signature verification failed. Returns detailed reason.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"checksumInvalid":{"summary":"Checksum invalid","value":{"error":{"code":"HOLA_VALIDATION_FAILED","message":"Invalid checksum: expected A, got B","details":{"valid":false,"stage":"format_checksum_and_payload_validation","reasonCode":"checksum_invalid","hint":"Recompute checksum: sum UTF-16 code units over canonical uppercase prefix through signature plus trailing '/', mod 23, index into ABCDEFGHJKMNPQRSTUVWXYZ.","checks":{"formatValid":true,"checksumValid":false,"timestampValid":true,"noncetsValid":true}}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-04-15T07:47:00.000Z"}},"nonceReplay":{"summary":"Nonce replay detected","value":{"error":{"code":"HOLA_VALIDATION_FAILED","message":"Nonce replay detected within allowed timestamp window","details":{"valid":false,"stage":"nonce_replay_validation","reasonCode":"nonce_replay","hint":"Request a new nonce and sign a new hola message.","checks":{"formatValid":true,"checksumValid":true,"timestampValid":true,"noncetsValid":true,"nonceReplaySafe":false}}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-04-15T07:47:00.000Z"}},"timestampStale":{"summary":"Timestamp stale/future","value":{"error":{"code":"HOLA_TIMESTAMP_INVALID","message":"Timestamp stale or future: age is 601s, max allowed is 300s","details":{"valid":false,"stage":"timestamp_freshness_validation","reasonCode":"timestamp_stale_or_future","hint":"Generate a fresh hola using a recent timestamp within the allowed age window.","ageSeconds":601,"maxAgeSeconds":300}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-04-15T07:47:00.000Z"}},"signaturePublicKeyUnavailable":{"summary":"Public key unavailable","value":{"error":{"code":"HOLA_SIGNATURE_INVALID","message":"Public key unavailable or invalid length","details":{"valid":false,"stage":"signature_verification","reasonCode":"public_key_unavailable","hint":"Verify signer public key encoding, parent token status, and exact signed payload.","tokenExists":true,"tokenActive":true,"tokenExpired":false,"publicKeyAvailable":false}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-04-15T07:47:00.000Z"}},"signatureMismatch":{"summary":"Signature mismatch","value":{"error":{"code":"HOLA_SIGNATURE_INVALID","message":"Signature verification failed","details":{"valid":false,"stage":"signature_verification","reasonCode":"signature_mismatch","hint":"Verify signer public key encoding, parent token status, and exact signed payload.","tokenExists":true,"tokenActive":true,"tokenExpired":false,"publicKeyAvailable":true}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-04-15T07:47:00.000Z"}},"tokenExpired":{"summary":"Sender token expired","value":{"error":{"code":"HOLA_SIGNATURE_INVALID","message":"Token expired (not_after)","details":{"valid":false,"stage":"signature_verification","reasonCode":"token_expired","hint":"Verify signer public key encoding, parent token status, and exact signed payload.","tokenExists":true,"tokenActive":false,"tokenExpired":true,"publicKeyAvailable":false}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-04-15T07:47:00.000Z"}},"subagentPublicKeyDecodeFailed":{"summary":"Subagent public key decode failed","value":{"error":{"code":"HOLA_SIGNATURE_INVALID","message":"Public key unavailable or invalid length: Invalid base32 input","details":{"valid":false,"stage":"signature_verification","reasonCode":"public_key_decode_failed","hint":"Verify signer public key encoding, parent token status, and exact signed payload.","isSubagentFormat":true,"delegateId":"researcher-sub-001","issuerTokenId":"PKCNJDBDEFCP","tokenExists":true,"tokenActive":true,"tokenExpired":false,"publicKeyAvailable":false}},"requestId":"01HX7HBE4X7AMP9Q4MP2YE2C41","timestamp":"2026-04-15T07:47:00.000Z"}}}}}},"500":{"description":"Server error or unable to generate HOLA response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"post_api_testhola"}}}}