{
 "openapi": "3.1.0",
 "info": {
  "title": "graftfork API",
  "version": "1.0.0",
  "summary": "Keep your changed copy of an open-source project on upstream's releases.",
  "description": "graftfork (Graft, at graftfork.com) moves your changes onto each new upstream release, runs your tests, and delivers the moved branch only if every change lands and the tests pass. This API lets an agent connect copies, start syncs, read what stopped one, and hand in resolutions. A resolution never moves a copy by itself: Graft runs the sync again and the tests decide.\n\nIt also rebuilds a program blind from its duramen record: connect the spec (POST /regen), read the free grade (GET /regen/{id}/report), rebuild (POST /regen/{id}/run, which spends agent work), poll the rebuild (GET /regen/runs/{id}) until it ends, and fetch its signed record (GET /regen/runs/{id}/record) to check with npx graftfork verify-record.\n\nAuthenticate with a personal API token from https://graftfork.com/app#settings as a bearer token, or connect with OAuth 2.1 (authorization code with PKCE S256, Dynamic Client Registration; https://graftfork.com/.well-known/oauth-authorization-server): the person allows the client on a consent page and sets the same limits, and its access token works here when it was issued for https://graftfork.com/api/v1. Rate limits: 120 requests a minute per token, 30 of them changes. An organization's owner can also make agents the organization owns (POST /orgs/{id}/agents, the admin scope): their tokens act only on the organization's copies, keep working when the person who made them leaves, never review or approve, and are written in the ledger as agent:<agent id> on behalf of org:<org id>. A token can be limited when it is made: an expiry (401 token_expired after it), the copies and specs it may act on (others answer 404; connecting, checks, organizations and webhooks answer 403 out_of_scope), and a monthly amount of agent work (402 token_budget_spent once the runs it started have spent it). GET /token shows a token its limits. Every use of a token is logged and shown in /app, and what a token does is written in Graft's ledger as the agent (agent:<token id>) on the user's behalf.",
  "contact": {
   "email": "support@graftfork.com",
   "url": "https://graftfork.com/docs/api"
  }
 },
 "servers": [
  {
   "url": "https://graftfork.com/api/v1"
  }
 ],
 "security": [
  {
   "token": []
  },
  {
   "oauth": []
  }
 ],
 "tags": [
  {
   "name": "copies"
  },
  {
   "name": "runs"
  },
  {
   "name": "upstreams"
  },
  {
   "name": "conflicts"
  },
  {
   "name": "checks"
  },
  {
   "name": "upgrades"
  },
  {
   "name": "specs"
  },
  {
   "name": "webhooks"
  },
  {
   "name": "account"
  },
  {
   "name": "orgs"
  },
  {
   "name": "try",
   "description": "Without an account or a token"
  },
  {
   "name": "agent accounts"
  }
 ],
 "paths": {
  "/try": {
   "servers": [
    {
     "url": "https://graftfork.com/api"
    }
   ],
   "post": {
    "operationId": "startTry",
    "summary": "Try a public fork, no account",
    "tags": [
     "try"
    ],
    "security": [],
    "description": "Runs one real sync of a public GitHub fork onto upstream's newest release in a short-lived sandbox: it never pushes and runs no agent. No token, no account. Limits: 10 tries a day per network address (people on one network share them), one running at a time, 10 minutes each; a result for the same fork, base and release is reused for 24 hours. Send Content-Type application/json. Poll statusUrl (GET /api/try/{id}) every pollSeconds until status is done, failed or error; resultUrl (https://graftfork.com/t/{id}) is the page to share.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "repo": {
          "type": "string",
          "description": "The fork: owner/name or its GitHub URL"
         },
         "upstream": {
          "type": "string",
          "description": "The project it's a copy of, when Graft can't tell (owner/name)"
         },
         "base": {
          "type": "string",
          "description": "The upstream release the changes started from, when Graft can't tell"
         }
        },
        "required": [
         "repo"
        ]
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The same fork and release is running for someone else: watch that try (shared: true)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "id": {
           "type": "string"
          },
          "shared": {
           "type": "boolean"
          },
          "statusUrl": {
           "type": "string",
           "format": "uri"
          },
          "resultUrl": {
           "type": "string",
           "format": "uri"
          },
          "pollSeconds": {
           "type": "integer"
          }
         },
         "required": [
          "id"
         ]
        }
       }
      }
     },
     "201": {
      "description": "Started, or answered from a result in the last 24 hours (cached: true)",
      "headers": {
       "Location": {
        "description": "statusUrl",
        "schema": {
         "type": "string",
         "format": "uri"
        }
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "id": {
           "type": "string"
          },
          "cached": {
           "type": "boolean"
          },
          "statusUrl": {
           "type": "string",
           "format": "uri"
          },
          "resultUrl": {
           "type": "string",
           "format": "uri"
          },
          "pollSeconds": {
           "type": "integer"
          }
         },
         "required": [
          "id",
          "statusUrl",
          "resultUrl",
          "pollSeconds"
         ]
        }
       }
      }
     },
     "400": {
      "description": "Not a public GitHub repository, or a bad upstream or base",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "string"
          }
         },
         "required": [
          "error"
         ]
        }
       }
      }
     },
     "429": {
      "description": "This address's limit for the day, or a try of yours is still running (id names it)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "string"
          },
          "id": {
           "type": "string"
          }
         },
         "required": [
          "error"
         ]
        }
       }
      }
     },
     "503": {
      "description": "Try is paused or its queue is full",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "string"
          }
         },
         "required": [
          "error"
         ]
        }
       }
      }
     }
    },
    "parameters": [
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ]
   }
  },
  "/try/{id}": {
   "servers": [
    {
     "url": "https://graftfork.com/api"
    }
   ],
   "get": {
    "operationId": "getTry",
    "summary": "A try's progress and result",
    "tags": [
     "try"
    ],
    "security": [],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The try's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "description": "status is queued, running, done, failed or error. Times are UTC, ISO 8601. limitMinutes is the try's time limit, not how long it took. A reused result (cached: true, reusedFrom) keeps the original run's startedAt and finishedAt. Results are kept for 30 days.",
    "responses": {
     "200": {
      "description": "The try",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true,
         "properties": {
          "id": {
           "type": "string"
          },
          "status": {
           "type": "string",
           "enum": [
            "queued",
            "running",
            "done",
            "failed",
            "error"
           ]
          },
          "result": {
           "type": [
            "object",
            "null"
           ],
           "additionalProperties": true
          },
          "detection": {
           "type": [
            "object",
            "null"
           ],
           "additionalProperties": true
          },
          "limitMinutes": {
           "type": "integer"
          },
          "cached": {
           "type": "boolean"
          },
          "reusedFrom": {
           "type": "string"
          }
         }
        }
       }
      }
     },
     "404": {
      "description": "No such try",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "error": {
           "type": "string"
          }
         },
         "required": [
          "error"
         ]
        }
       }
      }
     }
    }
   }
  },
  "/token": {
   "get": {
    "operationId": "getToken",
    "summary": "The token's owner and scopes",
    "description": "Any valid token can call this.",
    "tags": [
     "account"
    ],
    "responses": {
     "200": {
      "description": "The token",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "email": {
           "type": "string"
          },
          "token": {
           "type": "string",
           "description": "The token's id (tok_...), never the token"
          },
          "scopes": {
           "type": "array",
           "items": {
            "type": "string",
            "enum": [
             "read",
             "sync",
             "resolve"
            ]
           }
          },
          "rate": {
           "type": "object",
           "additionalProperties": true
          },
          "limits": {
           "type": "object",
           "properties": {
            "expiresAt": {
             "type": [
              "string",
              "null"
             ],
             "description": "When the token stops working (UTC), or null"
            },
            "copies": {
             "type": [
              "array",
              "null"
             ],
             "items": {
              "type": "string"
             },
             "description": "The copy and spec ids it may act on, or null for all"
            },
            "monthlyUsd": {
             "type": [
              "number",
              "null"
             ],
             "description": "The agent work a month the runs it starts may spend, or null"
            },
            "spentUsd": {
             "type": [
              "number",
              "null"
             ],
             "description": "What those runs have spent this month (UTC)"
            }
           }
          }
         }
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    }
   }
  },
  "/copies": {
   "get": {
    "operationId": "listCopies",
    "summary": "List your copies",
    "description": "Needs the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "copies"
    ],
    "responses": {
     "200": {
      "description": "Your copies",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "copies": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Copy"
           }
          }
         },
         "required": [
          "copies"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "connectCopy",
    "summary": "Connect a repository",
    "description": "With only `repo`, Graft detects the upstream, the release your branch is on and how releases are tagged, as the dashboard's one click does, and starts the first sync when there is a newer release. With `branch`, also give `upstream` and `base`; the first sync then waits for a sync call or the next release. With `repo`, Graft's GitHub app must be installed on the repository. Or give `clone_url`, `branch`, `upstream` and `base` for any https git host: no GitHub app is needed, and Graft delivers the moves itself (graft pull). A full copy limit answers 403 copy_limit_reached: disconnect a copy first.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "copies"
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ConnectRequest"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Connected",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "copyId": {
           "type": "string"
          },
          "run": {
           "type": [
            "string",
            "null"
           ],
           "description": "The first sync, when one started"
          },
          "backend": {
           "type": "string",
           "enum": [
            "delegated",
            "none",
            "anthropic-byok",
            "managed-agents"
           ],
           "description": "Who resolves this copy's conflicts, as given or by default"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "copyId",
          "message"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "parameters": [
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/copies/{id}": {
   "get": {
    "operationId": "getCopy",
    "summary": "One copy, its latest syncs, and its desired and observed state",
    "description": "Needs the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "copies"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The copy",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "copy": {
           "$ref": "#/components/schemas/Copy"
          },
          "recentRuns": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Run"
           }
          },
          "desired": {
           "$ref": "#/components/schemas/CopyDesired"
          },
          "observed": {
           "$ref": "#/components/schemas/CopyObserved"
          }
         },
         "required": [
          "copy",
          "recentRuns"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "put": {
    "operationId": "setCopy",
    "summary": "Set a copy's desired state",
    "description": "The fields you send are set and the rest stay as they are; every field is checked before any is written, and a field that isn't a setting is refused (400). Graft's cron then converges the copy: it follows releases as `follow`, `pre_releases` and `schedule` say, delivers as `delivery` says, and gives Graft's agent at most `max_usd` a sync (null: the per-sync cap). Unpausing a copy on someone else's installation is refused (403), and so is changing the delivery of a copy whose move waits for its organization's review (409). Answers with the desired and observed state; watch `observed.state` (or a webhook) to see it converge. Changes to an organization's copy go in its audit log.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "copies"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CopyDesired"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The desired and observed state",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "copy": {
           "type": "string"
          },
          "desired": {
           "$ref": "#/components/schemas/CopyDesired"
          },
          "observed": {
           "$ref": "#/components/schemas/CopyObserved"
          }
         },
         "required": [
          "copy",
          "desired",
          "observed"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/copies/{id}/sync": {
   "post": {
    "operationId": "syncCopy",
    "summary": "Sync now",
    "description": "Queues a sync onto upstream's newest release (or the newest one the copy may take). Counts against the monthly sync limit. The copy moves only if every change lands and the tests pass. If the copy's last sync of the same release stayed because Graft's agent used its whole budget for one sync, it answers 409 with code needs_force; send {\"force\": true} to sync again anyway. With max_usd, the sync's agent never spends more than that; when it could (the per-sync cap within what's left this month), it answers 409 with code price_quote and estimateUsd, capUsd and workLeftUsd, and nothing is queued. Without max_usd, the copy's own max_usd (PUT /copies/{id}) is the most its agent may spend.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "copies"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": false,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "force": {
          "type": "boolean",
          "description": "Sync again even though the last sync of this release stopped at the per-sync agent budget"
         },
         "max_usd": {
          "type": "number",
          "minimum": 0,
          "description": "The most Graft's agent may spend on this sync, in dollars. A clean sync spends nothing; a conflict can spend up to the per-sync cap (see /usage)."
         }
        },
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "202": {
      "description": "Started",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/copies/{id}/disconnect": {
   "post": {
    "operationId": "disconnectCopy",
    "summary": "Disconnect a copy",
    "description": "Stops following the copy and frees its place (five per person, on every plan). Queued syncs are cancelled; its conflicts, kept moves and any read token you gave Graft are deleted. Its syncs and their signed records stay, marked detached. An organization's agent can't disconnect a copy (403 out_of_scope): only the copy's owner can.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "copies"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "Disconnected",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "disconnected": {
           "type": "boolean"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "disconnected"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/copies/{id}/runs": {
   "get": {
    "operationId": "listRuns",
    "summary": "A copy's syncs",
    "description": "Newest first, up to 50. When more is true, ask again with before=next_before for older ones.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "runs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "before",
      "in": "query",
      "required": false,
      "description": "A run's id from the last answer's next_before: only older ones.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Newest first, up to 50. When more is true, ask again with before=next_before for older ones.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "runs": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Run"
           }
          },
          "more": {
           "type": "boolean",
           "description": "Older ones remain: ask again with before=next_before"
          },
          "next_before": {
           "type": [
            "string",
            "null"
           ],
           "description": "The last item's id when more is true, else null"
          }
         },
         "required": [
          "runs",
          "more",
          "next_before"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/copies/{id}/upstream": {
   "get": {
    "operationId": "sendUpstream",
    "summary": "A change, ready to send upstream",
    "description": "One of the copy's changes as its latest move carried it, with its reason as the pull request's text for the upstream project (the MCP tool send_upstream). You open the pull request from your own account; Graft never opens one.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "copies"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "change",
      "in": "query",
      "required": true,
      "description": "The change's subject (or part of it), or its number among the latest move's carried changes",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The change and its pull request text",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "buildUpstream",
    "summary": "Build a change for upstream",
    "description": "Graft builds a branch (graft/upstream/<change>) with only this change on the release the copy's latest move carried it onto, runs the copy's tests on the release and on the change, and keeps the branch as a git bundle and the change as a patch file. Nothing is pushed and no pull request is opened: you open it from your own account. Only changes the latest move kept or rewrote. Building the same change again for the same move answers 200 with the build already made.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "upstreams"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "change": {
          "type": "string",
          "description": "The change's subject (or part of it), or its number among the latest move's carried changes"
         }
        },
        "required": [
         "change"
        ],
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Already built or building",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "upstream": {
           "$ref": "#/components/schemas/Upstream"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "upstream"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "202": {
      "description": "Started",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "upstream": {
           "$ref": "#/components/schemas/Upstream"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "upstream"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/upstreams/{id}": {
   "get": {
    "operationId": "getUpstream",
    "summary": "An upstream build",
    "description": "Its status, the tests' verdict, the pull request's title and text, download links for the bundle and the patch (an hour), the commands that fetch the branch and open the pull request from your account, and the tracked pull request's state.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "upstreams"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The build's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The build",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "upstream": {
           "$ref": "#/components/schemas/Upstream"
          }
         },
         "required": [
          "upstream"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/upstreams/{id}/pr": {
   "post": {
    "operationId": "trackUpstreamPr",
    "summary": "Track the upstream pull request",
    "description": "The pull request you opened upstream for this change: a github.com pull request URL on the copy's upstream. Graft checks it daily; once it's merged, the change counts as upstreamed and the next sync says so when it drops it.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "upstreams"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The build's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "url": {
          "type": "string",
          "examples": [
           "https://github.com/owner/name/pull/123"
          ]
         }
        },
        "required": [
         "url"
        ],
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Recorded",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "upstream": {
           "$ref": "#/components/schemas/Upstream"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "upstream"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/upstreams/{id}/review": {
   "post": {
    "operationId": "requestUpstreamReview",
    "summary": "Ask for Graft's reviewer",
    "description": "Asks for Graft's reviewer for upstreaming this change, an Enterprise service by contract and priced by email. It files a request that a person answers by email from support@graftfork.com. Nothing is charged.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "upstreams"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The build's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": false,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "note": {
          "type": "string",
          "description": "Anything the reviewer should know"
         }
        },
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Already asked",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "201": {
      "description": "Asked",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/copies/{id}/explain": {
   "get": {
    "operationId": "explainStop",
    "summary": "Why the last sync stayed",
    "description": "Plain words for what the last sync did and the next thing to do: resolve conflicts, ask the user a question, review a pull request, or something only the user can do.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "copies"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The explanation",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Explanation"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/runs/{id}": {
   "get": {
    "operationId": "getRun",
    "summary": "A sync's report",
    "description": "Needs the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "runs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The run's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The run with its report, tests, log and conflicts",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "$ref": "#/components/schemas/RunDetail"
          }
         },
         "required": [
          "run"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/runs/{id}/take": {
   "get": {
    "operationId": "takeRun",
    "summary": "How to take a move Graft delivered",
    "description": "Graft delivers a moved copy as a git bundle it keeps. This answers with a download link that works for an hour and the commands to fetch the moved branch into a clone of your copy.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "runs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The run's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The link and commands",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/runs/{id}/revert": {
   "post": {
    "operationId": "revertRun",
    "summary": "Revert a move",
    "description": "Puts the copy back where it was before this move: the release it was on and the branch Graft carried from. The revert is itself a move with review: it waits for the organization's approvals when the organization reviews moves (one more when it asks that of an agent's move), and for a person when a path policy holds it; the person whose token asked can't approve it. With no review to go through it is taken at once (200), and the copy is held below the reverted release until the next Sync now. Graft never changes your branch: if you already took the move, revert it there too (git revert, or GitHub's Revert button on its pull request). Refused (409) when the run isn't the move the copy is on now, is itself a revert, was taken before Graft kept the copy's earlier state, when a move waits for review or a sync is running, or when the earlier branch was delivered by Graft and is no longer kept.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "runs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The run's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": false,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "note": {
          "type": "string",
          "description": "Why, for the reviewers and the record (500 characters at most)"
         }
        },
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Taken: no review applied",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string"
          },
          "reverts": {
           "type": "string"
          },
          "base": {
           "type": "string"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run",
          "reverts"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "202": {
      "description": "Waiting for review",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string"
          },
          "reverts": {
           "type": "string"
          },
          "review": {
           "type": "string"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run",
          "reverts",
          "review"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/runs/{id}/record": {
   "get": {
    "operationId": "runRecord",
    "summary": "A run's signed record",
    "description": "What the run did, signed by graftfork.com with Ed25519: the upstream commit, your branch's tip and the moved head, each change's outcome, route, reason and who decided, the tests and their verdicts, the agents, and who approved. The record is the JSON text in payload; check it with graft verify-record against the keys at /.well-known/graft-record-key.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "runs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The run's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The signed record",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/copies/{id}/take": {
   "get": {
    "operationId": "takeCopy",
    "summary": "How to take the move a copy is on now",
    "description": "The move the copy is on now: its latest, or after a revert the one it went back to. 404 when there is none, or Graft keeps no bundle of the state a revert went back to (take an earlier move by its run id).\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "copies"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The link and commands",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/conflicts": {
   "get": {
    "operationId": "listPending",
    "summary": "What's waiting",
    "description": "Conflicting files waiting for your agent, and questions waiting for the user's decision, across your copies.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "conflicts"
    ],
    "responses": {
     "200": {
      "description": "Waiting items",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "conflicts": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/PendingConflict"
           }
          },
          "questions": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/PendingQuestion"
           }
          }
         },
         "required": [
          "conflicts",
          "questions"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/conflicts/{id}": {
   "get": {
    "operationId": "getConflict",
    "summary": "One conflict or question",
    "description": "For a conflict: the file before the change (base), with it (ours) and in the new release (theirs), and the change's recorded reason. For a question: its options.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "conflicts"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The conflict's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The conflict or question",
      "content": {
       "application/json": {
        "schema": {
         "oneOf": [
          {
           "$ref": "#/components/schemas/Conflict"
          },
          {
           "$ref": "#/components/schemas/Question"
          }
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/conflicts/{id}/resolution": {
   "post": {
    "operationId": "submitResolution",
    "summary": "Hand in a resolved file",
    "description": "The whole file as it should be on the new release, or `delete: true`. Graft stores it; once every file of that sync is in, it runs the sync again with them and moves the copy only if every change lands and the tests pass. A wrong resolution is caught by the tests or leaves the copy where it was.\n\nNeeds the `resolve` scope.",
    "x-graft-scope": "resolve",
    "tags": [
     "conflicts"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The conflict's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "content": {
          "type": "string",
          "description": "The whole resolved file"
         },
         "delete": {
          "type": "boolean",
          "description": "True when the file should not exist on the new release"
         }
        },
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Saved",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Saved"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "413": {
      "$ref": "#/components/responses/E413"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "resolve"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/conflicts/{id}/answer": {
   "post": {
    "operationId": "answerQuestion",
    "summary": "Answer a question",
    "description": "Answers a question an agent asked the user, with one of its options (1-based). Send only the option the user picked. The answer's file is checked by the tests like any rewrite.\n\nNeeds the `resolve` scope.",
    "x-graft-scope": "resolve",
    "tags": [
     "conflicts"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The question's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "option": {
          "type": "integer",
          "minimum": 1
         }
        },
        "required": [
         "option"
        ],
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Saved",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Saved"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "resolve"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/audit": {
   "get": {
    "operationId": "orgAudit",
    "summary": "An organization's audit log, signed",
    "description": "The organization's audit log, oldest first: who did what and when (members, roles, copies joining and leaving, review settings, every move held, approved, asked about, sent back or superseded, exports). Each entry has its copy in Graft's hash-chained ledger (seq and hash; null for entries from before the ledger copy, or whose ledger write failed), and the payload holds every ledger copy of the organization's entries after the cursor, so each hash can be recomputed and compared with the ledger's signed daily checkpoints at /.well-known/graft-ledger, and a copy no entry claims (an entry deleted, or stripped of its copy) is reported, as is a newer entry with no copy unless ledgerFailed marks its ledger write as failed. Signed like a run's record. POST /orgs/{id}/audit/verify checks the signature and recomputes every ledger hash; graft verify-record from npm checks the signature only (graftfork 0.3.1 and later read it as an audit log), and recomputing the hashes comes to the npm package in a later release. Up to 10,000 entries; after gives only the entries after that id, for a SIEM that polls. Only the organization's owners and reviewers may read it; a token limited to some copies can't. Each export is itself an entry (audit.exported).\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "account"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "after",
      "in": "query",
      "required": false,
      "description": "An audit entry's id: only newer entries",
      "schema": {
       "type": "integer",
       "minimum": 0
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The signed log: an envelope whose payload is the log's JSON text (graft_org_audit: 1)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/audit/stream": {
   "get": {
    "operationId": "orgAuditStream",
    "summary": "An organization's audit log as a stream, for a SIEM",
    "description": "One page of the organization's audit log as application/x-ndjson, oldest first: a header line ({type: header, graft_org_audit_stream: 1, org, after, key}); one line per entry ({type: entry, id, at, by, action, target, detail, ledger}) with its whole ledger copy (seq, ts, actor, action, subject, details, prev_hash, hash), or ledger null with ledgerFailed when its ledger write failed; an orphan line ({type: orphan, ledger}) for a ledger copy no entry claims (an entry deleted around the code); and a trailer ({type: trailer, next, more, count, sha256, sig}): sha256 is of every byte before the trailer line, sig is Ed25519 over those 32 bytes with the record key (/.well-known/graft-record-key). Poll with after set to the last trailer's next; more says whether to ask again now. Each entry's hash recomputes from its prev_hash and fields (POST /orgs/{id}/audit/verify checks the whole log; graft verify-record --stream, which checks a page, isn't in the npm package yet). Reading doesn't fill the log: the first read of a UTC day per token writes one audit.streamed entry, later ones don't. At most one read per 30 seconds per token (429 with Retry-After). Who: an owner or reviewer with a read token, or an organization's agent with the audit scope, which outlives any member. Never a token limited to some copies.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "after",
      "in": "query",
      "required": false,
      "description": "An audit entry's id: the trailer's next from the last page",
      "schema": {
       "type": "integer",
       "minimum": 0
      }
     },
     {
      "name": "limit",
      "in": "query",
      "required": false,
      "description": "Entries in the page, 1 to 1000 (default 500)",
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 1000
      }
     }
    ],
    "responses": {
     "200": {
      "description": "One page",
      "content": {
       "application/x-ndjson": {
        "schema": {
         "type": "string"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/agent-record": {
   "get": {
    "operationId": "agentRecord",
    "summary": "An organization's agent change record",
    "description": "Every change Graft carried, rewrote or dropped in a Team or Enterprise organization's copies over a date range: the repository and releases, the change and its recorded reason, who decided (Graft by rule, an agent and its models, or a person), resolutions handed in, the tests' verdict, who approved the move, the run's signed record and its transcripts' SHA-256s; then agents' registrations and resolutions through a token, and the organization's audit log. Signed like a run's record: check it with graft verify-record. Only the organization's owners and reviewers may read it.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "account"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "from",
      "in": "query",
      "required": false,
      "description": "The first day, YYYY-MM-DD (UTC). Default: 29 days before to",
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "to",
      "in": "query",
      "required": false,
      "description": "The last day, YYYY-MM-DD (UTC), included. Default: today. Up to 366 days in all",
      "schema": {
       "type": "string",
       "format": "date"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The signed bundle: an envelope whose payload is the record's JSON text",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/checks": {
   "get": {
    "operationId": "listChecks",
    "summary": "Your recent change checks",
    "description": "Needs the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "checks"
    ],
    "responses": {
     "200": {
      "description": "Newest first, up to 30, with this month's count",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "checks": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Check"
           }
          },
          "usage": {
           "type": "object",
           "additionalProperties": true
          }
         },
         "required": [
          "checks"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "createCheck",
    "summary": "Check a change against the project's own tests",
    "description": "Runs the project's own tests on the change (head) and on where it left the base, in a sandbox, and compares them: a failure the base already has is not blamed on the change. The result is pass, fail (the new failures by name, when the test output names them) or could_not_run (why). Nothing is pushed and no agent runs. Counts against the plan's monthly checks. For a GitHub repository Graft's app is installed on, give repo and either base and head or pr; for another host, clone_url with base and head (and a read token for a private repository).\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "checks"
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/CheckRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The same check is already queued or running",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "check": {
           "type": "string"
          },
          "status": {
           "type": "string"
          },
          "reused": {
           "type": "boolean"
          }
         },
         "required": [
          "check"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "202": {
      "description": "Queued",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "check": {
           "type": "string"
          },
          "status": {
           "type": "string"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "check"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "502": {
      "$ref": "#/components/responses/E502"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "parameters": [
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/checks/{id}": {
   "get": {
    "operationId": "getCheck",
    "summary": "A change check's result",
    "description": "Needs the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "checks"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The check's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The check, with both sides' runs and the end of their output",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "check": {
           "$ref": "#/components/schemas/Check"
          }
         },
         "required": [
          "check"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/usage": {
   "get": {
    "operationId": "getUsage",
    "summary": "Plan and this month's use",
    "description": "Needs the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "account"
    ],
    "responses": {
     "200": {
      "description": "Usage",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Usage"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/agents": {
   "post": {
    "operationId": "createAgentAccount",
    "summary": "An agent makes its own account and pays for it",
    "tags": [
     "agent accounts"
    ],
    "security": [],
    "description": "For an AI agent with a wallet and no person to sign up: one payment of 12 USD in USDC buys 30 days of Premium, and the same answer gives the new account and a personal API token with read, sync, resolve and pay (shown once). The token works for 90 days, and each renewal or top-up paid with it moves that out to 90 days again; its monthly cap (tokenMonthlyUsd) is the account's agent work for the month. The account works through this API and the MCP server at https://graftfork.com/mcp; it has no web sign-in and gets no email. Past the paid days it falls back to Free; renew with /agents/renew.\n\nPaid with x402 (version 2, scheme exact) in USDC on eip155:8453 (Base mainnet): send the request without PAYMENT-SIGNATURE and Graft answers 402 with the requirements; sign an EIP-3009 transferWithAuthorization for exactly that amount to that address and send the request again with PAYMENT-SIGNATURE. Graft verifies and settles it through an x402 facilitator, which pays the gas. A payment for another amount, asset, network or address is refused before it is settled, and one transaction counts once.",
    "parameters": [
     {
      "name": "PAYMENT-SIGNATURE",
      "in": "header",
      "required": false,
      "description": "The x402 payment: base64 of the JSON PaymentPayload {x402Version: 2, accepted, payload}, where accepted is the requirement from the 402 answer, unchanged, and payload is the signed EIP-3009 authorization. Leave it out to get the requirements.",
      "schema": {
       "type": "string"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "name": {
          "type": "string",
          "maxLength": 60,
          "description": "A name for the agent, shown on its token"
         },
         "contact": {
          "type": "string",
          "format": "email",
          "description": "An email address for a person responsible for the agent (optional)"
         }
        },
        "required": [
         "name"
        ],
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Paid: the account and its token",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "account": {
           "type": "string"
          },
          "token": {
           "type": "string",
           "description": "graft_..., shown once"
          },
          "scopes": {
           "type": "array",
           "items": {
            "type": "string"
           }
          },
          "premiumUntil": {
           "type": "string"
          },
          "transaction": {
           "type": "string"
          },
          "tokenExpiresAt": {
           "type": "string",
           "description": "When the token stops working (UTC) unless a renewal or top-up moves it"
          },
          "tokenMonthlyUsd": {
           "type": [
            "number",
            "null"
           ],
           "description": "The token's monthly cap on agent work"
          },
          "next": {
           "type": "object",
           "additionalProperties": true
          },
          "note": {
           "type": "string"
          }
         },
         "required": [
          "account",
          "token",
          "scopes",
          "premiumUntil",
          "transaction"
         ]
        }
       }
      }
     },
     "400": {
      "description": "No name, a bad contact address, or a PAYMENT-SIGNATURE without a readable EIP-3009 authorization (invalid_request); nothing settles",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "402": {
      "description": "Payment required. The body is the x402 v2 PaymentRequired object, also sent base64-encoded in the PAYMENT-REQUIRED header. It accepts one requirement: scheme exact, network eip155:8453, asset 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (USDC), amount 12000000 (12 USD, 6 decimals), payTo Graft's address. An error field says why a payment that was sent was refused.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      }
     },
     "409": {
      "description": "That payment or transaction already made an account, named in error.account (conflict); or it settled and isn't recorded yet (payment_unrecorded): send it again in a minute, and don't pay again",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Paid, but the token wasn't made (internal_error); error.transaction names the payment for support",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Payments aren't set up, or Graft isn't open to new accounts (unavailable)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/agents/renew": {
   "post": {
    "operationId": "renewAgentAccount",
    "summary": "Pay for more time on an agent account",
    "tags": [
     "agent accounts"
    ],
    "description": "Adds 30 days of Premium to the agent account that owns the bearer token, for 12 USD in USDC, and moves the token's expiry to 90 days from now. Only agent accounts can renew this way.\n\nPaid with x402 (version 2, scheme exact) in USDC on eip155:8453 (Base mainnet): send the request without PAYMENT-SIGNATURE and Graft answers 402 with the requirements; sign an EIP-3009 transferWithAuthorization for exactly that amount to that address and send the request again with PAYMENT-SIGNATURE. Graft verifies and settles it through an x402 facilitator, which pays the gas. A payment for another amount, asset, network or address is refused before it is settled, and one transaction counts once.",
    "parameters": [
     {
      "name": "PAYMENT-SIGNATURE",
      "in": "header",
      "required": false,
      "description": "The x402 payment: base64 of the JSON PaymentPayload {x402Version: 2, accepted, payload}, where accepted is the requirement from the 402 answer, unchanged, and payload is the signed EIP-3009 authorization. Leave it out to get the requirements.",
      "schema": {
       "type": "string"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "Paid",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "account": {
           "type": "string"
          },
          "premiumUntil": {
           "type": "string"
          },
          "transaction": {
           "type": "string"
          },
          "tokenExpiresAt": {
           "type": "string"
          },
          "tokenMonthlyUsd": {
           "type": [
            "number",
            "null"
           ]
          }
         },
         "required": [
          "account",
          "premiumUntil",
          "transaction"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "description": "No token, or not an agent account's token (unauthorized)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "402": {
      "description": "Payment required. The body is the x402 v2 PaymentRequired object, also sent base64-encoded in the PAYMENT-REQUIRED header. It accepts one requirement: scheme exact, network eip155:8453, asset 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (USDC), amount 12000000 (12 USD, 6 decimals), payTo Graft's address. An error field says why a payment that was sent was refused.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      }
     },
     "409": {
      "description": "That transaction was already used (conflict), or the payment settled and isn't recorded yet (payment_unrecorded)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Graft failed (internal_error)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Payments aren't set up (unavailable)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/topups": {
   "post": {
    "operationId": "buyAgentWork",
    "summary": "Buy more agent work, paid in USDC",
    "tags": [
     "agent accounts"
    ],
    "description": "One top-up: 10 USD in USDC adds 7 USD of Graft's agent work to the bearer token's account, usable this month and next, on Premium (the same as a top-up bought by card). Needs the pay scope: every agent account's token has it, and a person opts a token into it in /app Settings. Off Premium it answers 409 not_premium before asking for money. Sending the same PAYMENT-SIGNATURE again answers 200 with the credit it bought (replayed: true) and settles nothing. On an agent account's token it also moves the token's expiry to 90 days from now and raises its monthly cap to the account's agent work for the month. The MCP tool buy_agent_work does the same.\n\nPaid with x402 (version 2, scheme exact) in USDC on eip155:8453 (Base mainnet): send the request without PAYMENT-SIGNATURE and Graft answers 402 with the requirements; sign an EIP-3009 transferWithAuthorization for exactly that amount to that address and send the request again with PAYMENT-SIGNATURE. Graft verifies and settles it through an x402 facilitator, which pays the gas. A payment for another amount, asset, network or address is refused before it is settled, and one transaction counts once.",
    "parameters": [
     {
      "name": "PAYMENT-SIGNATURE",
      "in": "header",
      "required": false,
      "description": "The x402 payment: base64 of the JSON PaymentPayload {x402Version: 2, accepted, payload}, where accepted is the requirement from the 402 answer, unchanged, and payload is the signed EIP-3009 authorization. Leave it out to get the requirements.",
      "schema": {
       "type": "string"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "This payment was already counted: the credit it bought (replayed: true)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "201": {
      "description": "Paid: the credit",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "credit": {
           "type": "string"
          },
          "addsUsd": {
           "type": "number"
          },
          "paidUsd": {
           "type": "number"
          },
          "usableThrough": {
           "type": "string",
           "description": "The last day (UTC) it can pay for agent work"
          },
          "transaction": {
           "type": "string"
          },
          "agentWork": {
           "type": "object",
           "additionalProperties": true
          },
          "token": {
           "type": "object",
           "additionalProperties": true
          }
         },
         "required": [
          "credit",
          "addsUsd",
          "paidUsd",
          "usableThrough",
          "transaction"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "description": "A PAYMENT-SIGNATURE without a readable EIP-3009 authorization (payer and nonce), refused before anything settles (invalid_request)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "401": {
      "description": "No token (unauthorized)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "402": {
      "description": "Payment required. The body is the x402 v2 PaymentRequired object, also sent base64-encoded in the PAYMENT-REQUIRED header. It accepts one requirement: scheme exact, network eip155:8453, asset 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (USDC), amount 12000000 (12 USD, 6 decimals), payTo Graft's address. An error field says why a payment that was sent was refused.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      }
     },
     "403": {
      "description": "The token has no pay scope (insufficient_scope)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "409": {
      "description": "The account isn't on Premium (not_premium), the payment bought agent work for another account (conflict), or it settled and isn't recorded yet (payment_unrecorded): send it again in a minute, and don't pay again",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "429": {
      "description": "Rate limited (rate_limited)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "503": {
      "description": "Payments aren't set up (unavailable)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/orgs/{id}/policy": {
   "get": {
    "operationId": "getOrgPolicy",
    "summary": "An organization's path policy",
    "description": "The paths an agent's move may not change without a person: when a move an agent made (Graft's managed agent, or a resolution or answer handed in through the API or MCP) changes one, it is held for review with the matched paths, even when the organization doesn't review moves otherwise, and the signed record says which rule held it. Rules are globs; a copy's own rules and the repository's .graft/policy.yml (read at the commit the move started from) add to these. `people` holds a person's own moves too. Any member may read it.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The rules",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/OrgPolicy"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "put": {
    "operationId": "setOrgPolicy",
    "summary": "Add to an organization's path policy",
    "description": "Owners only. Replaces the rules with `paths` (a list of globs, or \"defaults\" for the offered set). Through a token the new rules must keep every rule there is, and can't turn `people` off: taking anything away answers 403 needs_person, and an owner does it in /app. Every change is in the organization's audit log and the ledger.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PolicyRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The rules now",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/OrgPolicy"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/copies/{id}/policy": {
   "get": {
    "operationId": "getCopyPolicy",
    "summary": "A copy's path policy",
    "description": "The copy's own protected paths, and its organization's, which also hold it. A copy in no organization is held by its own rules and its repository's .graft/policy.yml, and waits for its owner.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "copies"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The rules",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/CopyPolicy"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "put": {
    "operationId": "setCopyPolicy",
    "summary": "Add to a copy's path policy",
    "description": "Replaces the copy's own rules with `paths`. They add to its organization's and never take any away. Through a token the new rules must keep every rule the copy has (403 needs_person otherwise); its owner takes rules away in /app.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "copies"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The copy's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/PolicyRequest"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The rules now",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/CopyPolicy"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/alerts": {
   "get": {
    "operationId": "listForkAlerts",
    "summary": "An organization's fork watch alerts",
    "description": "Team and Enterprise. What fork watch found new in the accounts the organization watches, newest first: a security advisory newly affecting a fork (`advisory`, with its severity), a new upstream release a fork doesn't have (`behind`), and a fork whose changes stopped carrying by themselves (`carry`). Each alert appears once. `watching` lists the accounts scanned every day; `stopped` lists watched accounts Graft can't scan because its GitHub app was removed from them. Only the organization's members can read them (the MCP tool list_fork_alerts).\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "limit",
      "in": "query",
      "required": false,
      "description": "At most this many (1 to 200, default 100).",
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 200
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The alerts",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "org": {
           "type": "string"
          },
          "name": {
           "type": "string"
          },
          "watching": {
           "type": "array",
           "items": {
            "type": "string"
           }
          },
          "stopped": {
           "type": "array",
           "items": {
            "type": "string"
           }
          },
          "alerts": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ForkAlert"
           }
          }
         },
         "required": [
          "org",
          "alerts"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/agents": {
   "get": {
    "operationId": "listOrgAgents",
    "summary": "An organization's agents",
    "description": "The inventory of the organization's agents: identities the organization owns, not a person. Each with its scopes, team, copies, monthly amount, expiry, last use, what it spent this month, the person who made it (and whether they are still a member) and its owner of record, org:<id>. Owners only; the admin scope is opt-in and only an organization's owner can make a token with it.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The agents and the kill switch",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "agents": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/OrgAgent"
           }
          },
          "disabled": {
           "type": "boolean"
          },
          "disabledAt": {
           "type": [
            "string",
            "null"
           ]
          }
         },
         "required": [
          "agents",
          "disabled"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "createOrgAgent",
    "summary": "Make an agent for the organization",
    "description": "An agent the organization owns, and its first token, shown once. Its token keeps working when the person who made it leaves. It acts only on the organization's copies (copies narrows that), with read, sync and resolve at most: an agent can never review or approve a move, and it can't disconnect a copy (403 out_of_scope), since that deletes a member's copy, its conflicts, its kept moves and its read token. What it does is written in Graft's ledger as agent:<agent id> on behalf of org:<org id>, and in the organization's audit log as agent:<agent id>; a run it starts says so in its signed record (initiator kind agent, with agent and org). Owners only.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "name": {
          "type": "string",
          "maxLength": 60
         },
         "team": {
          "type": [
           "string",
           "null"
          ],
          "maxLength": 60,
          "description": "Free text: whose budget its runs count against"
         },
         "scopes": {
          "type": "array",
          "items": {
           "type": "string",
           "enum": [
            "read",
            "sync",
            "resolve",
            "audit"
           ]
          },
          "description": "audit: read the audit log and its stream (GET /orgs/{id}/audit/stream), for a SIEM; give a SIEM an agent with only audit"
         },
         "copies": {
          "type": "array",
          "items": {
           "type": "string"
          },
          "description": "Some of the organization's copies. Leave out for all of them, now and later"
         },
         "monthly_usd": {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 10000,
          "description": "The agent work a month the runs it starts may spend"
         },
         "expires_in_days": {
          "type": "integer",
          "minimum": 1,
          "maximum": 365
         },
         "expires_at": {
          "type": "string",
          "format": "date-time"
         }
        },
        "required": [
         "name",
         "scopes"
        ],
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "The agent, and its token (shown once)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "agent": {
           "$ref": "#/components/schemas/OrgAgent"
          },
          "token": {
           "type": "string"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "agent",
          "token"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/agents/{agent}": {
   "delete": {
    "operationId": "retireOrgAgent",
    "summary": "Retire an agent",
    "description": "Its tokens stop now. The agent stays in the inventory as retired, and what it did keeps its records. Owners only.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "agent",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "pattern": "^agt_[A-Za-z0-9_-]{6,40}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "Retired",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "retired": {
           "type": "string"
          }
         },
         "required": [
          "retired"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/agents/{agent}/rotate": {
   "post": {
    "operationId": "rotateOrgAgent",
    "summary": "A new token for an agent",
    "description": "Mints a new token, shown once; the agent's earlier token stops now. Owners only.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "agent",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "pattern": "^agt_[A-Za-z0-9_-]{6,40}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "The new token",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "agent": {
           "$ref": "#/components/schemas/OrgAgent"
          },
          "token": {
           "type": "string"
          },
          "revoked": {
           "type": "integer"
          }
         },
         "required": [
          "agent",
          "token"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/agents/disable": {
   "post": {
    "operationId": "disableOrgAgents",
    "summary": "Turn all of the organization's agents off",
    "description": "The kill switch: every agent of the organization stops at once (their tokens answer 401 agent_disabled), and the syncs they queued that haven't started are cancelled. A run already under way finishes, and a move it makes still waits for a person's review. Owners only.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "Off",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "disabled": {
           "type": "boolean"
          },
          "cancelled": {
           "type": "integer"
          }
         },
         "required": [
          "disabled"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/agents/enable": {
   "post": {
    "operationId": "enableOrgAgents",
    "summary": "Turn the organization's agents back on",
    "description": "They work again with the tokens they had. Owners only.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "On",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "disabled": {
           "type": "boolean"
          }
         },
         "required": [
          "disabled"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/budgets": {
   "get": {
    "operationId": "getOrgBudgets",
    "summary": "An organization's monthly caps",
    "description": "An owner's admin token reads it too. The organization's cap and each team's, with this month's spend under each. Caps apply in the order run (max_usd), token, agent, team, organization, fork. The organization's owners' webhooks get budget.nearing at 75% and 90% of a cap and budget.capped once no session can start under it, each once a month. Owners and reviewers.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The caps",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/OrgBudgets"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "put": {
    "operationId": "setOrgBudgets",
    "summary": "Set or clear an organization's caps",
    "description": "monthly_usd is the organization's cap; teams maps a team's name to its cap. null clears one; anything left out stays. A run counts toward the team of the agent that started it, or else of the copy's owner, as it was when the run was queued. Owners only.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "monthly_usd": {
          "type": [
           "number",
           "null"
          ],
          "exclusiveMinimum": 0,
          "maximum": 10000
         },
         "teams": {
          "type": "object",
          "additionalProperties": {
           "type": [
            "number",
            "null"
           ]
          }
         }
        },
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The caps now",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/OrgBudgets"
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/enterprise-auth": {
   "get": {
    "operationId": "getOrgEnterpriseAuth",
    "summary": "An organization's enterprise-managed authorization",
    "description": "The issuer the organization trusts for ID-JAG assertions (Claude's or its identity provider's), whether it is on, just-in-time accounts, the scopes and monthly cap a token from an assertion gets, the tenant ID to enter in Claude, the client id and audience to configure at an identity provider, and the latest refusal reason. Owners only. A token can read these settings but not change them: change them on the Team tab in /app, signed in with an authenticator code. See /docs/enterprise-auth.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The settings",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   },
   "put": {
    "operationId": "setOrgEnterpriseAuth",
    "summary": "Set an organization's enterprise-managed authorization (refused with a token)",
    "description": "Always 403 for an owner's token: the trusted issuer changes only on the Team tab in /app, in a session signed in with an authenticator code, because a token proves nothing about two-factor at the time of the change. Kept so a client gets a clear answer. See /docs/enterprise-auth.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "issuer": {
          "type": "string",
          "maxLength": 512
         },
         "enabled": {
          "type": "boolean"
         },
         "jit": {
          "type": "boolean"
         },
         "scopes": {
          "type": "array",
          "items": {
           "enum": [
            "read",
            "sync",
            "resolve"
           ]
          }
         },
         "monthly_usd": {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 1000
         }
        },
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The settings now",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   },
   "delete": {
    "operationId": "clearOrgEnterpriseAuth",
    "summary": "Turn off an organization's enterprise-managed authorization (refused with a token)",
    "description": "Always 403 for an owner's token: turn it off on the Team tab in /app, in a session signed in with an authenticator code.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "The settings now",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/members/team": {
   "post": {
    "operationId": "setMemberTeam",
    "summary": "Set a member's team",
    "description": "Free text (null or empty clears it). Syncs queued after this count toward the new team. Owners only.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "email": {
          "type": "string"
         },
         "team": {
          "type": [
           "string",
           "null"
          ],
          "maxLength": 60
         }
        },
        "required": [
         "email"
        ],
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Set",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "email": {
           "type": "string"
          },
          "team": {
           "type": [
            "string",
            "null"
           ]
          }
         },
         "required": [
          "email",
          "team"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/console": {
   "get": {
    "operationId": "getOrgConsole",
    "summary": "An organization's console overview",
    "description": "An owner's admin token reads it too. What the Team tab's overview shows: members (joined and invited), the owners and reviewers who count toward approvals, copies, moves waiting, agents, channels, this month's spend against the caps, the most approvals the organization can ask for now (review.room), moves its path policy held lately, and the newest audit entries. Owners and reviewers.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The overview",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/settings": {
   "get": {
    "operationId": "getOrgSettings",
    "summary": "An organization's settings",
    "description": "Any member reads them: defaults (what a copy takes when it joins the organization: delivery, follow, max_usd; what a spec connected into it takes: delivery, builds, max_usd), the review checklist shown to reviewers beside each move, quiet hours for the team channels (quiet.now says whether they are on now), and your own notification choices (notify.muted).\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The settings",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "put": {
    "operationId": "setOrgSettings",
    "summary": "Change an organization's settings",
    "description": "Owners only. Each field sent replaces what was there (null clears it); the rest stay. Everything is checked before anything is written; each change is written in the audit log (settings.defaults, settings.checklist, settings.quiet).\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "defaults": {
          "type": [
           "object",
           "null"
          ],
          "properties": {
           "delivery": {
            "enum": [
             "graft",
             "github"
            ]
           },
           "builds": {
            "enum": [
             1,
             2,
             3
            ]
           },
           "max_usd": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000
           },
           "follow": {
            "enum": [
             "all",
             "minor",
             "major"
            ]
           }
          },
          "additionalProperties": false
         },
         "checklist": {
          "type": [
           "string",
           "null"
          ],
          "maxLength": 2000
         },
         "quiet": {
          "type": [
           "object",
           "null"
          ],
          "properties": {
           "start": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$"
           },
           "end": {
            "type": "string",
            "pattern": "^\\d{2}:\\d{2}$"
           },
           "tz": {
            "type": "string",
            "description": "An IANA time zone; default UTC"
           },
           "through": {
            "type": "array",
            "items": {
             "enum": [
              "run.finished",
              "run.needs_person",
              "budget.nearing",
              "budget.capped",
              "audit.entry",
              "regen.finished",
              "review.overdue"
             ]
            },
            "description": "Events that go out during quiet hours anyway; left out, budget.capped"
           }
          },
          "required": [
           "start",
           "end"
          ]
         }
        },
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The settings now",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/notify": {
   "put": {
    "operationId": "setOrgNotify",
    "summary": "Your email from an organization's channels",
    "description": "The events you don't want from the organization's email channels; an empty list for all of them. Your own choice: any member. An owner's admin token works too.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "muted": {
          "type": "array",
          "items": {
           "enum": [
            "run.finished",
            "run.needs_person",
            "budget.nearing",
            "budget.capped",
            "audit.entry",
            "regen.finished",
            "review.overdue"
           ]
          }
         }
        },
        "required": [
         "muted"
        ],
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The settings now",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/audit/entries": {
   "get": {
    "operationId": "listOrgAudit",
    "summary": "An organization's audit log, filtered",
    "description": "The log newest first, 100 at a time (more gives the before for the next page), unsigned: for reading. action is one action or a prefix ending in a dot (move.); who and q match part of who did it and of the target or detail. For evidence, use GET /orgs/{id}/audit (signed). Owners and reviewers; an owner's admin token too.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "action",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "who",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "q",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "before",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 1
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Entries",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/audit/verify": {
   "post": {
    "operationId": "verifyOrgAudit",
    "summary": "Check an organization's audit log",
    "description": "Signs the log as GET /orgs/{id}/audit does, then checks it as graft verify-record does: the signature against Graft's published keys, each ledger entry's hash, each entry against its ledger copy, and no ledger copy left unclaimed. Answers ok, the counts and any problems. Written in the log as audit.verified. A check that doesn't trust this server: download the signed log and run graft verify-record. Owners and reviewers.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "The check",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean"
          },
          "signature": {
           "type": "boolean"
          },
          "entries": {
           "type": "integer"
          },
          "anchored": {
           "type": "integer"
          },
          "unanchored": {
           "type": "integer"
          },
          "failed": {
           "type": "integer"
          },
          "problems": {
           "type": "array",
           "items": {
            "type": "string"
           }
          }
         },
         "required": [
          "ok",
          "signature",
          "problems"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/team-stats": {
   "get": {
    "operationId": "getOrgTeamStats",
    "summary": "How the team works",
    "description": "What Graft saw of the organization's agents and people over the last 7, 30 or 90 days, nothing estimated: who started moves (the organization's agents, a person's agent token, people, the release schedule, ops), what became of them (landed, sent back, replaced, settled, waiting), hours from ready to decided (median and 90th percentile, by who started it), each reviewer's live assignments and decisions, Graft's agent work by team, agent and copy and per landed move (counted as /usage counts it), why runs stayed, agents' questions people answered and how fast, and approvals thrown away by a re-carry or a moved branch. Every number has its n; a median or per-move figure under 5 is null (too few to say). notMeasured lists what Graft can't see, on every answer. Owners and reviewers; an owner's admin token too.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "days",
      "in": "query",
      "required": false,
      "schema": {
       "enum": [
        7,
        30,
        90
       ]
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The tiles",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "window": {
           "type": "object",
           "additionalProperties": true
          },
          "tiles": {
           "type": "object",
           "additionalProperties": true
          },
          "notMeasured": {
           "type": "array",
           "items": {
            "type": "string"
           }
          }
         },
         "required": [
          "window",
          "tiles",
          "notMeasured"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/reviewers": {
   "get": {
    "operationId": "getOrgReviewers",
    "summary": "Who reviews what",
    "description": "Each owner and reviewer: moves assigned to them and waiting (live), how long the oldest has waited, decisions in the last 30 days, and the median hours from an assignment to their decision with its n (null under 5). Listed by email: for sharing work, not ranking people. Also the organization's routing settings. Owners and reviewers; an owner's admin token too.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The reviewers",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "people": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "email": {
              "type": "string"
             },
             "role": {
              "type": "string"
             },
             "team": {
              "type": [
               "string",
               "null"
              ]
             },
             "live": {
              "type": "integer"
             },
             "oldestHours": {
              "type": [
               "number",
               "null"
              ]
             },
             "decisions30d": {
              "type": "integer"
             },
             "medianHoursToDecide": {
              "type": [
               "number",
               "null"
              ]
             },
             "decidedN": {
              "type": "integer"
             }
            }
           }
          },
          "routing": {
           "type": "object",
           "additionalProperties": true
          }
         },
         "required": [
          "people"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/review": {
   "get": {
    "operationId": "getOrgReview",
    "summary": "Moves waiting for review, with what to read",
    "description": "The organization's moves waiting for review in risk order (a path policy held it, tests not passing or not run, files an agent rewrote, changes new since the copy's last approved move, an agent started it, waiting longest), each with reading: read (what a reviewer must read and why, each with its source), skip (carried changes they can skip and why: dropped because the release contains them, or carried as approved before), since (the move it is compared with; reviewed and second say whether it was reviewed and whether a second person approved it), matchedBy (patch id or name), tests, policy, startedBy, diff (a GitHub compare link, or why there is none), batchable and order {rank, why}. It says what changed, not what is correct. Approving stays a person's, signed in to /app. Owners and reviewers; an owner's admin token too; never an organization's agent.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The queue",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "org": {
           "type": "string"
          },
          "moves": {
           "type": "array",
           "items": {
            "type": "object",
            "additionalProperties": true
           }
          },
          "checklist": {
           "type": [
            "string",
            "null"
           ]
          },
          "empty": {
           "type": [
            "string",
            "null"
           ]
          }
         },
         "required": [
          "org",
          "moves"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/channels": {
   "get": {
    "operationId": "listOrgChannels",
    "summary": "An organization's team channels",
    "description": "Each channel's destination (kind webhook, slack, teams or email; the host or the recipients), its events, its team, whether it is on (a webhook turns off after 20 failed posts in a row), and its recent deliveries. Owners and reviewers; an owner's admin token too.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The channels",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "createOrgChannel",
    "summary": "Add a team channel",
    "description": "Owners only. A destination the organization owns, so it keeps working when the person who made it leaves. kind webhook posts Graft's signed JSON (the secret is in the answer, once); slack takes a hooks.slack.com incoming webhook and teams a Teams Workflows or connector webhook, each getting a formatted message; email goes to recipients, who must be members (each can mute events for themselves). The address is checked as any webhook's: https on 443, never a private or internal address, by DNS before every post. events defaults to all but audit.entry. team limits it to that team's events (a run charged to the team, its budget or its agent's, a rebuild by a member of it); organization-wide events go only to channels with no team. Quiet hours hold posts and email until they end, except the events they let through (budget.capped unless an owner chose otherwise).\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "name": {
          "type": "string",
          "maxLength": 60
         },
         "kind": {
          "enum": [
           "webhook",
           "slack",
           "teams",
           "email"
          ]
         },
         "url": {
          "type": "string",
          "format": "uri"
         },
         "recipients": {
          "type": "array",
          "items": {
           "type": "string",
           "format": "email"
          }
         },
         "events": {
          "type": "array",
          "items": {
           "enum": [
            "run.finished",
            "run.needs_person",
            "budget.nearing",
            "budget.capped",
            "audit.entry",
            "regen.finished",
            "review.overdue"
           ]
          }
         },
         "team": {
          "type": [
           "string",
           "null"
          ],
          "maxLength": 60
         }
        },
        "required": [
         "name",
         "kind"
        ],
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "The channel, and a webhook channel's secret",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/channels/{channel}": {
   "put": {
    "operationId": "updateOrgChannel",
    "summary": "Change a team channel",
    "description": "Owners only: name, events, team (null: every team), recipients (email channels) or paused. The address can't change: add a new channel.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "channel",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "pattern": "^ch_[A-Za-z0-9_-]{6,40}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "name": {
          "type": "string"
         },
         "events": {
          "type": "array",
          "items": {
           "enum": [
            "run.finished",
            "run.needs_person",
            "budget.nearing",
            "budget.capped",
            "audit.entry",
            "regen.finished",
            "review.overdue"
           ]
          }
         },
         "team": {
          "type": [
           "string",
           "null"
          ]
         },
         "recipients": {
          "type": "array",
          "items": {
           "type": "string"
          }
         },
         "paused": {
          "type": "boolean"
         }
        },
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The channel",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   },
   "delete": {
    "operationId": "deleteOrgChannel",
    "summary": "Remove a team channel",
    "description": "Owners only: the channel, its webhook and what waits to be sent go.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "channel",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "pattern": "^ch_[A-Za-z0-9_-]{6,40}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "Removed",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "removed": {
           "type": "string"
          }
         },
         "required": [
          "removed"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/channels/{channel}/test": {
   "post": {
    "operationId": "testOrgChannel",
    "summary": "Send a test to a team channel",
    "description": "Owners only: one post (or one email to each recipient) now, whatever the channel's events, quiet hours or pause. One that gets through turns a webhook that went off back on. 502 when it didn't get through, with why.\n\nNeeds the `admin` scope.\n\nAn API token only: OAuth can't grant the `admin` scope.",
    "x-graft-scope": "admin",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "channel",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "pattern": "^ch_[A-Za-z0-9_-]{6,40}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "Sent",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "502": {
      "$ref": "#/components/responses/E502"
     }
    },
    "security": [
     {
      "token": []
     }
    ]
   }
  },
  "/orgs/{id}/usage": {
   "get": {
    "operationId": "getOrgUsage",
    "summary": "An organization's spend by team, agent and member",
    "description": "An owner's admin token reads it too. Graft's agent work the organization's runs spent in a month (this one by default), for chargeback: by team, by agent (runs an agent started) and by member (the copy's owner, for runs no agent started). Owners and reviewers.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "orgs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The organization's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "month",
      "in": "query",
      "required": false,
      "description": "YYYY-MM (UTC). Default: this month",
      "schema": {
       "type": "string",
       "pattern": "^\\d{4}-\\d{2}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Spend",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "month": {
           "type": "string"
          },
          "totalUsd": {
           "type": "number"
          },
          "teams": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "team": {
              "type": [
               "string",
               "null"
              ]
             },
             "runs": {
              "type": "integer"
             },
             "spentUsd": {
              "type": "number"
             }
            }
           }
          },
          "agents": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "agent": {
              "type": "string"
             },
             "name": {
              "type": [
               "string",
               "null"
              ]
             },
             "team": {
              "type": [
               "string",
               "null"
              ]
             },
             "runs": {
              "type": "integer"
             },
             "spentUsd": {
              "type": "number"
             },
             "monthlyUsd": {
              "type": [
               "number",
               "null"
              ]
             },
             "retired": {
              "type": "boolean"
             }
            }
           }
          },
          "members": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "email": {
              "type": "string"
             },
             "runs": {
              "type": "integer"
             },
             "spentUsd": {
              "type": "number"
             }
            }
           }
          }
         },
         "required": [
          "month",
          "totalUsd",
          "teams",
          "agents",
          "members"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/upgrades": {
   "get": {
    "operationId": "listUpgrades",
    "summary": "Your dependency upgrades",
    "description": "Newest first, up to 50. When more is true, ask again with before=next_before for older ones.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "upgrades"
    ],
    "parameters": [
     {
      "name": "before",
      "in": "query",
      "required": false,
      "description": "A upgrade's id from the last answer's next_before: only older ones.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Newest first, up to 50. When more is true, ask again with before=next_before for older ones.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "upgrades": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Upgrade"
           }
          },
          "more": {
           "type": "boolean",
           "description": "Older ones remain: ask again with before=next_before"
          },
          "next_before": {
           "type": [
            "string",
            "null"
           ],
           "description": "The last item's id when more is true, else null"
          }
         },
         "required": [
          "upgrades",
          "more",
          "next_before"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "startUpgrade",
    "summary": "Upgrade a dependency, checked by the tests",
    "description": "Upgrades one dependency of a connected copy (npm: package.json and package-lock.json; Python: requirements files and pyproject.toml) on a new branch, graft/upgrade/<dependency>-<version>, and runs the project's tests before and after. The upgrade is delivered (kept by Graft for you to take, or as a pull request on a copy that chose them) only if it adds no failing test. When it breaks tests on Premium, Team or Enterprise, Graft's agent changes the project's code (never its tests) and the tests run again. Counts as one sync against the monthly limit.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "upgrades"
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/UpgradeRequest"
       }
      }
     }
    },
    "responses": {
     "202": {
      "description": "Started",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "upgrade": {
           "type": "string"
          },
          "run": {
           "type": "string"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "upgrade",
          "run",
          "message"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "parameters": [
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/upgrades/{id}": {
   "get": {
    "operationId": "getUpgrade",
    "summary": "One upgrade and its report",
    "description": "Needs the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "upgrades"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The upgrade's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The upgrade",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "upgrade": {
           "$ref": "#/components/schemas/Upgrade"
          }
         },
         "required": [
          "upgrade"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/upgrades/{id}/take": {
   "get": {
    "operationId": "takeUpgrade",
    "summary": "How to take a delivered upgrade",
    "description": "A download link that works for an hour and the commands that fetch graft/upgrade/<dependency>-<version> into a clone of the copy.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "upgrades"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The upgrade's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The link and commands",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen": {
   "get": {
    "operationId": "listSpecs",
    "summary": "Your specs",
    "description": "Specs are repositories whose program is kept in a duramen record. Graft rebuilds the implementation blind from the record's brief and keeps a rebuild only when the record's own suite passes every case.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "responses": {
     "200": {
      "description": "Your specs, and whether rebuilds are open to you",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "available": {
           "type": "boolean"
          },
          "specs": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Spec"
           }
          }
         },
         "required": [
          "available",
          "specs"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "connectSpec",
    "summary": "Connect a spec",
    "description": "A repository Graft's GitHub app can see (`repo`) or any https git address (`clone_url`, with `token` and `username` for a private one), the branch, the record's path (a .duramen file or folder) and the implementation's folder and language. Premium, Team and Enterprise only.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/ConnectSpec"
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Connected",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "id": {
           "type": "string"
          },
          "repo": {
           "type": "string"
          },
          "report": {
           "type": "string",
           "description": "The free report queued on connect: read it with GET /regen/{id}/report once it finishes. Absent when the record wasn't graded (message says why)."
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "id"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "parameters": [
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}": {
   "get": {
    "operationId": "getSpec",
    "summary": "One spec, its rebuilds, and its desired and observed state",
    "description": "Needs the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The spec",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "spec": {
           "$ref": "#/components/schemas/Spec"
          },
          "runs": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Rebuild"
           }
          },
          "desired": {
           "$ref": "#/components/schemas/SpecDesired"
          },
          "observed": {
           "$ref": "#/components/schemas/SpecObserved"
          }
         },
         "required": [
          "spec",
          "runs"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "put": {
    "operationId": "setSpec",
    "summary": "Set a spec's desired state",
    "description": "The fields you send are set and the rest stay as they are; a field that isn't a setting is refused (400). `max_usd` is the most one rebuild may spend when nothing else says, never more than Graft's own cap ($2 by default) and at least $0.50, one builder's floor; null gives the cap back. Unpausing a spec on someone else's installation is refused (403), and a pull request (`delivery: github`) needs Graft's GitHub app on the repository.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SpecDesired"
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "The desired and observed state",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "spec": {
           "type": "string"
          },
          "desired": {
           "$ref": "#/components/schemas/SpecDesired"
          },
          "observed": {
           "$ref": "#/components/schemas/SpecObserved"
          }
         },
         "required": [
          "spec",
          "desired",
          "observed"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "delete": {
    "operationId": "disconnectSpec",
    "summary": "Disconnect a spec",
    "description": "As /app's Disconnect: its queued rebuilds are cancelled and its kept rebuilds and any read token are deleted. Its rebuilds' signed records stay.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "Disconnected",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "deleted": {
           "type": "string"
          }
         },
         "required": [
          "deleted"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/webhooks": {
   "get": {
    "operationId": "listWebhooks",
    "summary": "Your webhooks",
    "description": "Each webhook's host (never its URL), events, whether it's on, and its last ten deliveries.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "webhooks"
    ],
    "responses": {
     "200": {
      "description": "Your webhooks and the event types",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "hooks": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Webhook"
           }
          },
          "events": {
           "type": "array",
           "items": {
            "type": "string",
            "enum": [
             "run.finished",
             "run.needs_person",
             "regen.finished",
             "budget.capped",
             "budget.nearing",
             "audit.entry",
             "review.overdue"
            ]
           }
          }
         },
         "required": [
          "hooks",
          "events"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "createWebhook",
    "summary": "Add a webhook",
    "description": "Graft posts the events you name to this https URL when a sync or rebuild of yours ends or needs a person (see the webhooks section of this description for each event's body). The URL must be https on port 443, with no credentials, and must not resolve to a private or internal address; it is checked again before every post, and redirects are never followed. Each post is signed: Graft-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\" under the secret>. Refuse a t more than five minutes old. Graft-Delivery is the same on every retry of one delivery. A failed post is tried again after 1, 5 and 30 minutes, 2, 6 and 12 hours; after 20 failed posts in a row the webhook is turned off, and a test that gets through turns it back on. A webhook lasts as long as the token that made it: when that token is revoked or expires, the webhook is turned off for good and its pending posts are dropped. The secret is in this answer only. Up to 5 webhooks per person.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "webhooks"
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "url": {
          "type": "string",
          "examples": [
           "https://agent.example.com/graft"
          ]
         },
         "events": {
          "type": "array",
          "items": {
           "type": "string",
           "enum": [
            "run.finished",
            "run.needs_person",
            "regen.finished",
            "budget.capped",
            "budget.nearing",
            "audit.entry",
            "review.overdue"
           ]
          },
          "description": "Leave out for all but audit.entry, which you name to get it"
         },
         "org": {
          "type": "string",
          "description": "An organization you own: audit.entry for that one only. Leave out for every organization you own"
         }
        },
        "required": [
         "url"
        ],
        "additionalProperties": false
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Added",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "hook": {
           "$ref": "#/components/schemas/Webhook"
          },
          "secret": {
           "type": "string",
           "description": "whsec_...: shown only here"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "hook",
          "secret"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "parameters": [
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/webhooks/{id}": {
   "get": {
    "operationId": "getWebhook",
    "summary": "A webhook and its deliveries",
    "description": "Its deliveries: Newest first, up to 50. When hook.deliveriesMore is true, ask again with before=hook.deliveriesNextBefore for older ones.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "webhooks"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The webhook's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "before",
      "in": "query",
      "required": false,
      "description": "A delivery's id from the last answer's next_before: only older ones.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The webhook with a page of its deliveries",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "hook": {
           "$ref": "#/components/schemas/Webhook"
          }
         },
         "required": [
          "hook"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "delete": {
    "operationId": "deleteWebhook",
    "summary": "Remove a webhook",
    "description": "Needs the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "webhooks"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The webhook's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "Removed, with its deliveries",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "deleted": {
           "type": "string"
          }
         },
         "required": [
          "deleted"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/webhooks/{id}/test": {
   "post": {
    "operationId": "testWebhook",
    "summary": "Post a test event now",
    "description": "Posts a ping, signed like any event, and answers with what the URL answered. A ping that gets through turns a webhook that was off back on, unless the token that made it was revoked or expired (409).\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "webhooks"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The webhook's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "200": {
      "description": "Delivered",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "hook": {
           "$ref": "#/components/schemas/Webhook"
          },
          "test": {
           "type": "string"
          }
         },
         "required": [
          "hook",
          "test"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "422": {
      "$ref": "#/components/responses/E422"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "502": {
      "$ref": "#/components/responses/E502"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/run": {
   "post": {
    "operationId": "rebuildSpec",
    "summary": "Rebuild now",
    "description": "Queues a rebuild: a brief from the record, blind builds from the brief alone, then the record's own suite and a hidden holdout. Spends agent work (at most $2 a rebuild by default). The rebuild is kept only if every case passes. One identical to a passing rebuild (same record, model, builds and duramen) stops after its brief as unchanged, unless force. With max_usd, when the spec's estimate (the median of its past rebuilds, as GET /regen/{id} shows) is more than it, it answers 409 with code price_quote and estimateUsd, capUsd, workLeftUsd and mostUsd, and nothing is spent; ask again with a higher max_usd. Without max_usd it is never quoted: it runs on the default (or the spec's own max_usd from PUT /regen/{id} when that is lower), and the message says when the estimate is over it. A free report still on its way is stopped (stoppedReport): the rebuild's brief checks the record instead.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "requestBody": {
     "required": false,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "force": {
          "type": "boolean",
          "description": "Rebuild even when nothing changed since a passing rebuild"
         },
         "max_usd": {
          "type": "number",
          "minimum": 0,
          "description": "The most this rebuild may spend, in dollars, in place of the default ($2). It may be lower, or higher up to your agent work left and at most $10."
         },
         "reason": {
          "type": "string",
          "maxLength": 500,
          "description": "Why you want this run, in words (at most 500 characters). Kept on the run and signed into its record as reason {text, from: asker}; GET /regen/runs/{id} shows it. A rebuild a push starts takes the record commit's message instead (from commit)."
         }
        }
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "202": {
      "description": "Queued",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string"
          },
          "capUsd": {
           "type": "number",
           "description": "The most this rebuild may spend"
          },
          "estimateUsd": {
           "type": [
            "number",
            "null"
           ],
           "description": "What it is likely to cost, or null with no rebuilds to go on"
          },
          "stoppedReport": {
           "type": "string",
           "description": "The free report (grade) that was still on its way and was stopped for this rebuild, whose brief checks the record instead; the message says so too"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/runs/{id}": {
   "get": {
    "operationId": "getRebuild",
    "summary": "A rebuild",
    "description": "Its builds and their verdicts, the suite's result, duramen agree across builds, the choices the builder recorded and where they differ from the previous passing rebuild, and for a rebuild that passed, a download link (an hour) and the commands that fetch graft/regen/<id> into your clone. It also says how isolated the rebuild was (judgeSandbox, builderNetwork, buildersOffline), a Rust or Go rebuild's compile seconds on the judge (compile, from summary.rust or summary.go), and who started it (initiator), the same as its signed record. Each read also has Graft look at running builders (at most once every 30 seconds), so polling moves a rebuild to its judge as soon as its builds are done instead of at the next 10-minute check.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The rebuild's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The rebuild",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "$ref": "#/components/schemas/Rebuild"
          }
         },
         "required": [
          "run"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/runs/{id}/record": {
   "get": {
    "operationId": "rebuildRecord",
    "summary": "A rebuild's signed record",
    "description": "What the rebuild did, signed by graftfork.com with Ed25519: the commit and the record it rebuilt from, duramen's release, how blind the builders were, each build's model, cost and verdict, the suite's result, and what was delivered. Yours, or any rebuild of a spec whose results are public. The record is the JSON text in payload; check it with graft verify-record (a saved file, or the rebuild's id) against the keys at /.well-known/graft-record-key. For a few minutes after a rebuild ends its record is being signed: the answer is then 409 with code signing and a Retry-After, so ask again then.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The rebuild's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The signed record",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/runs/{id}/rejudge": {
   "post": {
    "operationId": "rejudgeRebuild",
    "summary": "Judge a rebuild's builds again",
    "description": "The neutral referee: a finished rebuild's builds, as Graft stored them, judged again by today's runner at that rebuild's commit and against the brief its builders were given, with the record's own suite and a hidden holdout, by the same rules as the rebuild's own judge. It queues a new run (trigger rejudge) that names the rebuild in rejudgeOf; read it with GET /regen/runs/{id}, which shows the seed and, per build, whether its verdict matches the original's. It costs one of Graft's runner sessions and no builders, so it spends no agent work. It delivers nothing (no branch, no pull request, no email) and never changes the rebuild it judges. Refused (409) while that rebuild hasn't finished, when it has no builds or Graft no longer keeps them, or while another run of the spec is queued or running; 503 until Graft's runner that can do it is deployed.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "requestBody": {
     "required": false,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "same_seed": {
          "type": "boolean",
          "description": "Draw the hidden holdout with the original rebuild's seed (the same hidden requests). Default false: a fresh seed."
         },
         "duramen_ref": {
          "type": "string",
          "description": "The duramen release (a tag such as v0.3.0, or a full commit) to judge with. Default and, for now, the only one taken: the one the rebuild ran on (its tag or the commit it resolved to). Another answers 409, since duramen writes its version into the brief and another release's brief never matches the one the builders were given."
         }
        }
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The rebuild's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "202": {
      "description": "Queued",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string",
           "description": "The re-judge's id"
          },
          "rejudgeOf": {
           "type": "string"
          },
          "seed": {
           "type": "integer",
           "description": "The hidden holdout's seed"
          },
          "sameSeed": {
           "type": "boolean"
          },
          "duramen": {
           "type": "string"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run",
          "rejudgeOf",
          "seed"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/drafts": {
   "get": {
    "operationId": "listDrafts",
    "summary": "A spec's drafts",
    "description": "Edit the record on a new branch of your repository and push it. Check the branch (free: the grade and duramen check's errors), then rebuild it (priced as a rebuild) and read how its answers differ from the spec's last kept build. If you like it, merge the branch yourself; Graft never writes your branches. Each draft is a check (kind check: its grade and reasons) or a rebuild (kind rebuild: its verdict, the kept rebuild it is compared with in compareTo, and once the judge compares them, unexplained: {count, of}, the requests whose answers changed where the old and new records' oracles agree, so nobody asked for the change, of the requests asked). Newest first, up to 20.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "before",
      "in": "query",
      "required": false,
      "description": "A draft's id from the last answer's next_before: only older ones.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The drafts. Newest first, up to 20. When more is true, ask again with before=next_before for older ones.",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "spec": {
           "type": "string"
          },
          "branch": {
           "type": "string",
           "description": "The spec's own branch"
          },
          "lastKept": {
           "type": [
            "string",
            "null"
           ],
           "description": "The spec's last kept rebuild, which a new draft is compared with"
          },
          "drafts": {
           "type": "array",
           "items": {
            "type": "object",
            "additionalProperties": true
           }
          },
          "more": {
           "type": "boolean",
           "description": "Older ones remain: ask again with before=next_before"
          },
          "next_before": {
           "type": [
            "string",
            "null"
           ],
           "description": "The last item's id when more is true, else null"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "spec",
          "drafts",
          "more",
          "next_before"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "rebuildDraft",
    "summary": "Rebuild from the record on a branch",
    "description": "A draft rebuild: the record as edited on another branch of the spec's repository, rebuilt blind with the spec's record path, implementation folder and language, judged by that record's own suite and hidden holdout, and kept only if every case passes. Priced like a rebuild (max_usd, 409 price_quote), one run per spec at a time. The branch must exist in the repository (404 when Graft's GitHub app can't find it). A passing draft waits as a bundle on graft/regen/<run>: never a pull request, never written to the branch or the spec's branch. It never becomes the spec's last kept build, never counts for skip-unchanged, and never shows on the public /regen page. Its detail (GET /regen/runs/{id}) names compareTo, the spec's previous kept rebuild, and carries behaviourDiff once the judge compares their answers (see Rebuild.behaviourDiff). Changing behaviour usually means editing the oracle too, which is code a person approves.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "branch": {
          "type": "string",
          "examples": [
           "change/green-at-78"
          ],
          "description": "The branch with your edited record"
         },
         "max_usd": {
          "type": "number",
          "minimum": 0,
          "description": "The most this rebuild may spend, as for POST /regen/{id}/run"
         },
         "reason": {
          "type": "string",
          "maxLength": 500,
          "description": "Why you want this run, in words (at most 500 characters). Kept on the run and signed into its record as reason {text, from: asker}; GET /regen/runs/{id} shows it. A rebuild a push starts takes the record commit's message instead (from commit)."
         }
        },
        "required": [
         "branch"
        ]
       }
      }
     }
    },
    "responses": {
     "202": {
      "description": "Queued",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string"
          },
          "branch": {
           "type": "string"
          },
          "commit": {
           "type": [
            "string",
            "null"
           ],
           "description": "The branch's head, when Graft could read it"
          },
          "compareTo": {
           "type": [
            "string",
            "null"
           ]
          },
          "capUsd": {
           "type": "number"
          },
          "estimateUsd": {
           "type": [
            "number",
            "null"
           ]
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run",
          "branch"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/drafts/check": {
   "post": {
    "operationId": "checkDraft",
    "summary": "Grade the record on a branch",
    "description": "A draft check: duramen check and duramen mutate on the record as edited on another branch, by a runner with no model, as a spec report (free for the first 20 reports a day, then max_usd as for POST /regen/{id}/report). Its grade, reasons and duramen check's errors stay on the check (GET /regen/runs/{id}, report), never on the spec. Check before you rebuild: it spends nothing.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "branch": {
          "type": "string",
          "examples": [
           "change/green-at-78"
          ]
         },
         "max_usd": {
          "type": "number",
          "minimum": 0,
          "description": "The most this check may cost. Default 0: only a free one."
         },
         "reason": {
          "type": "string",
          "maxLength": 500,
          "description": "Why you want this run, in words (at most 500 characters). Kept on the run and signed into its record as reason {text, from: asker}; GET /regen/runs/{id} shows it. A rebuild a push starts takes the record commit's message instead (from commit)."
         }
        },
        "required": [
         "branch"
        ]
       }
      }
     }
    },
    "responses": {
     "202": {
      "description": "Queued",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string"
          },
          "branch": {
           "type": "string"
          },
          "commit": {
           "type": [
            "string",
            "null"
           ]
          },
          "priceUsd": {
           "type": "number"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run",
          "branch"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders": {
   "get": {
    "operationId": "listOrders",
    "summary": "A spec's change orders",
    "description": "Change the record without a branch: start a change order, write whole files of the record's folder into it, check it (free), then rebuild from it. A person approves, in /app first, any change an agent wrote or started and any change to the oracle or a file the judge reads; an agent can't. A kept rebuild waits as a bundle with two commits, the record change then the implementation; the record change alone is a patch at any time. Graft never writes the user's repository. Newest first, up to 30, each in plain fields (no texts): state (draft, checked, rebuilt, taken or discarded), source (person or agent, and createdByToken), files changed, the last check, approval ({needed, approved, at, how}) and the rebuild. ready is false while checking and rebuilding aren't open yet (they answer 503 ORDERS_NOT_READY).\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The orders",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "spec": {
           "type": "string"
          },
          "ready": {
           "type": "boolean"
          },
          "orders": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ChangeOrder"
           }
          }
         },
         "required": [
          "spec",
          "orders"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "createOrder",
    "summary": "Start a change order",
    "description": "A new change order at the spec branch's head, written as your agent's (source agent, the token and client in its history and Graft's ledger, and an organization's spec's audit log). files is optional: whole texts, as for PUT .../files. A spec connected by address can't have orders yet (409). At most 10 open orders a spec and 30 an account; creates, saves, reads and patches count against 120 an hour.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "title": {
          "type": "string",
          "maxLength": 120,
          "description": "One line: it becomes the record change's commit subject",
          "examples": [
           "Green starts at 78 F"
          ]
         },
         "files": {
          "type": "array",
          "maxItems": 40,
          "items": {
           "$ref": "#/components/schemas/OrderFile"
          }
         }
        },
        "required": [
         "title"
        ]
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Started",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "order": {
           "$ref": "#/components/schemas/ChangeOrder"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "order"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "413": {
      "$ref": "#/components/responses/E413"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/draft": {
   "post": {
    "operationId": "draftOrder",
    "summary": "Draft a change order from one line",
    "description": "The hosted drafter: a new change order (source drafter) and one drafting session on Graft's agents. The session has no network and sees only the record's folder at the branch's head (never the implementation) and duramen; it edits the record, and the oracle when the change needs it, runs duramen check until it reports no errors or its budget ends, and hands back whole files. Graft checks them (paths a change order may hold, 40 files, 256 KB a file, 1 MB in all, a one-line title) and saves them into the order; follow it with GET .../orders/{order} (drafting: state drafting, drafted or failed, costUsd, why). Then it is an ordinary order: check it, a person approves any oracle change in /app (the drafter and an agent can't), rebuild. Spends agent work: at most $0.75 by default or max_usd (at most $3), never more than the account has left this month; the answer's estimate says what. 503 DRAFTER_NOT_READY until the drafter is open. When Anthropic didn't answer as the session was started, it may be running: the answer is still 202, with code draft_pending, the order, retryable true and a Retry-After (and retryAfter) of 300 seconds. Read the order after that (GET .../orders/{order}) and don't start another draft; an Idempotency-Key retry gets the same 202.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "intent": {
          "type": "string",
          "maxLength": 500,
          "description": "One line: what should change",
          "examples": [
           "green starts at 78 F instead of 80, keep the other bands"
          ]
         },
         "max_usd": {
          "type": "number",
          "minimum": 0.5,
          "maximum": 3,
          "description": "The most this drafting session may spend, in place of the default $0.75"
         }
        },
        "required": [
         "intent"
        ]
       }
      }
     }
    },
    "responses": {
     "202": {
      "description": "Drafting, or (code draft_pending) a session that may have started",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "code": {
           "type": "string",
           "enum": [
            "draft_pending"
           ],
           "description": "Only when the session may have started: see Retry-After"
          },
          "order": {
           "$ref": "#/components/schemas/ChangeOrder"
          },
          "session": {
           "type": "object",
           "additionalProperties": true
          },
          "estimate": {
           "type": "object",
           "additionalProperties": true
          },
          "retryAfter": {
           "type": "integer"
          },
          "retryable": {
           "type": "boolean"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "order",
          "message"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       },
       "Retry-After": {
        "$ref": "#/components/headers/RetryAfter"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "413": {
      "$ref": "#/components/responses/E413"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/order-files": {
   "get": {
    "operationId": "orderFiles",
    "summary": "The record's folder",
    "description": "The files a change order may change: the record's folder at the branch's head (or with order, at that order's base), [{path, role, size, editable}], the record first. role is record (*.duramen), oracle (code the judge runs) or data. With path, one file's whole text, to edit and send back whole. With order, a file the order changes is the order's own edited text (edited: true, with baseBlob), and the listing marks such files edited: true, so a second whole-text write starts from the first. Read through Graft's GitHub app with a read-only token; a path outside the record's folder is 400.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "path",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "order",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "at",
      "in": "query",
      "required": false,
      "description": "base: with order and path, the text at the order's base even for a file the order changes",
      "schema": {
       "type": "string",
       "enum": [
        "base"
       ]
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The folder, or one file",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "413": {
      "$ref": "#/components/responses/E413"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/{order}": {
   "get": {
    "operationId": "getOrder",
    "summary": "A change order",
    "description": "One order in full: each file with its edited text, its base blob and its line diff (hunks), what needs a person's approval and why (approval.needed), the check (report, duramen check's errors, duramen diff's record diff and the kinds by hunk), its runs, rebuildBlocked (the first rebuild gate that fails, in words, or null), take once a rebuild is kept, and its history (events; an agent's name their token).\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "order",
      "in": "path",
      "required": true,
      "description": "The change order's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The order",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "order": {
           "type": "object",
           "additionalProperties": true
          }
         },
         "required": [
          "order"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/{order}/files": {
   "put": {
    "operationId": "saveOrder",
    "summary": "Edit a change order's files",
    "description": "Replaces files in the order by path, each a whole new text (never a patch). text null restores a file to its base; delete true deletes it; a text equal to the base drops it. expect is the overlaySha you edited (null before the first save): if the order was saved since, 409 order_changed and nothing changes. An edit puts the order back to draft, and any approval no longer holds: a person approves the new version. Refused (409) while a check or rebuild of it is on its way, and once it is taken or discarded.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "order",
      "in": "path",
      "required": true,
      "description": "The change order's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "expect": {
          "type": [
           "string",
           "null"
          ],
          "description": "The overlaySha you edited; null before the first save"
         },
         "files": {
          "type": "array",
          "maxItems": 40,
          "items": {
           "$ref": "#/components/schemas/OrderFile"
          }
         },
         "title": {
          "type": "string",
          "maxLength": 120
         }
        },
        "required": [
         "expect"
        ]
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Saved",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "order": {
           "$ref": "#/components/schemas/ChangeOrder"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "order"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "413": {
      "$ref": "#/components/responses/E413"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/{order}/diff": {
   "get": {
    "operationId": "orderDiff",
    "summary": "A change order's diff",
    "description": "The order's line diff without the texts: per file its role, hunks, lines added and removed, and why a person must approve it (needs); approval; and once checked, the record's hunks grouped by the kind of change duramen diff gives them (breaking, tightening, additive, relaxing, prose; a heuristic).\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "order",
      "in": "path",
      "required": true,
      "description": "The change order's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The diff",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/{order}/check": {
   "post": {
    "operationId": "checkOrder",
    "summary": "Check a change order",
    "description": "A report on the record with the order's edits (duramen check, duramen diff against the record before them, mistakes planted in the oracle), by a runner with no model, on Graft's own clone: free within the day's free reports, then max_usd as for POST /regen/{id}/report. Read it with GET .../orders/{order}. A clean check of exactly these edits (no errors, and a version bump big enough for the changes) moves the order to checked; one whose bump is too small leaves it a draft, with check.versionOk false. 503 ORDERS_NOT_READY until checking is open.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "order",
      "in": "path",
      "required": true,
      "description": "The change order's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": false,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "max_usd": {
          "type": "number",
          "minimum": 0
         },
         "reason": {
          "type": "string",
          "maxLength": 500,
          "description": "Why you want this run, in words (at most 500 characters). Kept on the run and signed into its record as reason {text, from: asker}; GET /regen/runs/{id} shows it. A rebuild a push starts takes the record commit's message instead (from commit). Without one, the change order's title is its reason (from order)."
         }
        }
       }
      }
     }
    },
    "responses": {
     "202": {
      "description": "Queued",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string"
          },
          "order": {
           "type": "string"
          },
          "priceUsd": {
           "type": "number"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/{order}/approve-oracle": {
   "post": {
    "operationId": "approveOrderOracle",
    "summary": "Approve an oracle change order (a person only)",
    "description": "Always 403 person_only with a token. A change an agent made (a token or Graft's drafter created or wrote the order since a person last approved it) waits for a person, and so does a change to the oracle (code the judge runs), to any file the judge reads, or to the record's oracle statement. The person, who approves exactly that version in /app (Specs). Read what needs approving in the order's approval.needed and show it to the user.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "order",
      "in": "path",
      "required": true,
      "description": "The change order's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/{order}/rebuild": {
   "post": {
    "operationId": "rebuildOrder",
    "summary": "Rebuild from a change order",
    "description": "A draft rebuild with the order's edits applied on Graft's own clone, priced like a rebuild (max_usd, 409 price_quote). Gates, in order, each 409 with its reason: checking open (503 ORDERS_NOT_READY), no other rebuild of the spec on its way, a check of exactly these edits with no errors, a big enough version bump (409 version_too_small, with version from, to, need and raiseTo), a person's approval when one is needed (an agent made the change, or it touches the oracle; 409 approval_needed, so only once the order is checked, with approval.needed, approveAt and how: an agent can't approve it, and this answer isn't kept under an Idempotency-Key, so the same request works after the approval), the branch still at the order's base, the spec's last kept rebuild in its history. A kept one waits as a bundle on graft/regen/<run> with two commits, the record change then the implementation (GET .../take). Nothing is written to the repository.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "order",
      "in": "path",
      "required": true,
      "description": "The change order's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": false,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "max_usd": {
          "type": "number",
          "minimum": 0
         },
         "reason": {
          "type": "string",
          "maxLength": 500,
          "description": "Why you want this run, in words (at most 500 characters). Kept on the run and signed into its record as reason {text, from: asker}; GET /regen/runs/{id} shows it. A rebuild a push starts takes the record commit's message instead (from commit). Without one, the change order's title is its reason (from order)."
         }
        }
       }
      }
     }
    },
    "responses": {
     "202": {
      "description": "Queued",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string"
          },
          "order": {
           "type": "string"
          },
          "capUsd": {
           "type": "number"
          },
          "estimateUsd": {
           "type": [
            "number",
            "null"
           ]
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "400": {
      "$ref": "#/components/responses/E400"
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/{order}/take": {
   "get": {
    "operationId": "takeOrder",
    "summary": "Take a change order's rebuild",
    "description": "Once a rebuild of the order is kept: the bundle's download link (an hour) and the commands that fetch graft/regen/<run> into the user's clone. Fetching the bundle takes the order (it becomes read-only); asking for the link doesn't. 409 before a rebuild.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "order",
      "in": "path",
      "required": true,
      "description": "The change order's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "How to take it",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/{order}/discard": {
   "post": {
    "operationId": "discardOrder",
    "summary": "Discard a change order",
    "description": "Ends the order: its edited files are deleted now (except any a run of it still holds, which retention deletes), its history stays. Discarding it again answers 200 with the same order. Refused while a check or rebuild of it is on its way, and once it is taken.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "order",
      "in": "path",
      "required": true,
      "description": "The change order's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "requestBody": {
     "required": false,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "why": {
          "type": "string",
          "maxLength": 300
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "Discarded",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "order": {
           "$ref": "#/components/schemas/ChangeOrder"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "order"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/orders/{order}/patch": {
   "get": {
    "operationId": "orderPatch",
    "summary": "A change order's record change as a patch",
    "description": "The record change alone, as a git patch to apply in a clone at the order's base (git apply --index), in patch. Changes no state: an order you download is still open to check, rebuild or edit.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "name": "order",
      "in": "path",
      "required": true,
      "description": "The change order's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The patch",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "order": {
           "type": "string"
          },
          "filename": {
           "type": "string"
          },
          "rebuilt": {
           "type": "boolean"
          },
          "patch": {
           "type": "string"
          }
         },
         "required": [
          "order",
          "patch"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   }
  },
  "/regen/{id}/report": {
   "get": {
    "operationId": "getSpecReport",
    "summary": "A spec's report",
    "description": "What the record is worth before anything is spent on it, from duramen check and duramen mutate on the pinned release, by a runner with no builder: a grade (strong, usable or thin), its first reasons in plain words, the numbers (requirements, examples, requirements no case reaches or that rest on the oracle alone, mistakes planted in the oracle and how many the cases catch) and what a blind rebuild of it can be judged on (fit: Graft doesn't analyse whether the program keeps state, renders, or depends on timing). path says which rule the grade follows (oracle or no oracle, pathWords in words); a record with no oracle is graded with its latest passing rebuild (gradedOn: its run and kept build; strongMeans: what strong means there; reportGrade: the report run's own grade, as its signed record holds it). Made when the spec is connected, when a push changes the record, and on demand. When the latest one didn't run (a branch the runner couldn't clone, say), failedReport has its run, when and why (as the email words it: a reason that is Graft's own reads as \"Something went wrong on Graft's side\"), and message says what to fix, or to grade again when there is nothing to fix; the report, if any, is the one before it.\n\nNeeds the `read` scope.",
    "x-graft-scope": "read",
    "tags": [
     "specs"
    ],
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The latest report (null before the first), any report on its way, and the latest one that failed (failedReport)",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "additionalProperties": true
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     }
    },
    "security": [
     {
      "oauth": [
       "read"
      ]
     },
     {
      "token": []
     }
    ]
   },
   "post": {
    "operationId": "refreshSpecReport",
    "summary": "Grade the record again",
    "description": "Queues a new report: runner time only, no model. Free for the first 20 a person asks for or Graft starts in a day (UTC); past those, $0.10 of agent work for one you ask for with max_usd covering it (Graft never charges for the ones it starts itself). 409 while a rebuild or report of the spec is queued or running.\n\nNeeds the `sync` scope.",
    "x-graft-scope": "sync",
    "tags": [
     "specs"
    ],
    "requestBody": {
     "required": false,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "max_usd": {
          "type": "number",
          "minimum": 0,
          "description": "The most this report may cost. Default 0: only a free one (the first 20 a day); past those it costs $0.10 of agent work, and a lower max_usd answers 409 with the price, charging nothing."
         }
        }
       }
      }
     }
    },
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "description": "The spec's id.",
      "schema": {
       "type": "string",
       "pattern": "^[A-Za-z0-9_-]{8,64}$"
      }
     },
     {
      "$ref": "#/components/parameters/IdempotencyKey"
     }
    ],
    "responses": {
     "202": {
      "description": "Queued",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "run": {
           "type": "string"
          },
          "message": {
           "type": "string"
          }
         },
         "required": [
          "run"
         ]
        }
       }
      },
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimitLimit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimitRemaining"
       },
       "Idempotent-Replayed": {
        "$ref": "#/components/headers/IdempotentReplayed"
       }
      }
     },
     "401": {
      "$ref": "#/components/responses/E401"
     },
     "402": {
      "$ref": "#/components/responses/E402"
     },
     "403": {
      "$ref": "#/components/responses/E403"
     },
     "404": {
      "$ref": "#/components/responses/E404"
     },
     "409": {
      "$ref": "#/components/responses/E409"
     },
     "429": {
      "$ref": "#/components/responses/E429"
     },
     "500": {
      "$ref": "#/components/responses/E500"
     },
     "503": {
      "$ref": "#/components/responses/E503"
     }
    },
    "security": [
     {
      "oauth": [
       "sync"
      ]
     },
     {
      "token": []
     }
    ]
   }
  }
 },
 "webhooks": {
  "run.finished": {
   "post": {
    "summary": "A sync ended: data.outcome is moved, stayed or failed (a move its organization reviews: once it's decided). A revert has data.run.trigger 'revert', data.run.release the release it went back to, data.run.undid the release it undid and data.run.reverts that run.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "allOf": [
         {
          "$ref": "#/components/schemas/WebhookEvent"
         },
         {
          "type": "object",
          "properties": {
           "type": {
            "const": "run.finished"
           }
          }
         }
        ]
       }
      }
     }
    },
    "parameters": [
     {
      "name": "Graft-Signature",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Event",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Delivery",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Any 2xx takes it; anything else is tried again later"
     }
    }
   }
  },
  "run.needs_person": {
   "post": {
    "summary": "A sync stopped on a question for the user, conflicting files to resolve, or its organization's review (data.reason; a held move has data.links.review).",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "allOf": [
         {
          "$ref": "#/components/schemas/WebhookEvent"
         },
         {
          "type": "object",
          "properties": {
           "type": {
            "const": "run.needs_person"
           }
          }
         }
        ]
       }
      }
     }
    },
    "parameters": [
     {
      "name": "Graft-Signature",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Event",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Delivery",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Any 2xx takes it; anything else is tried again later"
     }
    }
   }
  },
  "regen.finished": {
   "post": {
    "summary": "A rebuild ended: passed (kept), stayed (not kept) or failed. data.kind is rebuild, rejudge for a re-judge of rebuild data.rejudgeOf's builds (which delivers nothing), draft for a rebuild of the record on branch data.draftBranch (a bundle only, compared with rebuild data.compareTo), or order for a change order's run (data.orderId; data.check is true for its check and false for its rebuild, which is a bundle only, compared with rebuild data.compareTo).",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "allOf": [
         {
          "$ref": "#/components/schemas/WebhookEvent"
         },
         {
          "type": "object",
          "properties": {
           "type": {
            "const": "regen.finished"
           }
          }
         }
        ]
       }
      }
     }
    },
    "parameters": [
     {
      "name": "Graft-Signature",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Event",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Delivery",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Any 2xx takes it; anything else is tried again later"
     }
    }
   }
  },
  "budget.capped": {
   "post": {
    "summary": "Graft's agent stopped at its budget, or the account is out of agent work (data.reason); or, to an organization's owners, an agent's, team's or the organization's monthly cap can't start more (data.reason agent_cap, team_cap or org_cap).",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "allOf": [
         {
          "$ref": "#/components/schemas/WebhookEvent"
         },
         {
          "type": "object",
          "properties": {
           "type": {
            "const": "budget.capped"
           }
          }
         }
        ]
       }
      }
     }
    },
    "parameters": [
     {
      "name": "Graft-Signature",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Event",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Delivery",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Any 2xx takes it; anything else is tried again later"
     }
    }
   }
  },
  "budget.nearing": {
   "post": {
    "summary": "To an organization's owners: an agent's, team's or the organization's monthly cap is 75% or 90% used (data.threshold), once each a month.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "allOf": [
         {
          "$ref": "#/components/schemas/WebhookEvent"
         },
         {
          "type": "object",
          "properties": {
           "type": {
            "const": "budget.nearing"
           }
          }
         }
        ]
       }
      }
     }
    },
    "parameters": [
     {
      "name": "Graft-Signature",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Event",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Delivery",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Any 2xx takes it; anything else is tried again later"
     }
    }
   }
  },
  "audit.entry": {
   "post": {
    "summary": "An entry in the audit log of an organization you own (data.org, data.entry: id, at, by, action, target, detail, and its ledger seq and hash). Only when named in events; with org set, that organization's only.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "allOf": [
         {
          "$ref": "#/components/schemas/WebhookEvent"
         },
         {
          "type": "object",
          "properties": {
           "type": {
            "const": "audit.entry"
           }
          }
         }
        ]
       }
      }
     }
    },
    "parameters": [
     {
      "name": "Graft-Signature",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Event",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Delivery",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Any 2xx takes it; anything else is tried again later"
     }
    }
   }
  },
  "review.overdue": {
   "post": {
    "summary": "A team channel's: a move assigned to a reviewer has waited past the organization's nudge time (data.copy, data.run, data.assignee, data.hours, data.nudge, data.links.review). Only when named in events. Quiet hours hold it.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "allOf": [
         {
          "$ref": "#/components/schemas/WebhookEvent"
         },
         {
          "type": "object",
          "properties": {
           "type": {
            "const": "review.overdue"
           }
          }
         }
        ]
       }
      }
     }
    },
    "parameters": [
     {
      "name": "Graft-Signature",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Event",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Delivery",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Any 2xx takes it; anything else is tried again later"
     }
    }
   }
  },
  "ping": {
   "post": {
    "summary": "A test post (POST /webhooks/{id}/test).",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "allOf": [
         {
          "$ref": "#/components/schemas/WebhookEvent"
         },
         {
          "type": "object",
          "properties": {
           "type": {
            "const": "ping"
           }
          }
         }
        ]
       }
      }
     }
    },
    "parameters": [
     {
      "name": "Graft-Signature",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Event",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "Graft-Delivery",
      "in": "header",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "Any 2xx takes it; anything else is tried again later"
     }
    }
   }
  }
 },
 "components": {
  "headers": {
   "RetryAfter": {
    "description": "Seconds to wait before asking again. On every 429 (for daily_limit_reached, up to the next 00:00 UTC); on a 409 for a record still being signed (signing) or a key still being answered (idempotency_in_progress); on a 503 when GitHub or Anthropic didn't answer (upstream_unavailable); on a 202 draft_pending, and on its replay under the same Idempotency-Key. Never on a limit that doesn't lift on its own.",
    "schema": {
     "type": "integer",
     "minimum": 1
    }
   },
   "RateLimitLimit": {
    "description": "Requests a minute this token may make: 120, or 30 on a refused change.",
    "schema": {
     "type": "integer"
    }
   },
   "RateLimitRemaining": {
    "description": "Requests this token has left in the current minute.",
    "schema": {
     "type": "integer",
     "minimum": 0
    }
   },
   "IdempotentReplayed": {
    "description": "true when this is the first answer to an Idempotency-Key used before, replayed; nothing was done again.",
    "schema": {
     "type": "string",
     "enum": [
      "true"
     ]
    }
   }
  },
  "parameters": {
   "IdempotencyKey": {
    "name": "Idempotency-Key",
    "in": "header",
    "required": false,
    "description": "Any write: the same key from the same token within 24 hours answers with the first answer (Idempotent-Replayed: true, with its Retry-After when it had one) and does nothing more. Not kept: a 5xx, and a refusal that a wait or another action lifts (any with retryable true, and approval_needed, limit_reached, daily_limit_reached, too_many_open_orders, resume_limit_reached, monthly_limit_reached, agent_work_used, copy_limit_reached, signing, plan_required), so the same key runs the request again once it has lifted. The same key with a different request is 422 idempotency_mismatch.",
    "schema": {
     "type": "string",
     "minLength": 1,
     "maxLength": 255
    }
   }
  },
  "securitySchemes": {
   "token": {
    "type": "http",
    "scheme": "bearer",
    "description": "A personal API token (graft_...) from https://graftfork.com/app#settings, with the scopes it needs: read (See your copies, syncs, reports, waiting conflicts and usage); sync (Connect or disconnect a repository, start a sync, check, dependency upgrade or upstream build, connect, set or rebuild a spec or ask for a new spec report, start, edit, check, rebuild or discard a change order, revert a move, set a copy's desired state, add or remove a webhook, and track an upstream pull request or ask for Graft's reviewer (rebuilds and paid reports spend agent work)); resolve (Hand in a resolved file or answer a question (Graft still runs your tests before anything moves))."
   },
   "oauth": {
    "type": "oauth2",
    "description": "OAuth 2.1 for agents and MCP clients: register at https://graftfork.com/oauth/register (public clients, no secret), then the authorization code with PKCE (S256 only). Ask for resource=https://graftfork.com/api/v1 to use the token here. The access token lasts an hour; the refresh token is swapped for a new one on each use, and one used twice revokes the grant. The person sets the token's limits on the consent page and revokes it in https://graftfork.com/app#settings.",
    "flows": {
     "authorizationCode": {
      "authorizationUrl": "https://graftfork.com/oauth/authorize",
      "tokenUrl": "https://graftfork.com/oauth/token",
      "refreshUrl": "https://graftfork.com/oauth/token",
      "scopes": {
       "read": "See your copies, syncs, reports, waiting conflicts and usage",
       "sync": "Connect or disconnect a repository, start a sync, check, dependency upgrade or upstream build, connect, set or rebuild a spec or ask for a new spec report, start, edit, check, rebuild or discard a change order, revert a move, set a copy's desired state, add or remove a webhook, and track an upstream pull request or ask for Graft's reviewer (rebuilds and paid reports spend agent work)",
       "resolve": "Hand in a resolved file or answer a question (Graft still runs your tests before anything moves)"
      }
     }
    }
   }
  },
  "responses": {
   "E400": {
    "description": "The request is invalid",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E401": {
    "description": "No token, or the token is wrong or revoked (unauthorized), or past its expiry (token_expired), or its organization turned its agents off (agent_disabled)",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E402": {
    "description": "A monthly cap is used, checked in this order: the token's (token_budget_spent), the organization's agent's (agent_budget_spent), its team's (team_budget_spent), the organization's (org_budget_spent); error also has capUsd, spentUsd and resetsOn. Or this month's syncs, change checks or upstream builds are used (monthly_limit_reached, with limit, resetsAt and resetsOn). Or a rebuild can't start because this month's agent work is used, yours or a cap over it (agent_work_used, with resetsAt and resetsOn; agent work added in Billing lifts it sooner). None lifts on its own before the 1st (UTC); retryable is false",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E403": {
    "description": "The token lacks the scope (insufficient_scope), the plan doesn't include it (plan_required: rebuilding from a spec is on Premium, Team and Enterprise), the role doesn't allow it (forbidden), the token is limited to some copies and this route makes something new or reaches past them (out_of_scope), or only a person may do it (needs_person, person_only), or every copy you may connect is connected (copy_limit_reached; disconnect one first)",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E404": {
    "description": "Not found, not yours, or outside the token's copies",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E409": {
    "description": "The state doesn't allow it (already connected, already running, out of date), or it could cost more than max_usd (price_quote, with estimateUsd, capUsd and workLeftUsd beside the message), or an Idempotency-Key's first request is still being answered (idempotency_in_progress), or a rebuild's record is still being signed (signing; see Retry-After), or a release was retried as often as Graft retries one (resume_limit_reached; sync it again), or a spec or account holds as many open change orders as it may (too_many_open_orders, with open naming the ones the token may reach and hidden counting the rest; rebuild and take one, or discard one), or a rebuild's budget is under what a builder needs to start (conflict, retryable false; ask with a higher max_usd)",
    "headers": {
     "Retry-After": {
      "$ref": "#/components/headers/RetryAfter"
     }
    },
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E413": {
    "description": "Too large",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E422": {
    "description": "The Idempotency-Key was used for a different request (idempotency_mismatch)",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E429": {
    "description": "A limit that lifts on its own, always with Retry-After: rate limited (rate_limited), change orders' 120 saves and reads an hour (limit_reached, with retryAfter and retryable true), or today's free spec reports used with no agent work to pay for another (daily_limit_reached, with retryAfter and resetsAt, the next 00:00 UTC; agent work added in Billing lifts it sooner). Monthly limits are 402, not 429",
    "headers": {
     "Retry-After": {
      "$ref": "#/components/headers/RetryAfter"
     },
     "RateLimit-Limit": {
      "$ref": "#/components/headers/RateLimitLimit"
     },
     "RateLimit-Remaining": {
      "$ref": "#/components/headers/RateLimitRemaining"
     }
    },
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E500": {
    "description": "Graft failed (internal_error); try again",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E502": {
    "description": "The code host or another service Graft relies on failed (upstream_error); try again",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "E503": {
    "description": "Graft paused hosted runs (paused; retryable false, no Retry-After: retrying won't help until Graft turns them back on), or GitHub or Anthropic didn't answer and nothing was changed (upstream_unavailable; retryable true, Retry-After 60), or a service (rebuilds, the drafter or record signing) isn't set up (unavailable), or checking and rebuilding change orders isn't open yet (ORDERS_NOT_READY), or the hosted drafter isn't open (DRAFTER_NOT_READY)",
    "headers": {
     "Retry-After": {
      "$ref": "#/components/headers/RetryAfter"
     }
    },
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   }
  },
  "schemas": {
   "ChangeOrder": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "spec": {
      "type": "string"
     },
     "title": {
      "type": "string"
     },
     "state": {
      "type": "string",
      "enum": [
       "draft",
       "checked",
       "previewed",
       "rebuilt",
       "taken",
       "discarded"
      ]
     },
     "source": {
      "type": "string",
      "enum": [
       "person",
       "agent",
       "drafter"
      ],
      "description": "Who wrote the current edits"
     },
     "createdByToken": {
      "type": [
       "string",
       "null"
      ],
      "description": "The token that started it, when an agent did"
     },
     "branch": {
      "type": "string"
     },
     "base": {
      "type": "string",
      "description": "The branch's commit the order began at"
     },
     "overlaySha": {
      "type": [
       "string",
       "null"
      ],
      "description": "SHA-256 of the order's edits; send it back as expect"
     },
     "files": {
      "type": "integer"
     },
     "changes": {
      "type": "array",
      "items": {
       "type": "object",
       "additionalProperties": true
      }
     },
     "checked": {
      "type": "boolean"
     },
     "check": {
      "type": [
       "object",
       "null"
      ]
     },
     "approval": {
      "type": "object",
      "properties": {
       "needed": {
        "type": "array",
        "items": {
         "type": "string"
        },
        "description": "What needs a person's approval before a rebuild, and why"
       },
       "approved": {
        "type": "boolean"
       },
       "at": {
        "type": [
         "string",
         "null"
        ]
       },
       "how": {
        "type": "string",
        "description": "Where a person approves it: /app, never a token"
       }
      }
     },
     "rebuildRun": {
      "type": [
       "string",
       "null"
      ]
     },
     "takenAt": {
      "type": [
       "string",
       "null"
      ]
     },
     "discardedAt": {
      "type": [
       "string",
       "null"
      ]
     }
    },
    "required": [
     "id",
     "spec",
     "title",
     "state",
     "approval"
    ]
   },
   "OrderFile": {
    "type": "object",
    "properties": {
     "path": {
      "type": "string",
      "description": "A file in the record's folder"
     },
     "text": {
      "type": [
       "string",
       "null"
      ],
      "description": "The whole new text; null restores the base"
     },
     "delete": {
      "type": "boolean",
      "description": "Delete the file"
     }
    },
    "required": [
     "path"
    ]
   },
   "OrgAgent": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "name": {
      "type": "string"
     },
     "team": {
      "type": [
       "string",
       "null"
      ]
     },
     "scopes": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "copies": {
      "type": [
       "array",
       "null"
      ],
      "items": {
       "type": "string"
      }
     },
     "monthlyUsd": {
      "type": [
       "number",
       "null"
      ]
     },
     "spentUsd": {
      "type": "number",
      "description": "This month (UTC)"
     },
     "expiresAt": {
      "type": [
       "string",
       "null"
      ]
     },
     "createdAt": {
      "type": "string"
     },
     "createdBy": {
      "type": "string",
      "description": "The owner who made it; the agent stays the organization's if they leave"
     },
     "createdByStillMember": {
      "type": "boolean"
     },
     "lastUsedAt": {
      "type": [
       "string",
       "null"
      ]
     },
     "status": {
      "type": "string",
      "enum": [
       "active",
       "expired",
       "retired"
      ]
     },
     "ownerOfRecord": {
      "type": "string",
      "description": "org:<organization id>"
     },
     "token": {
      "type": [
       "object",
       "null"
      ],
      "description": "The live token's last four characters"
     }
    },
    "required": [
     "id",
     "name",
     "scopes",
     "status",
     "ownerOfRecord"
    ]
   },
   "OrgBudgets": {
    "type": "object",
    "properties": {
     "month": {
      "type": "string"
     },
     "order": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "thresholds": {
      "type": "array",
      "items": {
       "type": "integer"
      }
     },
     "org": {
      "type": "object",
      "properties": {
       "monthlyUsd": {
        "type": [
         "number",
         "null"
        ]
       },
       "spentUsd": {
        "type": "number"
       }
      }
     },
     "teams": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "team": {
         "type": "string"
        },
        "monthlyUsd": {
         "type": "number"
        },
        "spentUsd": {
         "type": "number"
        }
       }
      }
     }
    },
    "required": [
     "month",
     "org",
     "teams"
    ]
   },
   "Error": {
    "type": "object",
    "properties": {
     "error": {
      "type": "object",
      "properties": {
       "code": {
        "type": "string",
        "examples": [
         "invalid_request",
         "invalid_json",
         "app_not_installed",
         "unauthorized",
         "token_expired",
         "agent_disabled",
         "token_budget_spent",
         "agent_budget_spent",
         "team_budget_spent",
         "org_budget_spent",
         "insufficient_scope",
         "out_of_scope",
         "plan_required",
         "forbidden",
         "not_found",
         "conflict",
         "order_changed",
         "needs_force",
         "price_quote",
         "too_large",
         "rate_limited",
         "limit_reached",
         "monthly_limit_reached",
         "agent_work_used",
         "daily_limit_reached",
         "copy_limit_reached",
         "resume_limit_reached",
         "too_many_open_orders",
         "internal_error",
         "upstream_error",
         "unavailable",
         "paused",
         "upstream_unavailable",
         "idempotency_mismatch",
         "idempotency_in_progress",
         "signing",
         "needs_person",
         "person_only",
         "approval_needed",
         "version_too_small",
         "ORDERS_NOT_READY",
         "REVIEW_IN_PROGRESS",
         "IN_PROGRESS",
         "revert_exists",
         "busy",
         "review_waiting",
         "not_current",
         "not_taken",
         "is_revert",
         "no_earlier_state",
         "bundle_gone"
        ]
       },
       "message": {
        "type": "string"
       },
       "estimateUsd": {
        "type": [
         "number",
         "null"
        ],
        "description": "price_quote: what it may (sync, upgrade) or is likely to (rebuild) cost"
       },
       "capUsd": {
        "type": "number",
        "description": "price_quote: the most it may spend as asked"
       },
       "workLeftUsd": {
        "type": "number",
        "description": "price_quote: your agent work left this month"
       },
       "retryAfter": {
        "type": "integer",
        "description": "Seconds to wait, as in Retry-After: limit_reached on change orders, daily_limit_reached, upstream_unavailable"
       },
       "retryable": {
        "type": "boolean",
        "description": "Whether the same request may work later on its own: true after Retry-After (limit_reached, daily_limit_reached, upstream_unavailable), false until something else changes (monthly_limit_reached, agent_work_used, copy_limit_reached, resume_limit_reached, too_many_open_orders, paused, unavailable from the drafter)"
       },
       "resetsAt": {
        "type": "string",
        "format": "date-time",
        "description": "monthly_limit_reached and agent_work_used: when the month starts again, the 1st at 00:00 UTC; daily_limit_reached: the next 00:00 UTC"
       },
       "resetsOn": {
        "type": "string",
        "description": "monthly_limit_reached, agent_work_used and the 402 budget caps: that day, YYYY-MM-DD (UTC)"
       },
       "limit": {
        "type": "integer",
        "description": "monthly_limit_reached, copy_limit_reached, resume_limit_reached, too_many_open_orders: the limit reached"
       },
       "open": {
        "type": "array",
        "items": {
         "type": "string"
        },
        "description": "too_many_open_orders: the open change orders' ids, only those on specs the token may reach"
       },
       "hidden": {
        "type": "integer",
        "description": "too_many_open_orders, for a token limited to some specs: how many open orders are on specs it can't reach"
       },
       "person": {
        "type": "object",
        "properties": {
         "who": {
          "type": "string",
          "enum": [
           "you",
           "an owner",
           "a reviewer"
          ],
          "description": "Who must act: the account's person, an owner of the organization, or a reviewer in it"
         },
         "url": {
          "type": "string",
          "format": "uri",
          "description": "Where in /app they do it"
         },
         "say": {
          "type": "string",
          "description": "One sentence to show the user"
         }
        },
        "required": [
         "who",
         "url",
         "say"
        ],
        "description": "Only a person can lift this refusal: show the user say and url, and don't retry until they say it's done"
       }
      },
      "required": [
       "code",
       "message"
      ]
     }
    },
    "required": [
     "error"
    ]
   },
   "ConnectRequest": {
    "type": "object",
    "properties": {
     "repo": {
      "type": "string",
      "description": "Your repository on GitHub with Graft's app installed, owner/name. Or give clone_url instead",
      "examples": [
       "octo/tool"
      ]
     },
     "clone_url": {
      "type": "string",
      "description": "Instead of repo: your copy's https git address on any host, with branch, upstream and base. Graft delivers its moves itself (graft pull)"
     },
     "token": {
      "type": "string",
      "description": "With clone_url: a read token for a private repository, kept in a vault for this copy and deleted when it's disconnected"
     },
     "username": {
      "type": "string",
      "description": "With token: the username the host expects (default git)"
     },
     "branch": {
      "type": "string",
      "description": "The branch with your changes. Leave out to use the detected default branch (with repo only)."
     },
     "upstream": {
      "type": "string",
      "description": "The upstream repository: owner/name on GitHub or a https git URL"
     },
     "base": {
      "type": "string",
      "description": "The upstream release tag your branch sits on"
     },
     "tags": {
      "type": "string",
      "description": "Which tags are releases, as a git glob such as v*"
     },
     "follow": {
      "type": "string",
      "enum": [
       "all",
       "minor",
       "major"
      ]
     },
     "pre_releases": {
      "type": "boolean",
      "description": "With repo only (no branch): also follow upstream pre-releases"
     },
     "backend": {
      "type": "string",
      "enum": [
       "delegated",
       "none",
       "anthropic-byok",
       "managed-agents"
      ],
      "description": "Who resolves conflicts: you, the agent tracking this copy (delegated, the default on Free: list_pending_conflicts, get_conflict, submit_resolution), nobody, Graft's agent on the user's saved API key, or (Premium) Graft's own agent (managed-agents, the default on Premium)"
     }
    }
   },
   "Copy": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "repo": {
      "type": "string"
     },
     "branch": {
      "type": "string"
     },
     "upstream": {
      "type": "string"
     },
     "base": {
      "type": "string",
      "description": "The upstream release the copy sits on"
     },
     "backend": {
      "type": "string"
     },
     "paused": {
      "type": "boolean"
     },
     "waiting": {
      "type": "integer",
      "description": "Conflicts and questions waiting"
     },
     "last": {
      "type": [
       "object",
       "null"
      ]
     },
     "explain": {
      "type": [
       "object",
       "null"
      ]
     }
    },
    "required": [
     "id",
     "repo",
     "branch",
     "upstream",
     "base"
    ],
    "additionalProperties": true
   },
   "Run": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "trigger": {
      "type": "string"
     },
     "status": {
      "type": "string",
      "enum": [
       "queued",
       "running",
       "moved",
       "stayed",
       "waiting",
       "failed",
       "cancelled"
      ]
     },
     "release": {
      "type": [
       "string",
       "null"
      ]
     },
     "note": {
      "type": [
       "string",
       "null"
      ]
     },
     "prUrl": {
      "type": [
       "string",
       "null"
      ]
     },
     "capUsd": {
      "type": [
       "number",
       "null"
      ],
      "description": "On Graft's agent: the most the run's agent may spend (its session's hard budget once it starts; before that, your max_usd)"
     },
     "spentUsd": {
      "type": "number",
      "description": "On Graft's agent: what its agent spent"
     },
     "budgetReached": {
      "type": "boolean",
      "description": "On Graft's agent: the agent stopped at capUsd"
     },
     "policy": {
      "type": "object",
      "additionalProperties": true,
      "description": "On a move a path policy applied to: held, agent (an agent made the move), matched [{path, rule, from: organization | copy | repository}], rules (how many from each), people, file {commit, state: read | absent | not read | error}, error (unreadable rules hold the move)"
     }
    },
    "required": [
     "id",
     "status"
    ],
    "additionalProperties": true
   },
   "RunDetail": {
    "allOf": [
     {
      "$ref": "#/components/schemas/Run"
     },
     {
      "type": "object",
      "properties": {
       "report": {
        "type": [
         "object",
         "null"
        ]
       },
       "tests": {
        "type": "object",
        "additionalProperties": true
       },
       "log": {
        "type": [
         "string",
         "null"
        ]
       },
       "conflicts": {
        "type": "array",
        "items": {
         "type": "object",
         "additionalProperties": true
        }
       }
      }
     }
    ]
   },
   "PendingConflict": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "copy": {
      "type": "object",
      "additionalProperties": true
     },
     "release": {
      "type": "string"
     },
     "change": {
      "type": "string"
     },
     "file": {
      "type": "string"
     },
     "since": {
      "type": "string"
     }
    },
    "required": [
     "id",
     "file"
    ]
   },
   "PendingQuestion": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "copy": {
      "type": "object",
      "additionalProperties": true
     },
     "release": {
      "type": "string"
     },
     "question": {
      "type": "string"
     },
     "options": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "option": {
         "type": "integer"
        },
        "label": {
         "type": "string"
        },
        "summary": {
         "type": "string"
        }
       }
      }
     }
    },
    "required": [
     "id",
     "question",
     "options"
    ]
   },
   "Conflict": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "kind": {
      "const": "conflict"
     },
     "status": {
      "type": "string"
     },
     "file": {
      "type": "string"
     },
     "reason": {
      "type": "string"
     },
     "base": {
      "type": [
       "string",
       "null"
      ]
     },
     "ours": {
      "type": [
       "string",
       "null"
      ]
     },
     "theirs": {
      "type": [
       "string",
       "null"
      ]
     },
     "instructions": {
      "type": "string"
     }
    },
    "required": [
     "id",
     "kind",
     "file"
    ],
    "additionalProperties": true
   },
   "Question": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "kind": {
      "const": "question"
     },
     "status": {
      "type": "string"
     },
     "question": {
      "type": "string"
     },
     "options": {
      "type": "array",
      "items": {
       "type": "object",
       "additionalProperties": true
      }
     }
    },
    "required": [
     "id",
     "kind",
     "question",
     "options"
    ],
    "additionalProperties": true
   },
   "Saved": {
    "type": "object",
    "properties": {
     "saved": {
      "type": "boolean"
     },
     "resumedRun": {
      "type": [
       "string",
       "null"
      ],
      "description": "The sync Graft started again, once every file of the sync is in"
     },
     "message": {
      "type": "string"
     }
    },
    "required": [
     "saved",
     "message"
    ]
   },
   "Upstream": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "status": {
      "type": "string",
      "enum": [
       "queued",
       "running",
       "built",
       "failed"
      ]
     },
     "phase": {
      "type": [
       "string",
       "null"
      ]
     },
     "note": {
      "type": [
       "string",
       "null"
      ]
     },
     "copy": {
      "type": "object",
      "additionalProperties": true
     },
     "release": {
      "type": "string"
     },
     "change": {
      "type": "object",
      "properties": {
       "name": {
        "type": "string"
       },
       "subject": {
        "type": "string"
       }
      }
     },
     "branch": {
      "type": [
       "string",
       "null"
      ],
      "description": "graft/upstream/<change>"
     },
     "tests": {
      "type": [
       "object",
       "null"
      ],
      "description": "source, ran, why, and the verdict on the change alone against the release"
     },
     "pullRequest": {
      "type": "object",
      "properties": {
       "title": {
        "type": "string"
       },
       "body": {
        "type": "string"
       },
       "url": {
        "type": [
         "string",
         "null"
        ]
       },
       "state": {
        "type": [
         "string",
         "null"
        ],
        "enum": [
         "open",
         "closed",
         "merged",
         null
        ]
       },
       "mergedAt": {
        "type": [
         "string",
         "null"
        ]
       },
       "upstreamed": {
        "type": "boolean"
       }
      }
     },
     "downloads": {
      "type": [
       "object",
       "null"
      ],
      "description": "bundle and patch links, each good for an hour"
     },
     "commands": {
      "type": "array",
      "items": {
       "type": "string"
      }
     }
    },
    "required": [
     "id",
     "status",
     "change",
     "pullRequest"
    ],
    "additionalProperties": true
   },
   "Explanation": {
    "type": "object",
    "properties": {
     "copy": {
      "type": "object",
      "additionalProperties": true
     },
     "run": {
      "type": "object",
      "additionalProperties": true
     },
     "headline": {
      "type": "string"
     },
     "action": {
      "type": [
       "string",
       "null"
      ]
     },
     "next": {
      "type": "object",
      "properties": {
       "do": {
        "type": "string"
       },
       "why": {
        "type": "string"
       }
      },
      "required": [
       "do"
      ],
      "additionalProperties": true
     }
    },
    "required": [
     "copy",
     "headline",
     "next"
    ],
    "additionalProperties": true
   },
   "CheckRequest": {
    "type": "object",
    "properties": {
     "repo": {
      "type": "string",
      "description": "A GitHub repository Graft's app is installed on, owner/name"
     },
     "clone_url": {
      "type": "string",
      "description": "Or an https git address on any host"
     },
     "token": {
      "type": "string",
      "description": "With clone_url: a read token for a private repository, kept in a vault for this check only"
     },
     "username": {
      "type": "string",
      "description": "With token: the username the host expects (default git)"
     },
     "base": {
      "type": "string",
      "description": "What the change is compared with: a branch, tag or commit. With pr, defaults to the pull request's base"
     },
     "head": {
      "type": "string",
      "description": "The change: a branch, tag, commit or a ref such as refs/pull/12/head. With pr, defaults to the pull request's head commit"
     },
     "pr": {
      "type": "integer",
      "minimum": 1,
      "description": "A pull request's number on GitHub"
     }
    }
   },
   "Check": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "repo": {
      "type": "string"
     },
     "base": {
      "type": "string"
     },
     "head": {
      "type": "string"
     },
     "pr": {
      "type": [
       "integer",
       "null"
      ]
     },
     "status": {
      "type": "string",
      "enum": [
       "queued",
       "running",
       "done",
       "failed",
       "cancelled"
      ]
     },
     "outcome": {
      "type": [
       "string",
       "null"
      ],
      "enum": [
       "pass",
       "fail",
       "could_not_run",
       null
      ]
     },
     "headline": {
      "type": "string"
     },
     "reason": {
      "type": [
       "string",
       "null"
      ]
     },
     "newFailures": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "Failures with the change that the base doesn't have"
     },
     "alreadyFailing": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "Failures both sides have: not the change's"
     },
     "testFilesChanged": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "Files the change edits that shape what the tests run (package.json, conftest.py, ...): read them before trusting a pass"
     },
     "baseRun": {
      "type": [
       "object",
       "null"
      ]
     },
     "headRun": {
      "type": [
       "object",
       "null"
      ]
     }
    },
    "required": [
     "id",
     "status"
    ],
    "additionalProperties": true
   },
   "PolicyRequest": {
    "type": "object",
    "properties": {
     "paths": {
      "oneOf": [
       {
        "type": "array",
        "maxItems": 50,
        "items": {
         "type": "string",
         "maxLength": 200
        }
       },
       {
        "type": "string",
        "enum": [
         "defaults"
        ]
       }
      ],
      "description": "Globs: * and ? within a folder, ** across folders. No / matches a name anywhere; with a /, from the root. A folder covers everything in it. <glob>#scripts matches a package.json whose scripts the move changed. No negation."
     },
     "people": {
      "type": "boolean",
      "description": "Organizations only: hold a person's own moves too, not only an agent's."
     }
    },
    "required": [
     "paths"
    ],
    "additionalProperties": false
   },
   "OrgPolicy": {
    "type": "object",
    "properties": {
     "org": {
      "type": "string"
     },
     "paths": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "people": {
      "type": "boolean"
     },
     "defaults": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "file": {
      "type": "string",
      "description": "The repository file that adds rules: .graft/policy.yml"
     },
     "error": {
      "type": "string"
     }
    },
    "required": [
     "org",
     "paths",
     "people"
    ]
   },
   "CopyPolicy": {
    "type": "object",
    "properties": {
     "copy": {
      "type": "string"
     },
     "paths": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "defaults": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "file": {
      "type": "string"
     },
     "organization": {
      "type": [
       "object",
       "null"
      ],
      "additionalProperties": true
     }
    },
    "required": [
     "copy",
     "paths"
    ]
   },
   "ForkAlert": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "account": {
      "type": "string",
      "description": "The GitHub account scanned"
     },
     "repo": {
      "type": "string",
      "description": "The fork, owner/name"
     },
     "kind": {
      "type": "string",
      "enum": [
       "advisory",
       "behind",
       "carry"
      ]
     },
     "severity": {
      "type": [
       "string",
       "null"
      ],
      "enum": [
       "critical",
       "high",
       "moderate",
       "low",
       "unknown",
       null
      ]
     },
     "title": {
      "type": "string",
      "description": "One plain sentence"
     },
     "url": {
      "type": [
       "string",
       "null"
      ],
      "description": "The advisory's page"
     },
     "detail": {
      "type": "object",
      "additionalProperties": true
     },
     "at": {
      "type": "string"
     }
    },
    "required": [
     "id",
     "repo",
     "kind",
     "title",
     "at"
    ]
   },
   "UpgradeRequest": {
    "type": "object",
    "properties": {
     "copy_id": {
      "type": "string",
      "description": "The copy's id. Or give repo (owner/name, with branch when it's connected twice) or clone_url"
     },
     "repo": {
      "type": "string"
     },
     "branch": {
      "type": "string"
     },
     "clone_url": {
      "type": "string"
     },
     "dependency": {
      "type": "string",
      "description": "The package's name as npm or PyPI spells it",
      "examples": [
       "express",
       "@types/node",
       "requests"
      ]
     },
     "version": {
      "type": "string",
      "description": "The version to upgrade to, or latest (the default)",
      "examples": [
       "5.0.0",
       "latest"
      ]
     },
     "max_usd": {
      "type": "number",
      "minimum": 0,
      "description": "The most Graft's agent may spend fixing the code when the upgrade breaks tests, in dollars. More than the per-sync cap allows answers 409 price_quote."
     }
    },
    "required": [
     "dependency"
    ],
    "additionalProperties": false
   },
   "Upgrade": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "run": {
      "type": "string"
     },
     "copyId": {
      "type": "string"
     },
     "repo": {
      "type": [
       "string",
       "null"
      ]
     },
     "dependency": {
      "type": "string"
     },
     "requested": {
      "type": "string"
     },
     "version": {
      "type": [
       "string",
       "null"
      ]
     },
     "from": {
      "type": [
       "string",
       "null"
      ]
     },
     "status": {
      "type": "string",
      "enum": [
       "queued",
       "running",
       "agent",
       "delivered",
       "passed",
       "broken",
       "current",
       "untested",
       "failed",
       "cancelled"
      ]
     },
     "why": {
      "type": [
       "string",
       "null"
      ]
     },
     "upgradeBranch": {
      "type": [
       "string",
       "null"
      ]
     },
     "tests": {
      "type": [
       "object",
       "null"
      ]
     },
     "fix": {
      "type": [
       "object",
       "null"
      ]
     },
     "delivered": {
      "type": [
       "object",
       "null"
      ]
     },
     "prUrl": {
      "type": [
       "string",
       "null"
      ]
     }
    },
    "required": [
     "id",
     "dependency",
     "status"
    ],
    "additionalProperties": true
   },
   "ConnectSpec": {
    "type": "object",
    "properties": {
     "repo": {
      "type": "string",
      "description": "owner/name on GitHub (Graft's app installed)"
     },
     "clone_url": {
      "type": "string",
      "description": "Or any https git address"
     },
     "token": {
      "type": "string",
      "description": "A read token for clone_url, kept in a vault Graft can't read back"
     },
     "username": {
      "type": "string"
     },
     "branch": {
      "type": "string"
     },
     "record": {
      "type": "string",
      "description": "The record: a .duramen file or a folder of them, from the repository's root"
     },
     "impl": {
      "type": "string",
      "description": "The implementation's folder; a rebuild replaces it on graft/regen/<id>"
     },
     "lang": {
      "type": "string",
      "enum": [
       "ts",
       "py",
       "rs",
       "go"
      ],
      "description": "ts (TypeScript on Node.js), py (Python), rs (Rust) or go (Go); Rust and Go use the standard library only. A language Graft hasn't opened is refused with a reason."
     },
     "builds": {
      "type": "integer",
      "minimum": 1,
      "maximum": 3,
      "description": "Blind builds per rebuild; with 2 or 3, duramen agree compares them. Left out, Graft's default (one)"
     },
     "on_push": {
      "type": "boolean",
      "description": "Rebuild when a push changes a .duramen file of the record (GitHub app only)"
     },
     "on_model": {
      "type": "boolean",
      "description": "Rebuild when Graft reruns every opted-in spec on a new model"
     },
     "delivery": {
      "type": "string",
      "enum": [
       "graft",
       "github"
      ],
      "description": "graft: a bundle to take; github: that, and a pull request"
     },
     "org": {
      "type": "string",
      "description": "An organization you're a member of: the spec takes its defaults for what you leave out (builds, delivery, max_usd), and its team channels hear when a rebuild ends"
     }
    },
    "required": [
     "branch",
     "record",
     "impl",
     "lang"
    ]
   },
   "CopyDesired": {
    "type": "object",
    "properties": {
     "follow": {
      "type": "string",
      "enum": [
       "all",
       "minor",
       "major"
      ],
      "description": "Every release, only new minor releases, or only new major ones"
     },
     "pre_releases": {
      "type": "boolean",
      "description": "Also follow upstream pre-releases"
     },
     "schedule": {
      "type": "string",
      "enum": [
       "release",
       "weekly",
       "manual"
      ],
      "description": "Sync on each release, weekly, or only when asked"
     },
     "paused": {
      "type": "boolean"
     },
     "delivery": {
      "type": "string",
      "enum": [
       "graft",
       "github"
      ],
      "description": "graft: Graft keeps each move for you to take; github: a pull request"
     },
     "max_usd": {
      "type": [
       "number",
       "null"
      ],
      "minimum": 0,
      "maximum": 1000,
      "description": "The most one sync may give Graft's agent; under the per-sync cap it lowers it, and below $0.50 no agent starts. null: the per-sync cap"
     }
    },
    "additionalProperties": false
   },
   "CopyObserved": {
    "type": "object",
    "properties": {
     "state": {
      "type": "string",
      "enum": [
       "paused",
       "syncing",
       "needs_person",
       "moved",
       "stayed",
       "failed",
       "never_synced"
      ]
     },
     "settled": {
      "type": "boolean",
      "description": "Nothing under way and nobody's input awaited"
     },
     "base": {
      "type": "string",
      "description": "The upstream release the copy sits on"
     },
     "held": {
      "type": [
       "object",
       "null"
      ]
     },
     "lastRun": {
      "type": [
       "object",
       "null"
      ]
     },
     "why": {
      "type": [
       "string",
       "null"
      ]
     },
     "action": {
      "type": [
       "string",
       "null"
      ]
     },
     "waiting": {
      "type": "integer"
     },
     "review": {
      "type": [
       "object",
       "null"
      ]
     },
     "agentCapUsd": {
      "type": [
       "number",
       "null"
      ]
     }
    },
    "required": [
     "state",
     "settled"
    ],
    "additionalProperties": true
   },
   "SpecDesired": {
    "type": "object",
    "properties": {
     "paused": {
      "type": "boolean"
     },
     "delivery": {
      "type": "string",
      "enum": [
       "graft",
       "github"
      ]
     },
     "max_usd": {
      "type": [
       "number",
       "null"
      ],
      "minimum": 0.5,
      "maximum": 1000,
      "description": "The most one rebuild may spend, under Graft's own cap. null: the cap"
     },
     "on_push": {
      "type": "boolean"
     },
     "on_model": {
      "type": "boolean"
     },
     "builds": {
      "type": "integer",
      "minimum": 1,
      "maximum": 3
     }
    },
    "additionalProperties": false
   },
   "SpecObserved": {
    "type": "object",
    "properties": {
     "state": {
      "type": "string",
      "enum": [
       "paused",
       "rebuilding",
       "never_built",
       "passed",
       "stayed",
       "unchanged",
       "failed",
       "cancelled"
      ]
     },
     "settled": {
      "type": "boolean"
     },
     "lastRebuild": {
      "type": [
       "object",
       "null"
      ]
     },
     "why": {
      "type": [
       "string",
       "null"
      ]
     },
     "report": {
      "type": [
       "object",
       "null"
      ]
     },
     "rebuildCapUsd": {
      "type": "number"
     }
    },
    "required": [
     "state",
     "settled"
    ],
    "additionalProperties": true
   },
   "Webhook": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "host": {
      "type": "string"
     },
     "events": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "org": {
      "type": [
       "string",
       "null"
      ],
      "description": "The organization its audit.entry posts are limited to"
     },
     "secretHint": {
      "type": "string"
     },
     "on": {
      "type": "boolean"
     },
     "disabled": {
      "type": [
       "object",
       "null"
      ]
     },
     "failuresInARow": {
      "type": "integer"
     },
     "last": {
      "type": [
       "object",
       "null"
      ]
     },
     "deliveries": {
      "type": "array",
      "items": {
       "type": "object",
       "additionalProperties": true
      },
      "description": "GET /webhooks/{id} only: a page of deliveries, newest first"
     },
     "deliveriesMore": {
      "type": "boolean",
      "description": "GET /webhooks/{id} only: older deliveries remain"
     },
     "deliveriesNextBefore": {
      "type": [
       "string",
       "null"
      ],
      "description": "GET /webhooks/{id} only: ask again with before= this for older deliveries"
     }
    },
    "required": [
     "id",
     "host",
     "events",
     "on"
    ],
    "additionalProperties": true
   },
   "WebhookEvent": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string",
      "description": "evt_..."
     },
     "type": {
      "type": "string"
     },
     "created": {
      "type": "string",
      "format": "date-time"
     },
     "data": {
      "type": "object",
      "properties": {
       "copy": {
        "type": "object",
        "additionalProperties": true
       },
       "run": {
        "type": "object",
        "additionalProperties": true
       },
       "outcome": {
        "type": "string",
        "enum": [
         "moved",
         "stayed",
         "failed"
        ],
        "description": "run.finished"
       },
       "reason": {
        "type": "string",
        "description": "run.needs_person: question, conflicts or review; budget.capped: per_sync_cap, out_of_work or rebuild_cap"
       },
       "spec": {
        "type": "object",
        "additionalProperties": true
       },
       "rebuild": {
        "type": "object",
        "additionalProperties": true
       },
       "next": {
        "type": "string"
       },
       "links": {
        "type": "object",
        "additionalProperties": true
       }
      },
      "additionalProperties": true
     }
    },
    "required": [
     "id",
     "type",
     "created",
     "data"
    ]
   },
   "Spec": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "repo": {
      "type": "string"
     },
     "branch": {
      "type": "string"
     },
     "record": {
      "type": "string"
     },
     "impl": {
      "type": "string"
     },
     "lang": {
      "type": "string"
     },
     "builds": {
      "type": "integer"
     },
     "onPush": {
      "type": "boolean"
     },
     "onModel": {
      "type": "boolean"
     },
     "delivery": {
      "type": "string"
     },
     "last": {
      "type": [
       "object",
       "null"
      ],
      "description": "The spec's last rebuild (a Rebuild), never a report or a re-judge"
     },
     "active": {
      "type": [
       "object",
       "null"
      ],
      "description": "The spec's queued or running run, if any: id, what (rebuild, report or rejudge), trigger, status, phase, createdAt, startedAt and its place in the queue",
      "properties": {
       "id": {
        "type": "string"
       },
       "what": {
        "type": "string",
        "enum": [
         "rebuild",
         "report",
         "rejudge"
        ]
       },
       "status": {
        "type": "string"
       },
       "phase": {
        "type": [
         "string",
         "null"
        ]
       }
      }
     },
     "estimate": {
      "type": "object",
      "description": "Before the click: what a rebuild is likely to cost and how often it passes, from this spec's finished rebuilds, else other specs' in the same language; basis says which. The cap is the hard limit.",
      "properties": {
       "capUsd": {
        "type": "number"
       },
       "expectedUsd": {
        "type": [
         "number",
         "null"
        ]
       },
       "passRate": {
        "type": [
         "number",
         "null"
        ]
       },
       "rebuilds": {
        "type": "integer"
       },
       "basis": {
        "type": "string"
       }
      }
     },
     "report": {
      "type": [
       "object",
       "null"
      ],
      "description": "The spec's latest report (its grade and reasons), with at, commit and run, and its path, gradedOn and reportGrade (GET /regen/{id}/report); null before the first"
     },
     "nextReport": {
      "type": [
       "object",
       "null"
      ],
      "description": "GET /regen/specs only. What the next report costs: priceUsd, or available false and why",
      "properties": {
       "priceUsd": {
        "type": "number"
       },
       "available": {
        "type": "boolean"
       },
       "why": {
        "type": "string"
       }
      }
     }
    },
    "required": [
     "id",
     "repo",
     "record",
     "impl"
    ],
    "additionalProperties": true
   },
   "Rebuild": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string"
     },
     "status": {
      "type": "string",
      "enum": [
       "queued",
       "running",
       "passed",
       "stayed",
       "unchanged",
       "failed",
       "cancelled",
       "reported"
      ],
      "description": "passed: kept, every case passed. stayed: not kept, no build passed every case (label says \"Not kept\"). unchanged: the same as a passing rebuild, nothing rebuilt. failed: didn't finish, on Graft's side."
     },
     "label": {
      "type": "string",
      "description": "The status in words, as /app shows it: \"Passed: kept\", \"Not kept\", \"Didn't finish\" and so on"
     },
     "phase": {
      "type": [
       "string",
       "null"
      ]
     },
     "why": {
      "type": [
       "string",
       "null"
      ]
     },
     "suite": {
      "type": [
       "object",
       "null"
      ],
      "description": "The kept build's suite, or the first judged one's: passed and total",
      "properties": {
       "passed": {
        "type": [
         "integer",
         "null"
        ]
       },
       "total": {
        "type": [
         "integer",
         "null"
        ]
       }
      }
     },
     "choicesDiffer": {
      "type": [
       "integer",
       "null"
      ],
      "description": "Places this rebuild's recorded choices differ from the previous passing rebuild's, an entry worded two ways (the same REQ-, OPEN- or DEC- ID, or a near-identical heading) counted once; null with nothing to compare"
     },
     "choicesOpen": {
      "type": [
       "integer",
       "null"
      ],
      "description": "Of choicesDiffer, those naming an OPEN- item: places the record declares open"
     },
     "choicesUndeclared": {
      "type": [
       "integer",
       "null"
      ],
      "description": "Of choicesDiffer, the rest: places the record doesn't say it leaves open"
     },
     "take": {
      "type": [
       "object",
       "null"
      ]
     },
     "program": {
      "type": [
       "object",
       "null"
      ],
      "description": "The record's program: name, version, and cases (the cases in the brief, the lines of duramen build's cases.jsonl; the verdict rests on suite.total)",
      "properties": {
       "name": {
        "type": "string"
       },
       "version": {
        "type": [
         "string",
         "null"
        ]
       },
       "cases": {
        "type": [
         "integer",
         "null"
        ]
       }
      }
     },
     "holdout": {
      "type": [
       "object",
       "null"
      ],
      "description": "The kept build's hidden holdout: requests, how many requests no builder saw were drawn (it agreed with the record's oracle on requests - oracleErrors of them), and the seed; or none (why it didn't run) with requests 0",
      "properties": {
       "requests": {
        "type": "integer"
       },
       "seed": {
        "type": [
         "integer",
         "null"
        ]
       },
       "oracleErrors": {
        "type": "integer",
        "description": "Of requests, how many the oracle itself couldn't answer: not counted as agreed. Absent when none."
       },
       "none": {
        "type": "string"
       }
      }
     },
     "costUsd": {
      "type": "number",
      "description": "Agent work this rebuild spent"
     },
     "capUsd": {
      "type": [
       "number",
       "null"
      ],
      "description": "What its builders may spend together (the cap), or the max_usd asked for before they start"
     },
     "orderId": {
      "type": [
       "string",
       "null"
      ],
      "description": "A change order's run: its order"
     },
     "recordSigning": {
      "type": "boolean",
      "description": "Finished, and its signed record is still being written (at most 10 minutes)"
     },
     "builds": {
      "type": "array",
      "description": "GET /regen/runs/{id} only. Each blind build and its verdict",
      "items": {
       "type": "object",
       "properties": {
        "n": {
         "type": "integer"
        },
        "status": {
         "type": "string"
        },
        "model": {
         "type": [
          "string",
          "null"
         ]
        },
        "costUsd": {
         "type": "number"
        },
        "capUsd": {
         "type": "number"
        },
        "verdict": {
         "type": [
          "object",
          "null"
         ],
         "description": "pass, why, passed, total, and the holdout when it ran"
        },
        "choices": {
         "type": [
          "integer",
          "null"
         ],
         "description": "How many choices the build wrote in CHOICES.md"
        },
        "note": {
         "type": [
          "string",
          "null"
         ]
        }
       }
      }
     },
     "rejudges": {
      "type": "array",
      "description": "GET /regen/runs/{id} only, for a rebuild: its re-judges, newest first",
      "items": {
       "type": "object",
       "properties": {
        "id": {
         "type": "string"
        },
        "status": {
         "type": "string"
        },
        "label": {
         "type": "string"
        },
        "createdAt": {
         "type": "string"
        }
       }
      }
     },
     "suiteDetail": {
      "type": [
       "object",
       "null"
      ],
      "description": "GET /regen/runs/{id} only. The suite's whole result as the judge reported it"
     },
     "choices": {
      "type": "array",
      "description": "GET /regen/runs/{id} only. The choices the kept (or shown) build wrote in CHOICES.md"
     },
     "doctor": {
      "type": "object",
      "description": "GET /regen/runs/{id} only. Where the spec doesn't decide (the spec doctor): judged, summary, gaps (failing cases by requirement, places the builds chose differently, hidden requests answered differently), and of the gaps, open (naming an OPEN- item the record declares open) and undeclared (the rest)"
     },
     "judgeSandbox": {
      "type": [
       "object",
       "null"
      ],
      "description": "GET /regen/runs/{id} only. Where the judge ran the builds, as the runner reported it and the signed record keeps it (its judge field). driver_user: \"separate\" when each build ran as a user of its own that can't read the record or its oracle, else \"same as oracle\". network: \"none\" when the judge cut the network, \"not cut\", or \"not reported\". The judge fails closed: a runner that can't give a build its own user and no network judges nothing, so the rebuild is not kept, and it still reports its sandbox here. Null until a judge reported.",
      "properties": {
       "driver_user": {
        "type": "string",
        "enum": [
         "separate",
         "same as oracle"
        ]
       },
       "network": {
        "type": "string",
        "enum": [
         "none",
         "not cut",
         "not reported"
        ]
       }
      }
     },
     "builderNetwork": {
      "type": [
       "object",
       "null"
      ],
      "description": "GET /regen/runs/{id} only. The builders' environment networking as Anthropic held it when Graft read it back, just before the builders started (type, allowed_hosts, allow_package_managers, allow_mcp_servers). Null when the builders never started."
     },
     "buildersOffline": {
      "type": "string",
      "enum": [
       "none",
       "not none",
       "not checked"
      ],
      "description": "GET /regen/runs/{id} only. Graft's reading of builderNetwork: none when it was limited with no hosts, no package managers and no MCP servers; not checked before the builders start. Builders start only after a none."
     },
     "compile": {
      "type": [
       "object",
       "null"
      ],
      "description": "GET /regen/runs/{id} only. A Rust or Go rebuild's compile seconds on the judge: lang (rs or go), toolchainCopySeconds (Rust only: copying the runner's toolchain where the build's user can run it, null when the runner already had it) and builds ({n, seconds} for each build). Null for TypeScript and Python, and before the judge.",
      "properties": {
       "lang": {
        "type": "string",
        "enum": [
         "rs",
         "go"
        ]
       },
       "toolchainCopySeconds": {
        "type": [
         "number",
         "null"
        ]
       },
       "builds": {
        "type": "array",
        "items": {
         "type": "object",
         "properties": {
          "n": {
           "type": "integer"
          },
          "seconds": {
           "type": "number"
          }
         }
        }
       }
      }
     },
     "reason": {
      "type": [
       "object",
       "null"
      ],
      "description": "Why it was asked for, as its signed record says: text, and from: asker (given with the request), commit (the record commit's message, on a push), order (the change order's title, when its check or rebuild gave none) or graft (Graft's own words, for a new model). A ladder's next rung keeps its first rung's. Null when nobody gave one.",
      "properties": {
       "text": {
        "type": "string"
       },
       "from": {
        "type": "string",
        "enum": [
         "asker",
         "commit",
         "order",
         "graft"
        ]
       }
      }
     },
     "judgedBy": {
      "type": [
       "string",
       "null"
      ],
      "enum": [
       "examples",
       "examples and oracle",
       null
      ],
      "description": "How it was judged, as its signed record's judged_by says: examples (the record's written values alone; the record has no oracle) or examples and oracle (also agreement with the record's oracle). Null when Graft didn't record it (runs from before 2.1, or runs where no holdout or agree ran), and while it is queued or running."
     },
     "judged": {
      "type": "string",
      "description": "judgedBy in words, as /app and /regen show it: \"Judged on written values\", \"Judged on written values and agreement with the oracle\" or \"Not recorded\""
     },
     "judgedNote": {
      "type": [
       "string",
       "null"
      ],
      "description": "Why judgedBy is null, as /app says it: \"not judged yet\" (queued or running), \"this run is from before Graft kept it\" or \"neither the hidden holdout nor agree ran\". Null when judgedBy has a value."
     },
     "initiator": {
      "type": "object",
      "description": "GET /regen/runs/{id} only. Who started the rebuild, as its signed record says: kind person (in /app), agent (an API token: its id as token, and the MCP client's name and version as client when it said), ops (Graft's operator) or schedule (a push, a new model, the ladder's next rung).",
      "properties": {
       "kind": {
        "type": "string",
        "enum": [
         "person",
         "agent",
         "ops",
         "schedule"
        ]
       },
       "token": {
        "type": "string"
       },
       "client": {
        "type": "object"
       }
      }
     },
     "rejudgeOf": {
      "type": [
       "string",
       "null"
      ],
      "description": "For a re-judge (trigger rejudge): the rebuild whose builds it judged again"
     },
     "rejudge": {
      "type": [
       "object",
       "null"
      ],
      "description": "A re-judge's detail: of, seed, sameSeed, duramen, and per build its verdict, the original's, and whether they match"
     },
     "draftBranch": {
      "type": [
       "string",
       "null"
      ],
      "description": "For a draft (trigger draft, or a report that is a draft check): the branch whose record it rebuilt or checked"
     },
     "compareTo": {
      "type": [
       "string",
       "null"
      ],
      "description": "The spec's previous kept rebuild (the newest passed rebuild with a kept build that is neither a re-judge nor a draft) this one's judge compares behaviour with; null for a first rebuild and for a re-judge."
     },
     "behaviourDiff": {
      "type": [
       "object",
       "null"
      ],
      "description": "GET /regen/runs/{id} only. What changed in behaviour since compareTo, as the judge reported it: the previous kept build and the kept build of this rebuild, and the previous and current records' oracles, answer the same requests (the records' examples and a seeded sweep). against {run, build, commit}; sampled (false, with why, when it couldn't compare: the verdict stands); requests; probes {examples, breaking, sweep}; paths [{path, changed, explained, unexplained, examples [{id, op, request, before, after, oracleBefore, oracleAfter, explained}]}], unexplained first; unexplained, the requests whose answers changed where the two oracles agree, so nobody asked for the change (the number to read: 0 is what you want); errors [{who, error}]. Values other than counts are text, cut short. The signed record keeps against, sampled, requests, unexplained and the per-path counts. Null when the judge sent none (a first rebuild, a re-judge, a build not kept, or a runner from before the comparison)."
     }
    },
    "required": [
     "id",
     "status"
    ],
    "additionalProperties": true
   },
   "Usage": {
    "type": "object",
    "properties": {
     "plan": {
      "type": "string",
      "enum": [
       "free",
       "premium"
      ],
      "description": "The person's own plan; copies in a Team or Enterprise organization work as Premium (see coveredBy)"
     },
     "coveredBy": {
      "type": "array",
      "description": "Team or Enterprise organizations the person belongs to; their copies there work as Premium",
      "items": {
       "type": "object",
       "properties": {
        "orgId": {
         "type": "string"
        },
        "name": {
         "type": "string"
        },
        "plan": {
         "type": "string",
         "enum": [
          "team",
          "enterprise"
         ]
        }
       }
      }
     },
     "month": {
      "type": "string"
     },
     "syncs": {
      "type": "object",
      "properties": {
       "used": {
        "type": "integer"
       },
       "limit": {
        "type": "integer"
       },
       "coveredSyncs": {
        "type": [
         "integer",
         "null"
        ],
        "description": "The monthly sync limit for copies in a Team or Enterprise organization, or null when the person is in none"
       }
      }
     },
     "copies": {
      "type": "object",
      "properties": {
       "used": {
        "type": "integer",
        "description": "Copies of the person's own; copies in a Team or Enterprise organization count toward its forks instead"
       },
       "limit": {
        "type": "integer"
       }
      }
     },
     "graftAgentUsd": {
      "type": "number",
      "description": "Spent on Graft's agent this month on syncs only; agentWork.spentUsd counts syncs, upgrades and rebuilds"
     },
     "yourKeyUsd": {
      "type": "number",
      "description": "Spent on syncs with your own Anthropic key this month; Anthropic bills it, not Graft"
     },
     "agentWork": {
      "type": "object",
      "properties": {
       "leftUsd": {
        "type": "number",
        "description": "Agent work left this month: the included allowance, then top-ups"
       },
       "spentUsd": {
        "type": "number",
        "description": "Spent on Graft's agents this month: syncs, upgrades and rebuilds, a running one counted at its cap"
       },
       "caps": {
        "type": "object",
        "properties": {
         "perSyncUsd": {
          "type": "number"
         },
         "perRebuildUsd": {
          "type": "number",
          "description": "A rebuild's budget without max_usd"
         },
         "rebuildMaxUsd": {
          "type": "number",
          "description": "The most max_usd can give one rebuild now"
         },
         "monthUsd": {
          "type": "number",
          "description": "The account's monthly cap on Graft's agents"
         },
         "teamForkUsd": {
          "type": "number",
          "description": "In a Team or Enterprise organization: agent work per fork a month"
         }
        }
       }
      }
     }
    },
    "required": [
     "plan",
     "syncs"
    ],
    "additionalProperties": true
   }
  }
 }
}