{
  "service": "Riskhold",
  "description": "A risk register that keeps the judgement attributable to a person. An agent can read everything in a workspace and draft into it; it cannot accept a risk, record a review, change the scoring methodology, or remove anything.",
  "authentication": {
    "scheme": "Authorization: Bearer <token>",
    "issued": "On the Agents screen in settings, by a workspace owner. Shown once and stored only as a hash, so a lost key is replaced rather than recovered.",
    "scope": "One workspace. A key cannot see or reach any other."
  },
  "read": {
    "summary": "Everything in the workspace the key belongs to.",
    "collections": [
      "controls",
      "decision_records",
      "library_updates",
      "memberships",
      "methodology",
      "profile",
      "risk_controls",
      "risks",
      "scale",
      "scoring_config"
    ]
  },
  "create": {
    "summary": "4 kinds of register entry, each arriving as an unreviewed draft, and the company profile.",
    "resources": [
      "company_profile",
      "control",
      "decision_record",
      "risk",
      "risk_control"
    ],
    "note": "Every entry an agent creates is marked as its draft and as awaiting review. That is not a field you set \u2014 it is stamped on write, and a request that tries to arrive pre-reviewed is refused. The company profile is not an entry: it is recorded as answered by you, and what it proposes waits for a person."
  },
  "company_profile": {
    "summary": "What the company does, what data it holds and which tools it runs on. Riskhold's library turns the answers into suggested risks and controls \u2014 deterministically, with no model involved.",
    "how": "GET /api/v1/profile/ for the questions and their option tags. POST {\"answers\": {\"<question id>\": \"<tag>\" or [\"<tag>\", ...]}} to the same path. Questions you leave out keep their answer; null clears one.",
    "what_follows": "The response carries the open proposal: the library risks the answers imply, and flags on existing ones. A person accepts or dismisses each on the review screen at `review_url`; nothing reaches the register until they do, and accepted risks arrive as library drafts, not yours. Answer the profile before drafting and draft what the proposal does not cover.",
    "not_yours": "Agents may answer the company profile but not its quick checks. A check answer is recorded as a person's own statement about a control \u2014 '2-Step Verification is enforced, and we could show you' \u2014 and only that person can make it. Leave `checks` out; the answers already given are kept."
  },
  "update": {
    "summary": "Your own drafts, until a person has confirmed one.",
    "how": "POST the corrected entry to the same collection with its id. There is no PUT or PATCH.",
    "omitted_fields": "Left as they are, not cleared. Send only what you are changing. Anything you leave out keeps its current value, so correcting a title cannot erase a description you did not mention.",
    "defaults": "On creation, fields you omit take the workspace's own defaults \u2014 you do not need to restate the treatment or the source on every draft.",
    "stops_when": "Somebody reviews it. From that point the entry is the reviewer's, and a correction is refused with the reason \u2014 raise a new entry or ask them."
  },
  "delete": null,
  "delete_note": "Nothing, ever, including archiving \u2014 which takes an entry out of the working register and is a removal in everything but name. Enforced by a Postgres trigger as well as by this API, so it holds against a bug here.",
  "uploads": null,
  "uploads_note": "No endpoint accepts a file and none will. To record where a document lives, create a decision record naming its title, date and location. Riskhold points at evidence rather than holding it.",
  "refused": {
    "risk_acceptance": {
      "guarantee": "human_judgement",
      "why": "Agents may draft a risk but not accept one. Accepting residual risk is a statement about what your company will live with, so it needs a person's name against it."
    },
    "risk_review": {
      "guarantee": "human_judgement",
      "why": "Agents may not mark a risk as reviewed. A review is a record that somebody looked, and only somebody who looked can record one."
    },
    "scoring_config": {
      "guarantee": "human_judgement",
      "why": "Agents may not change the scoring methodology. It is the basis on which every score in the register is comparable, and changing it restates what the company has already published about itself."
    },
    "risk_scale": {
      "guarantee": "human_judgement",
      "why": "Agents may not change the assessment scale. What 'Major' means is a definition the company publishes, not a parameter."
    },
    "scale_level": {
      "guarantee": "human_judgement",
      "why": "Agents may not change the assessment scale. What 'Major' means is a definition the company publishes, not a parameter."
    },
    "membership": {
      "guarantee": "human_judgement",
      "why": "Agents may not change who has access to this workspace. Only an owner can."
    },
    "agent_key": {
      "guarantee": "human_judgement",
      "why": "Agents may not issue or revoke agent keys, including their own. A key that can mint keys is not a boundary."
    },
    "company_profile.checks": {
      "guarantee": "human_judgement",
      "why": "Agents may answer the company profile but not its quick checks. A check answer is recorded as a person's own statement about a control \u2014 '2-Step Verification is enforced, and we could show you' \u2014 and only that person can make it. Leave `checks` out; the answers already given are kept."
    },
    "everything_else": {
      "guarantee": "human_judgement",
      "why": "Agents may draft risks, controls, control links and decision records, and answer the company profile. Nothing else in Riskhold is writable by a machine."
    }
  },
  "guarantees": {
    "authorship": "An entry belongs to whoever wrote it. An agent may correct its own draft and nobody else's, and only until a person has confirmed it.",
    "human_judgement": "Anything that states what a person decided needs that person's name against it. Accepting risk, recording a review, and setting the methodology are all claims about judgement, not transcription.",
    "no_deletion": "An agent cannot remove anything from the register, by any route."
  },
  "errors": {
    "shape": {
      "error": {
        "code": "<string>",
        "message": "<sentence>"
      }
    },
    "codes": [
      "authorship",
      "human_judgement",
      "idempotency_conflict",
      "invalid",
      "malformed",
      "method_not_allowed",
      "no_deletion",
      "no_uploads",
      "not_found",
      "rate_limited",
      "unauthenticated"
    ],
    "note": "A refusal always says which promise refused it. Retrying an identical request after one of the three guarantee codes will always fail the same way \u2014 the answer is a different action, or a message to the person you are working with."
  },
  "rate_limit": {
    "requests": 300,
    "per_seconds": 60,
    "scope": "one agent key",
    "headers": [
      "X-RateLimit-Limit",
      "X-RateLimit-Remaining",
      "X-RateLimit-Reset"
    ],
    "note": "Sent on every response, not only on a refusal, so you can pace yourself rather than discover the ceiling by hitting it. It is set well above what drafting a register takes; reaching it usually means a retry loop. A refusal is a 429 with Retry-After and loses you nothing."
  },
  "writes_are_idempotent": {
    "header": "Idempotency-Key",
    "note": "Send a unique key with any POST. A retry carrying the same key returns the original response instead of creating a second entry. Without one, a timed-out request that actually succeeded becomes a duplicate \u2014 and an agent cannot delete the duplicate."
  }
}