# Authored by DotWin # ARKZ: the Human API and the ARK Record Protocol (ARP v0), for an agent holding a credential An ARK is one person's knowledge, voice and judgment, owned by that person. You can read from an ARK and propose records into it. Nothing you propose enters the ARK until its owner accepts it. ## Endpoints - REST: POST https://www.arkz.ai/api/human with a JSON body {"action": "", ...} - MCP (Streamable HTTP, JSON-RPC 2.0): https://www.arkz.ai/mcp (protocol versions 2025-06-18, 2025-03-26, 2024-11-05) - Send the credential as Authorization: Bearer (REST also takes x-ark-api-key). - No credential needed: the REST actions health and get_ark_schema. - Record schema: https://www.arkz.ai/protocol/v0/record.schema.json - Tool list: https://www.arkz.ai/protocol/v0/mcp-tools.json - OpenAPI 3.1: https://www.arkz.ai/developers/openapi.yaml - Documentation: https://www.arkz.ai/developers ## Credentials (the prefix tells you what you hold) - ark_live_ owner key: made by the ARK owner on the API keys settings page. Reads and writes as its scopes allow. - arka_ OAuth access credential: an app the owner connected (PKCE S256; discovery at /.well-known/oauth-authorization-server). - lic_ licence: a licensee's credential. It can never propose. - ark_src_ source key: made by the owner for one grant. Write only: propose_ark_record, propose_ark_records, get_ark_proposal_status. Anything else is refused with source_key_forbidden. 30 calls per 60 s and 1000 calls per 24 h. It cannot open an MCP session (initialize and tools/list answer -32003): use REST, or send bare tools/call messages. ## Actions and scopes Live for every ARK: - get_ark_profile, search_ark_topics: read:profile or read:stats. Free. - ask_ark: generate:text, generate:chat, generate:email or generate:social. Billed to the account behind the credential: the ARK owner for an owner key or OAuth; the licensee under the licence quota for a licence; covered (cost 0) for a bounded derivative licence. One question per call, no carried history, at most 4000 characters. Early access (only for an ARK enrolled in the protocol; otherwise the action is unknown, except get_ark_schema over REST, which answers for every ARK): - get_ark_schema: the record and scope schemas, the facets and kinds, the error codes. Public over REST. - list_ark_records, get_ark_record, get_ark_provenance: read:records. Vault records are never served. - propose_ark_record, propose_ark_records (up to 100 envelopes): write:proposals. Free at intake; the owner is charged only when they accept. - get_ark_proposal_status: write:proposals. Status only, for proposals this credential (or its grant) sent. Free. ## The envelope Required: arp ("0"), ark ("ark:arkz:"), facet, kind, authorship, content (text and/or structured), provenance.content_sha256. ARKZ assigns id, trust_tier and status and ignores them on a proposal. The hash: lower-case hex SHA-256 of the UTF-8 bytes of - content.text exactly as sent, when there is no structured part; - content.text + "\n" + JCS(content.structured), when structured is non-empty (JCS: keys sorted, no spaces). Python, for ASCII keys and integer values: json.dumps(s, sort_keys=True, separators=(',', ':'), ensure_ascii=False). For floats or non-ASCII keys, use a real RFC 8785 library. ARKZ recomputes the hash; a mismatch is hash_mismatch. ## Propose ```json { "action": "propose_ark_record", "envelope": { "arp": "0", "ark": "ark:arkz:3f6c1a52-8d4e-4b7a-9c2f-5e1d0a7b8c9d", "facet": "framework", "kind": "definition", "authorship": "self_paraphrase", "privacy": "training", "content": { "text": "A keystone habit is one routine that pulls the rest of a plan along with it. Name it, guard it, and let the other habits hang from it." }, "topics": ["habits", "planning"], "provenance": { "content_sha256": "767df8399817b451238ab1075a2bc55a8eb9cc144aaad4a969814e528308dd5a", "source_kind": "notes_app" } } } ``` The answer is {"arp": "0", "proposal_id": "", "status": "proposed", "lane": "typed"}, or {"arp": "0", "status": "duplicate", "duplicate_of": ""} when your source already sent the same content. A batch answers one result per envelope, in order; one refused item never stops the rest. ## Check status ```json { "action": "get_ark_proposal_status", "proposal_ids": ["0f8e2a4c-6b1d-4e3f-9a7c-2d5b8e1f4a60"] } ``` Statuses: proposed, held, accepted, rejected, expired, not_found (an id that is not yours looks the same as a missing one). Or list newest first with since, limit (up to 100) and the previous page's next as after. Poll with backoff; status checks are free and use no quota. ## Honesty rules (the owner decides; these keep you trusted) - Label authorship truthfully: self_verbatim only for the owner's exact words; self_paraphrase for a faithful retelling; self_structured for the owner's material put in structure. Anything you inferred cannot be proposed in v0 (authorship derived answers grant_scope): propose only what the owner said or gave. - Never invent verbatim words. If you did not capture it word for word, it is not self_verbatim. - Material written by someone else about the owner (a testimonial, a review, an article) is about_subject. - Do not send voice in v0: a voice/utterance proposal is taken at intake but can never be accepted today. - identity, relationship and boundary records are the owner's alone (subject_reserved). ## Errors Protocol refusals answer {"error": "", "message": "..."} with an HTTP status on both gateways: schema_invalid, hash_mismatch, verbatim_required (400); grant_scope, subject_reserved, scope_missing, not_enabled (403); not_found (404); quota_exceeded (429); not_stored (503: send the same request again). Gateway refusals differ by gateway: MCP sends codes (unauthorized 401, rate_limited 429, payload_too_large 413, method_not_allowed 405, server_error 500); REST sends an HTTP status and a plain message with no code for the same cases, so switch on the status. source_key_forbidden (403) and not_recorded (503, resend the same request) are sent by both. The three live actions answer an HTTP status and a message with no code. Over MCP a refused protocol tool is a normal result with isError true whose text is that JSON body; a live tool's text starts with "Error:"; a source key calling anything but its three tools gets JSON-RPC error -32003; bad params get -32602.