{"openapi":"3.1.0","info":{"title":"Pealboard API","version":"1.0.0","description":"Work management on GitHub Issues and Projects, with portals for the people who are not on GitHub.\n\nGitHub is the system of record. Every issue, comment, label, milestone, project and field value this API returns exists in GitHub first, and every write goes to GitHub before it goes anywhere else — so a write this API reports is a write GitHub accepted.\n\nReads come from Pealboard's cache of GitHub and cost the installation nothing. Writes cost one GitHub round trip and are serialised per installation, so a bulk edit cannot race itself into a secondary rate limit.\n\nEvery response carries `X-Request-Id`. Every failure has the same shape: `{ error: { code, message, detail?, remedy?, requestId } }`. Lists are cursor-paged: send `nextCursor` back as `cursor`. Creates accept `Idempotency-Key`, and a retry with the same key returns the first answer rather than making a second object.\n\n**Sign-in failures do not use that envelope**, because they are browser redirects and not API calls. Better Auth's endpoints under `/auth` and `/portal/auth` redirect a failed sign-in to `/auth/error?error=<code>` on the origin the person is looking at — the workspace application for `/auth`, and the portal the request arrived on for `/portal/auth`. Both applications proxy that path back to this API, so this API answers it, with a second redirect to the sign-in screen each one really serves: `/signin?error=<code>` on the workspace application and `/sign-in?error=<code>` on a portal. The client reads the `error` query parameter there and says what to do about it. The values are Better Auth's, not this API's; the ones a person actually meets are `email_not_found` (a GitHub account with no address this App may read), `account_not_linked`, `signup_disabled`, `please_restart_the_process` (an expired or reused `state`) and `unable_to_create_user`. Treat an unrecognised value as a generic failure and offer the sign-in screen again.\n\n**A sign-in link can be built for the mobile app.** `POST /auth/sign-in/magic-link` accepts an optional `client` of `web` or `mobile`, or an optional `callbackURL` — and the only values it accepts are the workspace application's own origin and `pealboard://`, the app's private-use scheme. A `mobile` link is `pealboard://sign-in/link#token=…`; a `web` one, and the default, is the same path on the application. Anything else is refused with `invalid_request` rather than silently sent to the default, because the link is a live credential and quietly delivering it elsewhere is an open redirect with a password in it. The token is in the fragment either way, so it is never in a server log or a `Referer`.\n\n**Signing up carries a Turnstile token.** `POST /auth/sign-up/email` takes `turnstileToken` beside `email`, `password` and `name`: the widget's response, from a Cloudflare Turnstile widget on the sign-up form. The API verifies it with Cloudflare before Better Auth sees the request, and checks the hostname the challenge was solved on as well as the token, because a token solved on somebody else's page is a valid token for the wrong site. Every way of not having a solved challenge is a refusal, and they read differently on purpose: no token at all is `422` with `\"This form needs its challenge completed.\"`, which is the one a client without the widget meets; a token that does not verify or was solved on another site is `403`; a verifier that cannot be reached is `502`, and the account is not created. A deployment with no `TURNSTILE_SECRET` does not check, and creates the account — which is what makes a local run and a preview usable, and is not true of `api.pealboard.com`. The portal's two sign-in paths and `POST /portal/requests` on an open portal take the same field and answer the same way.","license":{"name":"Proprietary","url":"https://pealboard.com/legal/terms"},"termsOfService":"https://pealboard.com/legal/terms","contact":{"name":"Pealboard","url":"https://pealboard.com/contact"}},"tags":[{"name":"Account","description":"Who you are, and which workspaces you belong to. No workspace is chosen."},{"name":"Workspaces","description":"The tenant: its name, its slug, and the calling person's role in it."},{"name":"Members","description":"The people in a workspace, their roles, and invitations to join."},{"name":"GitHub","description":"The App installation, the repositories it covers, and the state of the sync."},{"name":"Issues","description":"Issues across every connected repository: search, create, edit, comment, bulk edit."},{"name":"Projects","description":"GitHub Projects (v2), their fields, and the items on them."},{"name":"Labels","description":"Repository labels. A label belongs to one repository."},{"name":"Milestones","description":"Repository milestones. A milestone belongs to one repository."},{"name":"Views","description":"Saved filter, sort, grouping and layout over issues, and running one as a list or a board."},{"name":"Portals","description":"Portals as a workspace member administers them: request types, companies, customers, requests, the hold queue."},{"name":"Portal","description":"The portal-facing API, on a portal's own host, for a signed-in customer."},{"name":"Notifications","description":"The Inbox: your own notifications in this workspace."},{"name":"Integrations","description":"The outgoing Slack webhook."},{"name":"Attachments","description":"Files added to a request or an issue, stored by Pealboard and served through signed URLs."},{"name":"Billing","description":"The workspace's subscription: plan, standing, checkout and Stripe's own portal."},{"name":"API keys","description":"Credentials for scripts, integrations and the MCP server. Prefix `pb_`."},{"name":"Audit log","description":"Every write in the workspace, with the actor and the credential. Admins only."}],"servers":[{"url":"https://api.pealboard.com","description":"production"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A session token, or an API key beginning `pb_`. A browser sends the session cookie instead and needs no header. `X-Workspace-Id` names the workspace when the credential belongs to more than one."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"A stable identifier for the kind of failure. Safe to branch on."},"message":{"type":"string","description":"What went wrong, written for a person."},"detail":{"type":"object","additionalProperties":{},"description":"Structured particulars, when there are any."},"remedy":{"type":"string","description":"What to do about it, when there is something to do."},"requestId":{"type":"string","description":"Quote this when asking about a specific failure."}},"required":["code","message","requestId"]}},"required":["error"]},"Me":{"type":"object","properties":{"userId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"email":{"type":"string","format":"email"},"github":{"type":["object","null"],"properties":{"login":{"type":"string"},"userId":{"type":"number"},"writesAsSelf":{"type":"boolean"}},"required":["login","userId","writesAsSelf"],"description":"The person's linked GitHub identity, shared across every workspace they belong to."},"workspaceCount":{"type":"integer"}},"required":["userId","name","email","github","workspaceCount"]},"Workspace":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"slug":{"type":"string","minLength":2,"maxLength":40,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","description":"Lowercase letters, digits and hyphens.","example":"acme"},"createdAt":{"type":"string","format":"date-time"},"role":{"type":"string","enum":["owner","admin","member","viewer"],"description":"The calling person's role in this workspace."},"github":{"type":"object","properties":{"connected":{"type":"boolean"},"accountLogin":{"type":["string","null"]},"suspended":{"type":"boolean"}},"required":["connected","accountLogin","suspended"],"description":"The state of this workspace's GitHub connection."}},"required":["id","name","slug","createdAt","role","github"]},"Ok":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]},"Account":{"type":"object","properties":{"userId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"email":{"type":"string","format":"email"},"github":{"type":["object","null"],"properties":{"login":{"type":"string"},"userId":{"type":"number"},"writesAsSelf":{"type":"boolean"}},"required":["login","userId","writesAsSelf"]},"workspace":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"slug":{"type":"string","minLength":2,"maxLength":40,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","description":"Lowercase letters, digits and hyphens.","example":"acme"},"role":{"type":"string","enum":["owner","admin","member","viewer"],"description":"owner: billing and deleting the workspace. admin: members, portals, the GitHub connection. member: issues, comments, project fields. viewer: read only."},"membershipId":{"type":"string","format":"uuid","description":"A UUID this API issued."}},"required":["id","name","slug","role","membershipId"],"description":"The workspace this credential is acting in, or null when the request named none."},"credential":{"type":"object","properties":{"via":{"type":"string","enum":["session","api_key"]},"apiKeyId":{"type":["string","null"],"format":"uuid","description":"A UUID this API issued."},"scopes":{"type":"array","items":{"type":"string","enum":["self:read","workspace:read","workspace:write","members:read","members:write","github:read","github:write","issues:read","issues:write","projects:read","projects:write","portals:read","portals:write","requests:read","requests:write","views:read","views:write","keys:read","keys:write"]},"description":"Empty means the key is not narrowed."}},"required":["via","apiKeyId","scopes"],"description":"What this request was authenticated with."}},"required":["userId","name","email","github","workspace","credential"]},"Member":{"type":"object","properties":{"membershipId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"userId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["owner","admin","member","viewer"],"description":"owner: billing and deleting the workspace. admin: members, portals, the GitHub connection. member: issues, comments, project fields. viewer: read only."},"github":{"type":["object","null"],"properties":{"login":{"type":"string"},"writesAsSelf":{"type":"boolean"}},"required":["login","writesAsSelf"],"description":"The member's linked GitHub identity. `writesAsSelf` is their choice to have issues they create through Pealboard authored as themselves rather than as pealboard[bot]."},"createdAt":{"type":"string","format":"date-time"}},"required":["membershipId","userId","name","email","role","github","createdAt"]},"Invitation":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["owner","admin","member","viewer"],"description":"owner: billing and deleting the workspace. admin: members, portals, the GitHub connection. member: issues, comments, project fields. viewer: read only."},"invitedBy":{"type":"string","description":"The name of the member who sent it."},"workspaceName":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","email","role","invitedBy","workspaceName","expiresAt","createdAt"]},"Installation":{"type":"object","properties":{"connected":{"type":"boolean"},"installationId":{"type":["number","null"]},"accountLogin":{"type":["string","null"]},"accountType":{"type":["string","null"],"enum":["Organization","User",null]},"repositorySelection":{"type":["string","null"],"enum":["all","selected",null]},"suspended":{"type":"boolean"},"permissions":{"type":"object","additionalProperties":{"type":"string"}},"events":{"type":"array","items":{"type":"string"}},"repositoryCount":{"type":"number"},"syncEnabledCount":{"type":"number"}},"required":["connected","installationId","accountLogin","accountType","repositorySelection","suspended","permissions","events","repositoryCount","syncEnabledCount"]},"Repository":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"githubId":{"type":"number"},"nodeId":{"type":"string"},"ownerLogin":{"type":"string"},"name":{"type":"string"},"fullName":{"type":"string"},"private":{"type":"boolean"},"archived":{"type":"boolean"},"htmlUrl":{"type":"string","format":"uri"},"syncEnabled":{"type":"boolean","description":"Whether Pealboard mirrors this repository's issues. Off until a member turns it on."},"removed":{"type":"boolean","description":"True when the installation no longer has access to it."},"syncedAt":{"type":["string","null"],"format":"date-time"}},"required":["id","githubId","nodeId","ownerLogin","name","fullName","private","archived","htmlUrl","syncEnabled","removed","syncedAt"]},"SyncStatus":{"type":"object","properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/SyncJob"}},"budget":{"type":["object","null"],"properties":{"limit":{"type":"number"},"remaining":{"type":"number"},"resetAt":{"type":["string","null"],"format":"date-time"},"backfillPaused":{"type":"boolean","description":"True when the hourly GitHub budget is under 10%. Backfills wait; writes continue."}},"required":["limit","remaining","resetAt","backfillPaused"]}},"required":["jobs","budget"]},"SyncJob":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"kind":{"type":"string","enum":["repository_backfill","projects_backfill","reconcile"]},"repositoryId":{"type":["string","null"],"format":"uuid","description":"A UUID this API issued."},"repositoryFullName":{"type":["string","null"]},"status":{"type":"string","enum":["queued","running","paused","done","failed"]},"phase":{"type":"string","description":"Which part of the walk the job is in."},"error":{"type":["string","null"]},"resumeAfter":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"finishedAt":{"type":["string","null"],"format":"date-time"}},"required":["id","kind","repositoryId","repositoryFullName","status","phase","error","resumeAfter","createdAt","finishedAt"]},"Issue":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"nodeId":{"type":"string"},"number":{"type":"number"},"title":{"type":"string"},"body":{"type":["string","null"],"description":"What the person typed. The attribution block Pealboard writes for a portal request or for a member, and the hidden marker beside it, are stripped here and kept on GitHub. Sending an edited body back keeps them."},"state":{"type":"string","enum":["open","closed"],"description":"GitHub's own word for whether the issue is open or closed."},"stateReason":{"type":["string","null"],"enum":["completed","not_planned","duplicate","reopened",null],"description":"Why the issue is in this state. Null while it is open. `duplicate` is what makes a portal show a request as Merged."},"repository":{"$ref":"#/components/schemas/IssueRepository"},"author":{"$ref":"#/components/schemas/GithubActor"},"authoredBy":{"$ref":"#/components/schemas/AuthoredBy"},"assignees":{"type":"array","items":{"$ref":"#/components/schemas/GithubActor"}},"labels":{"type":"array","items":{"$ref":"#/components/schemas/IssueLabel"}},"milestone":{"$ref":"#/components/schemas/Milestone"},"issueType":{"$ref":"#/components/schemas/IssueType"},"subIssues":{"$ref":"#/components/schemas/SubIssuesSummary"},"projectItems":{"type":"array","items":{"$ref":"#/components/schemas/IssueProjectItem"}},"requests":{"type":"array","items":{"$ref":"#/components/schemas/LinkedRequest"},"description":"Empty for an issue no portal knows about, which is most of them."},"commentsCount":{"type":"number"},"locked":{"type":"boolean"},"htmlUrl":{"type":"string","format":"uri","description":"The issue on GitHub."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"closedAt":{"type":["string","null"],"format":"date-time"},"syncedAt":{"type":"string","format":"date-time","description":"When Pealboard last read this issue from GitHub."}},"required":["id","nodeId","number","title","body","state","stateReason","repository","author","authoredBy","assignees","labels","milestone","issueType","subIssues","projectItems","requests","commentsCount","locked","htmlUrl","createdAt","updatedAt","closedAt","syncedAt"],"example":{"id":"8f1c2b3e-0a4d-4e6f-9c11-2d3e4f5a6b7c","nodeId":"I_kwDOAbc123","number":412,"title":"Board columns lose their order after a drag","body":"Dragging a card to the last column reorders every other column.","state":"open","stateReason":null,"repository":{"id":"1b2c3d4e-5f60-4718-8293-a4b5c6d7e8f9","nodeId":"R_kgDOAbc123","fullName":"mersoft-corp/pealboard-api","ownerLogin":"mersoft-corp","name":"pealboard-api","private":false},"author":{"id":4891,"login":"jdoe","avatarUrl":null,"type":"User"},"authoredBy":null,"assignees":[{"id":5120,"login":"asmith","avatarUrl":null,"type":"User"}],"labels":[{"nodeId":"LA_kwDOAbc","name":"bug","color":"d73a4a"}],"milestone":null,"issueType":null,"subIssues":null,"projectItems":[],"requests":[],"commentsCount":3,"locked":false,"htmlUrl":"https://github.com/mersoft-corp/pealboard-api/issues/412","createdAt":"2026-09-01T09:14:00.000Z","updatedAt":"2026-09-08T16:02:11.000Z","closedAt":null,"syncedAt":"2026-09-08T16:02:14.000Z"}},"IssueRepository":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"nodeId":{"type":"string"},"fullName":{"type":"string","example":"mersoft-corp/pealboard-api"},"ownerLogin":{"type":"string"},"name":{"type":"string"},"private":{"type":"boolean"}},"required":["id","nodeId","fullName","ownerLogin","name","private"]},"GithubActor":{"type":["object","null"],"properties":{"id":{"type":"number","description":"GitHub's numeric user id."},"login":{"type":"string"},"avatarUrl":{"type":["string","null"],"format":"uri"},"type":{"type":"string","description":"`User`, `Bot` or `Organization`."}},"required":["id","login","avatarUrl","type"]},"AuthoredBy":{"type":["object","null"],"properties":{"membershipId":{"type":["string","null"],"format":"uuid","description":"A UUID this API issued."},"name":{"type":"string","description":"The member's display name. Never an email address."},"kind":{"type":"string","enum":["team"],"description":"A workspace member. The same word a portal uses for the same people."}},"required":["membershipId","name","kind"]},"IssueLabel":{"type":"object","properties":{"nodeId":{"type":"string"},"name":{"type":"string"},"color":{"type":"string"}},"required":["nodeId","name","color"]},"Milestone":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"nodeId":{"type":"string"},"repositoryId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"number":{"type":"number"},"title":{"type":"string"},"description":{"type":["string","null"]},"state":{"type":"string","enum":["open","closed"]},"dueOn":{"type":["string","null"],"format":"date-time"}},"required":["id","nodeId","repositoryId","number","title","description","state","dueOn"]},"IssueType":{"type":["object","null"],"properties":{"id":{"type":"number"},"nodeId":{"type":"string"},"name":{"type":"string"},"color":{"type":["string","null"]}},"required":["id","nodeId","name","color"],"description":"The organization issue type, when the organization uses them."},"SubIssuesSummary":{"type":["object","null"],"properties":{"total":{"type":"number"},"completed":{"type":"number"},"percentCompleted":{"type":"number"}},"required":["total","completed","percentCompleted"]},"IssueProjectItem":{"type":"object","properties":{"itemNodeId":{"type":"string","description":"Pass this to the project item routes."},"projectId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"projectNodeId":{"type":"string"},"projectTitle":{"type":"string"},"fieldValues":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ProjectFieldValue"},"description":"Keyed by the field's node id, which is what GitHub identifies it by."}},"required":["itemNodeId","projectId","projectNodeId","projectTitle","fieldValues"]},"ProjectFieldValue":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["text"]},"text":{"type":"string"}},"required":["type","text"]},{"type":"object","properties":{"type":{"type":"string","enum":["number"]},"number":{"type":"number"}},"required":["type","number"]},{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"date":{"type":"string"}},"required":["type","date"]},{"type":"object","properties":{"type":{"type":"string","enum":["single_select"]},"optionId":{"type":"string"},"name":{"type":"string"}},"required":["type","optionId","name"]},{"type":"object","properties":{"type":{"type":"string","enum":["iteration"]},"iterationId":{"type":"string"},"title":{"type":"string"},"startDate":{"type":"string"},"duration":{"type":"number"}},"required":["type","iterationId","title","startDate","duration"]},{"type":"object","properties":{"type":{"type":"string","enum":["multi_select"]},"optionIds":{"type":"array","items":{"type":"string"}}},"required":["type","optionIds"]}],"description":"One field's value on a project item. The shape follows GitHub's own union, so `type` says which of the other keys is present."},"LinkedRequest":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"portalId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"portalSlug":{"type":"string","minLength":2,"maxLength":40,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","description":"Lowercase letters, digits and hyphens.","example":"acme"},"portalName":{"type":"string"},"number":{"type":"string","description":"The portal-local request number.","example":"ACME-42"},"visibility":{"type":"string","enum":["private","company","public"]},"voteCount":{"type":"number"}},"required":["id","portalId","portalSlug","portalName","number","visibility","voteCount"],"description":"A portal request that points at this issue. One issue maps to at most one request per portal."},"FieldValueInput":{"type":["object","null"],"properties":{"optionId":{"type":"string","description":"A single-select option id."},"optionName":{"type":"string","description":"A single-select option by name, matched case-insensitively."},"text":{"type":"string"},"number":{"type":"number"},"date":{"type":"string","format":"date","description":"`YYYY-MM-DD`."},"iterationId":{"type":"string"},"optionIds":{"type":"array","items":{"type":"string"},"description":"A multi-select field's options."}},"description":"Send exactly one key. Send `null` in place of the whole object to clear the field. `Assignees`, `Labels`, `Milestone` and `Repository` cannot be set this way — GitHub requires those to be changed on the issue itself."},"IssueComment":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"nodeId":{"type":"string"},"issueId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"body":{"type":"string","description":"What the person typed. The attribution footer and the marker are stripped here and kept on GitHub; sending an edited body back keeps them."},"author":{"$ref":"#/components/schemas/GithubActor"},"authoredBy":{"$ref":"#/components/schemas/AuthoredBy"},"visibility":{"type":"string","enum":["internal","public"],"description":"`public` means a customer of the named portal can see it. Every other comment is internal and never reaches a customer."},"portalId":{"type":["string","null"],"format":"uuid","description":"Set when the comment was written in a portal by a customer."},"htmlUrl":{"type":"string","format":"uri"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","nodeId","issueId","body","author","authoredBy","visibility","portalId","htmlUrl","createdAt","updatedAt"]},"ActivityEntry":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"action":{"type":"string","description":"`issue.created`, `issue.closed`, `comment.added`, `project.field_set`, and the rest."},"actor":{"type":"object","additionalProperties":{},"description":"Who did it: a member, a customer, GitHub, or a Pealboard job."},"actorName":{"type":["string","null"],"description":"The display name behind `actor`, resolved when it names a member of this workspace. Null for a job, for GitHub's own users, and for a member who has since left."},"detail":{"type":"object","additionalProperties":{}},"createdAt":{"type":"string","format":"date-time"}},"required":["id","action","actor","actorName","detail","createdAt"]},"Project":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"nodeId":{"type":"string"},"number":{"type":"number"},"title":{"type":"string"},"shortDescription":{"type":["string","null"]},"ownerLogin":{"type":"string"},"public":{"type":"boolean"},"closed":{"type":"boolean"},"url":{"type":"string","format":"uri","description":"The project on GitHub."},"fields":{"type":"array","items":{"$ref":"#/components/schemas/ProjectField"}},"itemCount":{"type":"number"},"syncedAt":{"type":"string","format":"date-time"}},"required":["id","nodeId","number","title","shortDescription","ownerLogin","public","closed","url","fields","itemCount","syncedAt"]},"ProjectField":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"nodeId":{"type":"string","description":"What every field value is keyed by. Survives a rename."},"name":{"type":"string"},"dataType":{"type":"string","description":"GitHub's `ProjectV2FieldType`: TEXT, SINGLE_SELECT, NUMBER, DATE, ITERATION, and the rest."},"options":{"type":["array","null"],"items":{"$ref":"#/components/schemas/SingleSelectOption"},"description":"In the order the project shows them. Null unless the field is a single select."},"isIssueField":{"type":"boolean","description":"True when the field is an organization issue field linked into this project."}},"required":["id","nodeId","name","dataType","options","isIssueField"]},"SingleSelectOption":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"color":{"type":"string"},"description":{"type":"string"}},"required":["id","name","color","description"]},"ProjectItem":{"type":"object","properties":{"itemNodeId":{"type":"string"},"projectId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"content":{"$ref":"#/components/schemas/ProjectItemContent"},"fieldValues":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ProjectFieldValue"}},"archived":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["itemNodeId","projectId","content","fieldValues","archived","createdAt","updatedAt"]},"ProjectItemContent":{"type":"object","properties":{"type":{"type":"string","enum":["Issue","PullRequest","DraftIssue","Redacted"]},"issueId":{"type":["string","null"],"format":"uuid","description":"Set when the item is an issue in a synced repository."},"number":{"type":["number","null"]},"title":{"type":["string","null"]},"state":{"type":["string","null"],"enum":["open","closed",null],"description":"GitHub's own word for whether the issue is open or closed."},"repositoryFullName":{"type":["string","null"]},"htmlUrl":{"type":["string","null"],"format":"uri"}},"required":["type","issueId","number","title","state","repositoryFullName","htmlUrl"],"description":"What the card points at. `Redacted` is GitHub's answer for an item in a repository the installation cannot see; its content is null and that is not an error."},"Label":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"nodeId":{"type":"string"},"repositoryId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"color":{"type":"string","description":"Six hexadecimal digits, with no leading `#`.","example":"0F3D34"},"description":{"type":["string","null"]}},"required":["id","nodeId","repositoryId","name","color","description"]},"View":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"layout":{"type":"string","enum":["list","board","table"],"description":"`board` groups by the view's `groupBy`; the other two do not."},"filter":{"$ref":"#/components/schemas/IssueFilter"},"sort":{"$ref":"#/components/schemas/IssueSort"},"groupBy":{"$ref":"#/components/schemas/GroupBy"},"columns":{"type":"array","items":{"type":"string"},"description":"The columns the `table` layout shows, in order. Empty means the client's own default set."},"shared":{"type":"boolean","description":"Shared with the workspace. A view that is not shared is visible only to the member who owns it."},"ownerMembershipId":{"type":["string","null"],"format":"uuid","description":"Null for a shared view."},"builtIn":{"type":["string","null"],"description":"Which of the six seeded views this started as, or null for one somebody made."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","name","layout","filter","sort","groupBy","columns","shared","ownerMembershipId","builtIn","createdAt","updatedAt"],"example":{"id":"3c9a1f52-7b8e-4d21-9f03-6a5b4c3d2e1f","name":"Needs triage","layout":"list","filter":{"state":"open","noProjectStatus":true},"sort":{"key":"created","order":"desc"},"groupBy":null,"columns":[],"shared":true,"ownerMembershipId":null,"builtIn":"needs_triage","createdAt":"2026-09-09T10:00:00.000Z","updatedAt":"2026-09-09T10:00:00.000Z"}},"IssueFilter":{"type":"object","properties":{"q":{"type":"string","description":"Full text over title, number, label names and body."},"repository":{"type":"array","items":{"type":"string","format":"uuid","description":"A UUID this API issued."},"description":"Repository ids. Any of them matches."},"state":{"type":"string","enum":["open","closed","all"]},"label":{"type":"array","items":{"type":"string"},"description":"Label names. An issue must carry every one of them."},"assignee":{"type":"array","items":{"type":"string"},"description":"GitHub logins, `@me` for the caller, or `none` for unassigned. Any of them matches."},"milestone":{"type":"array","items":{"type":"string"},"description":"Milestone ids, or `none`."},"project":{"type":"array","items":{"type":"string","format":"uuid","description":"A UUID this API issued."},"description":"Project ids. Any of them matches."},"type":{"type":"array","items":{"type":"string"},"description":"Issue type names, or `none`."},"field":{"type":"object","additionalProperties":{"type":"string"},"description":"Project field node id to the value it must hold, or `none` for not set. Every entry must match.\n\nThe value is matched against the word on the card, and against an option id or an iteration id for a caller that already asked GitHub. A number is compared **as a number**, so `3` matches a stored `3.0`. Five operators are written into the value, because the parameter's name is already the field's node id: `>=`, `>`, `<=`, `<`, and `a..b` for an inclusive range. A comparison operand has to be a number or a date as `YYYY-MM-DD`; anything else is a 422 rather than a refusal from Postgres. A date comparison also matches an iteration, by its start date."},"updatedSince":{"type":"string","description":"An ISO 8601 instant, or a window such as `-7d`, `-24h`, `-2w`."},"closedSince":{"type":"string","description":"The same forms as `updatedSince`."},"noProjectStatus":{"type":"boolean","description":"Issues on no project, or on one whose `Status` field is not set."},"hasPortalRequest":{"type":"boolean","description":"Issues that are a request in at least one portal."}}},"IssueSort":{"type":"object","properties":{"key":{"type":"string","enum":["updated","created","number","title","comments","field"],"default":"updated"},"order":{"type":"string","enum":["asc","desc"],"default":"desc"},"fieldNodeId":{"type":"string","description":"The project field to order by, when `key` is `field`. A number sorts numerically, a date and an iteration chronologically, a single select in the project's own option order, and a text field alphabetically. An issue with no value for the field sorts last, whichever direction is asked for. A node id that names no field falls back to the default sort, so a view saved against a field somebody has since deleted still opens."}}},"GroupBy":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["project_field"]},"fieldNodeId":{"type":"string","description":"Any kind of project field. A single select's columns are its options, an iteration field's are its iterations, a date's are months, and a number's or a text field's are the distinct values this board holds."},"fieldName":{"type":"string","description":"For a view saved before any project existed. Resolved when the board is run."},"projectId":{"type":"string","format":"uuid","description":"A UUID this API issued."}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","enum":["label_prefix"]},"prefix":{"type":"string","example":"area:"}},"required":["kind","prefix"]},{"type":"object","properties":{"kind":{"type":"string","enum":["assignee"]}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","enum":["milestone"]}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","enum":["state"]}},"required":["kind"]},{"type":"null"}],"description":"What a board's columns are made of. Only the `board` layout reads it."},"Board":{"type":"object","properties":{"viewId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"groupBy":{"$ref":"#/components/schemas/GroupBy"},"field":{"allOf":[{"$ref":"#/components/schemas/ProjectField"},{"type":["object","null"]}]},"project":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"nodeId":{"type":"string"},"number":{"type":"number"},"title":{"type":"string"},"url":{"type":"string","format":"uri","description":"The project on GitHub."}},"required":["id","nodeId","number","title","url"],"description":"The project the columns came from, when they came from one. Present so the board can say which: a workspace can have two projects that both have a `Status`, and columns with nothing naming their project are columns nobody can tell are the wrong ones. Null for a board grouped by state, assignee, milestone or a label prefix."},"columns":{"type":"array","items":{"$ref":"#/components/schemas/BoardColumn"}}},"required":["viewId","groupBy","field","project","columns"]},"BoardColumn":{"type":"object","properties":{"id":{"type":"string","description":"Stable within the board: an option id, a label name, a login, a milestone id, or a state."},"name":{"type":"string","description":"What the column is headed."},"color":{"type":["string","null"]},"count":{"type":"number","description":"How many issues are in the column, which is more than the page returned."},"cards":{"type":"array","items":{"$ref":"#/components/schemas/Issue"}},"nextCursor":{"type":["string","null"],"description":"Pass back as `cursor` with `column` naming this column to page it."}},"required":["id","name","color","count","cards","nextCursor"]},"Notification":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"kind":{"type":"string","enum":["issue.assigned","issue.mentioned","portal.request_received","portal.status_changed","portal.public_comment","sync.failed"],"description":"Why this was sent."},"title":{"type":"string"},"detail":{"type":["string","null"]},"path":{"type":["string","null"],"description":"Where to go in the workspace application, such as `/issues/<id>`."},"issueId":{"type":["string","null"],"format":"uuid","description":"A UUID this API issued."},"readAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","kind","title","detail","path","issueId","readAt","createdAt"]},"Portal":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"slug":{"type":"string","minLength":2,"maxLength":40,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","description":"Lowercase letters, digits and hyphens.","example":"acme"},"name":{"type":"string"},"description":{"type":["string","null"]},"url":{"type":"string","format":"uri","description":"Where customers reach this portal."},"requestPrefix":{"type":"string","description":"The `ACME` in `ACME-42`."},"branding":{"$ref":"#/components/schemas/Branding"},"accessMode":{"type":"string","enum":["invite_only","domain_allowlist","open"],"description":"invite_only: a member adds each customer, and an unknown address is refused before a code is sent. domain_allowlist: anyone whose verified email domain is listed joins the company that domain is mapped to. open: anyone may sign in, and a new customer's first request waits in the hold queue."},"allowedDomains":{"type":"array","items":{"type":"string"}},"defaultVisibility":{"type":"string","enum":["private","company","public"],"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says."},"shareGithubComments":{"type":"boolean","description":"When on, every comment on a shared issue is visible to this portal's customers, including ones written before the setting was turned on. Off by default."},"notifyStatusChange":{"type":"boolean"},"notifyPublicComment":{"type":"boolean"},"counts":{"type":"object","properties":{"requestTypes":{"type":"integer"},"companies":{"type":"integer"},"customers":{"type":"integer"},"requests":{"type":"integer"},"held":{"type":"integer"}},"required":["requestTypes","companies","customers","requests","held"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","slug","name","description","url","requestPrefix","branding","accessMode","allowedDomains","defaultVisibility","shareGithubComments","notifyStatusChange","notifyPublicComment","counts","createdAt"]},"Branding":{"type":"object","properties":{"logoAttachmentId":{"type":["string","null"],"format":"uuid","description":"An attachment uploaded with `POST /v1/attachments`. Rendered through a signed URL."},"logoUrl":{"type":["string","null"],"format":"uri","description":"A signed link to the logo, good for 30 days. Read only."},"accentColor":{"type":["string","null"],"pattern":"^#[0-9a-fA-F]{6}$"},"welcomeText":{"type":["string","null"],"maxLength":2000}},"required":["logoAttachmentId","logoUrl","accentColor","welcomeText"]},"RequestType":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"portalId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"description":{"type":["string","null"]},"form":{"type":"array","items":{"$ref":"#/components/schemas/FormField"}},"destination":{"type":"object","properties":{"repositoryId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"repositoryFullName":{"type":"string"},"labels":{"type":"array","items":{"type":"string"}},"projectNodeId":{"type":["string","null"]},"initialFieldValues":{"type":"object","additionalProperties":{}},"issueType":{"type":["string","null"]}},"required":["repositoryId","repositoryFullName","labels","projectNodeId","initialFieldValues","issueType"]},"statusMap":{"type":"array","items":{"$ref":"#/components/schemas/StatusRule"}},"position":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","portalId","name","description","form","destination","statusMap","position","createdAt"]},"FormField":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z0-9_]+$","description":"The key a submission sends this field's value under."},"label":{"type":"string","minLength":1,"maxLength":120,"description":"The heading this field becomes in the issue body."},"type":{"type":"string","enum":["text","textarea","select","checkbox","email","url","number","date"],"description":"text is one line and textarea is many. select needs `options`; every other type ignores them."},"required":{"type":"boolean","default":false},"options":{"type":"array","items":{"type":"string","minLength":1,"maxLength":120},"maxItems":50},"helpText":{"type":"string","maxLength":300}},"required":["key","label","type"]},"StatusRule":{"type":"object","properties":{"when":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["closed"]},"stateReason":{"type":["string","null"],"enum":["completed","not_planned","duplicate",null]}},"required":["kind","stateReason"]},{"type":"object","properties":{"kind":{"type":"string","enum":["project_status"]},"optionName":{"type":"string","minLength":1,"maxLength":120}},"required":["kind","optionName"]},{"type":"object","properties":{"kind":{"type":"string","enum":["label"]},"name":{"type":"string","minLength":1,"maxLength":120}},"required":["kind","name"]}]},"status":{"type":"string","enum":["Open","Planned","In progress","Waiting on you","Completed","Merged","Closed"],"description":"The customer-facing word. Seven, fixed, and never GitHub's own vocabulary."}},"required":["when","status"],"description":"First match wins, in the order given. An empty list means the default rules in ADR 0009."},"Company":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}},"customerCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","name","domains","customerCount","createdAt"]},"Customer":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The `portal_members` id. Not the portal user's id."},"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","enum":["customer","manager"],"description":"customer: submits requests and sees their own, their company's and the portal's public ones. manager: a customer-side lead who sees every request in their company and can invite other customers into it."},"company":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"}},"required":["id","name"]},"lastSignedInAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","email","name","role","company","lastSignedInAt","createdAt"]},"PortalRequest":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"reference":{"type":"string","description":"`ACME-42`.","example":"ACME-42"},"number":{"type":"integer"},"title":{"type":"string"},"status":{"$ref":"#/components/schemas/RequestStatus"},"visibility":{"type":"string","enum":["private","company","public"],"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says."},"issue":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"number":{"type":"integer"},"htmlUrl":{"type":"string","format":"uri"},"state":{"type":"string","enum":["open","closed"]}},"required":["id","number","htmlUrl","state"],"description":"Null while the request is in the hold queue: it has not reached GitHub."},"requestType":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"}},"required":["id","name"]},"requester":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"email":{"type":"string","format":"email"}},"required":["id","name","email"]},"company":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"}},"required":["id","name"]},"voteCount":{"type":"integer"},"held":{"type":"boolean"},"rejectedAt":{"type":["string","null"],"format":"date-time"},"url":{"type":"string","format":"uri","description":"The request in its portal."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","reference","number","title","status","visibility","issue","requestType","requester","company","voteCount","held","rejectedAt","url","createdAt","updatedAt"]},"RequestStatus":{"type":"object","properties":{"id":{"type":"string","enum":["open","planned","in_progress","waiting_on_you","completed","merged","closed"],"description":"The stable identifier a client filters on. The word is in `status.label`."},"label":{"type":"string","enum":["Open","Planned","In progress","Waiting on you","Completed","Merged","Closed"],"description":"The customer-facing word. Seven, fixed, and never GitHub's own vocabulary."}},"required":["id","label"],"description":"Derived from the issue at read time, never stored."},"PortalSite":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":["string","null"]},"branding":{"$ref":"#/components/schemas/Branding"},"accessMode":{"type":"string","enum":["invite_only","domain_allowlist","open"],"description":"invite_only: a member adds each customer, and an unknown address is refused before a code is sent. domain_allowlist: anyone whose verified email domain is listed joins the company that domain is mapped to. open: anyone may sign in, and a new customer's first request waits in the hold queue."},"requestTypes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"description":{"type":["string","null"]},"form":{"type":"array","items":{"$ref":"#/components/schemas/FormField"}}},"required":["id","name","description","form"]}}},"required":["name","description","branding","accessMode","requestTypes"],"description":"Everything the portal's sign-in screen needs, with no credential. It names no repository, no label and no project: a destination is the workspace's business."},"PortalMe":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"email":{"type":"string","format":"email"},"name":{"type":"string"},"role":{"type":"string","enum":["customer","manager"],"description":"customer: submits requests and sees their own, their company's and the portal's public ones. manager: a customer-side lead who sees every request in their company and can invite other customers into it."},"company":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"}},"required":["id","name"]},"portal":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string","minLength":2,"maxLength":40,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","description":"Lowercase letters, digits and hyphens.","example":"acme"},"defaultVisibility":{"type":"string","enum":["private","company","public"],"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says."}},"required":["name","slug","defaultVisibility"]}},"required":["id","email","name","role","company","portal"]},"PortalExport":{"type":"object","properties":{"exportedAt":{"type":"string","format":"date-time"},"portal":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string","minLength":2,"maxLength":40,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","description":"Lowercase letters, digits and hyphens.","example":"acme"}},"required":["name","slug"]},"customer":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"company":{"type":["string","null"]}},"required":["name","email","company"]},"requests":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","enum":["Open","Planned","In progress","Waiting on you","Completed","Merged","Closed"],"description":"The customer-facing word. Seven, fixed, and never GitHub's own vocabulary."},"visibility":{"type":"string","enum":["private","company","public"],"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says."},"submittedAt":{"type":"string","format":"date-time"},"body":{"type":"string"},"comments":{"type":"array","items":{"type":"object","properties":{"body":{"type":"string"},"author":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}},"required":["body","author","createdAt"]}}},"required":["reference","title","status","visibility","submittedAt","body","comments"]}}},"required":["exportedAt","portal","customer","requests"]},"CustomerRequest":{"type":"object","properties":{"reference":{"type":"string"},"title":{"type":"string"},"status":{"$ref":"#/components/schemas/RequestStatus"},"visibility":{"type":"string","enum":["private","company","public"],"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says."},"requestType":{"type":["string","null"]},"submittedBy":{"type":"string","description":"The display name. Never an email address."},"company":{"type":["string","null"]},"voteCount":{"type":"integer"},"viewerVoted":{"type":"boolean"},"viewerSubscribed":{"type":"boolean"},"unread":{"type":"boolean","description":"Something has happened on this request since this customer last opened it."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["reference","title","status","visibility","requestType","submittedBy","company","voteCount","viewerVoted","viewerSubscribed","unread","createdAt","updatedAt"]},"CustomerRequestDetail":{"type":"object","properties":{"reference":{"type":"string"},"title":{"type":"string"},"status":{"$ref":"#/components/schemas/RequestStatus"},"visibility":{"type":"string","enum":["private","company","public"],"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says."},"requestType":{"type":["string","null"]},"submittedBy":{"type":"string","description":"The display name. Never an email address."},"company":{"type":["string","null"]},"voteCount":{"type":"integer"},"viewerVoted":{"type":"boolean"},"viewerSubscribed":{"type":"boolean"},"unread":{"type":"boolean","description":"Something has happened on this request since this customer last opened it."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"body":{"type":"string","description":"The request as it was submitted, without the attribution block."},"comments":{"type":"array","items":{"$ref":"#/components/schemas/PortalComment"}},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}}},"required":["reference","title","status","visibility","requestType","submittedBy","company","voteCount","viewerVoted","viewerSubscribed","unread","createdAt","updatedAt","body","comments","attachments"]},"PortalComment":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"body":{"type":"string"},"author":{"type":"object","properties":{"name":{"type":"string"},"kind":{"type":"string","enum":["customer","team"],"description":"Who wrote it, as a customer needs to know: somebody at their company, or the team."}},"required":["name","kind"]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","body","author","createdAt"]},"Attachment":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"filename":{"type":"string"},"contentType":{"type":"string"},"sizeBytes":{"type":"integer"},"url":{"type":"string","format":"uri","description":"Signed, and good for 30 days. Re-signed every time it is read."},"refreshUrl":{"type":"string","description":"Where to ask for a fresh link. `GET` it and read `url` from the answer. A request older than 30 days carries a `url` that no longer works and a `refreshUrl` that does.","example":"/portal/attachments/6f1b0a2c-0b0e-4a35-9a7d-9f9a2a3e4b5c"},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["id","filename","contentType","sizeBytes","url","refreshUrl","expiresAt","createdAt"]},"SlackIntegration":{"type":"object","properties":{"configured":{"type":"boolean"},"hostHint":{"type":["string","null"],"description":"`hooks.slack.com`. The URL itself is never returned."},"pathLast4":{"type":["string","null"],"description":"The last four characters of the webhook path, to recognise which URL is stored."},"enabledKinds":{"type":"array","items":{"type":"string","enum":["issue.assigned","issue.mentioned","portal.request_received","portal.status_changed","portal.public_comment","sync.failed"],"description":"Why this was sent."}},"lastDeliveryAt":{"type":["string","null"],"format":"date-time"},"lastError":{"type":["string","null"],"description":"Slack's own refusal from the last failed post, or null after a success."}},"required":["configured","hostHint","pathLast4","enabledKinds","lastDeliveryAt","lastError"]},"Billing":{"type":"object","properties":{"standing":{"type":"string","enum":["free","trialing","active","past_due","lapsed"],"description":"Only `lapsed` refuses a write, with a 402. `past_due` still writes: Stripe is retrying and moves it on when it gives up."},"plan":{"type":"string","enum":["free","team","business"]},"interval":{"type":["string","null"],"enum":["monthly","annual",null]},"trialEndsAt":{"type":["string","null"],"format":"date-time"},"currentPeriodEnd":{"type":["string","null"],"format":"date-time"},"trialAvailable":{"type":"boolean","description":"A workspace may take the 14-day trial once."},"billableMembers":{"type":"integer","description":"Owners, admins and members. Viewers are free."},"quantity":{"type":"integer","description":"What Stripe is currently charging for."},"limits":{"type":"object","properties":{"members":{"type":["integer","null"]},"portals":{"type":["integer","null"]},"repositories":{"type":["integer","null"]}},"required":["members","portals","repositories"]},"usage":{"type":"object","properties":{"members":{"type":"integer"},"portals":{"type":"integer"},"repositories":{"type":"integer"}},"required":["members","portals","repositories"]},"plans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["free","team","business"]},"name":{"type":"string"},"monthlyCents":{"type":"integer"},"annualCentsPerMonth":{"type":"integer"},"limits":{"type":"object","properties":{"members":{"type":["integer","null"]},"portals":{"type":["integer","null"]},"repositories":{"type":["integer","null"]}},"required":["members","portals","repositories"]}},"required":["id","name","monthlyCents","annualCentsPerMonth","limits"]}}},"required":["standing","plan","interval","trialEndsAt","currentPeriodEnd","trialAvailable","billableMembers","quantity","limits","usage","plans"]},"CheckoutSession":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Send the browser here. It is a `checkout.stripe.com` address."}},"required":["url"]},"ApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"prefix":{"type":"string","description":"Every Pealboard key begins with this.","example":"pb_"},"last4":{"type":"string","description":"The last four characters, to tell two keys apart."},"scopes":{"type":"array","items":{"type":"string","enum":["self:read","workspace:read","workspace:write","members:read","members:write","github:read","github:write","issues:read","issues:write","projects:read","projects:write","portals:read","portals:write","requests:read","requests:write","views:read","views:write","keys:read","keys:write"]},"description":"Empty means the key may do anything the membership that issued it may do."},"createdByMembershipId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"expiresAt":{"type":["string","null"],"format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time","description":"Recorded at most once a minute, so a busy key does not write a row per request."},"createdAt":{"type":"string","format":"date-time"}},"required":["id","name","prefix","last4","scopes","createdByMembershipId","expiresAt","lastUsedAt","createdAt"]},"ApiKeyIssued":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string"},"prefix":{"type":"string","description":"Every Pealboard key begins with this.","example":"pb_"},"last4":{"type":"string","description":"The last four characters, to tell two keys apart."},"scopes":{"type":"array","items":{"type":"string","enum":["self:read","workspace:read","workspace:write","members:read","members:write","github:read","github:write","issues:read","issues:write","projects:read","projects:write","portals:read","portals:write","requests:read","requests:write","views:read","views:write","keys:read","keys:write"]},"description":"Empty means the key may do anything the membership that issued it may do."},"createdByMembershipId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"expiresAt":{"type":["string","null"],"format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time","description":"Recorded at most once a minute, so a busy key does not write a row per request."},"createdAt":{"type":"string","format":"date-time"},"key":{"type":"string","description":"The key itself. This is the only time it is returned; Pealboard stores a SHA-256 digest.","example":"pb_AbCdEf0123456789AbCdEf0123456789AbCdEf01"}},"required":["id","name","prefix","last4","scopes","createdByMembershipId","expiresAt","lastUsedAt","createdAt","key"]},"AuditEntry":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"A UUID this API issued."},"action":{"type":"string","description":"The object, then what happened to it: `member.role_changed`."},"actor":{"type":"object","additionalProperties":{},"description":"Who did it, and with which credential — a session or a named API key."},"target":{"type":"object","properties":{"type":{"type":"string"},"id":{"type":"string"}},"required":["type","id"]},"before":{"type":["object","null"],"additionalProperties":{}},"after":{"type":["object","null"],"additionalProperties":{}},"requestId":{"type":["string","null"],"description":"The `X-Request-Id` of the request that made the change."},"createdAt":{"type":"string","format":"date-time"}},"required":["id","action","actor","target","before","after","requestId","createdAt"]}},"parameters":{}},"paths":{"/auth/error":{"get":{"operationId":"workspaceAuthError","tags":["Account"],"summary":"Where a failed sign-in lands","description":"Better Auth redirects a failed sign-in here with `?error=<code>`, and this hands the browser to the workspace application's sign-in screen with the same code.\n\nIt exists on this side because the application's Worker proxies `/auth/*` to this API unchanged — which is what makes the session cookie belong to the origin the person is looking at, and which means no route in the SPA can answer this path. Without it a failed sign-in ends on the library's own unbranded page.\n\n**Access.** **No credential.** Open to any caller. A sign-in has just failed, so by definition there is no credential. It reads one query parameter and answers with a redirect; it touches nothing.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":false,"name":"error","in":"query"}],"responses":{"302":{"description":"A redirect to the application's sign-in screen."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/github/start":{"get":{"operationId":"startGithubSignIn","tags":["Account"],"summary":"Begin Continue with GitHub","description":"A **GET**, so a native application can open it in a system browser. Everything else about signing in with GitHub is a POST from a page, which a phone cannot do and keep the browser it will come back to.\n\nOpen it on the workspace application's own origin (`https://app.pealboard.com/auth/github/start`), never on the API host: the OAuth `state` lives in a `__Host-` cookie belonging to that origin, and a flow started anywhere else arrives at the callback with no cookie and fails.\n\nWith `client=mobile`, send `codeChallenge` — `base64url(SHA-256(verifier))` for a verifier the app generated. When GitHub is done the browser is sent to `pealboard://auth?token=<code>`. That code is **not** a session: it is single-use, expires in 60 seconds, and is exchanged for a session token at `POST /auth/github/exchange` by sending the verifier. A private-use URI scheme is not claimed by an operating system, so any application on the device can receive that redirect; the verifier is what makes receiving it worthless.\n\n**Access.** **No credential.** Open to any caller. This is where signing in begins, so there is nobody to authenticate yet. It writes one short-lived row naming a PKCE challenge and redirects to GitHub; the credential it eventually issues is bound to that challenge.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["web","mobile"],"default":"web","description":"`mobile` hands the session back through `pealboard://`."},"required":false,"description":"`mobile` hands the session back through `pealboard://`.","name":"client","in":"query"},{"schema":{"type":"string","description":"Required for `mobile`. `base64url(SHA-256(verifier))` of a verifier the app generated and keeps. Without it the code this flow issues could be redeemed by whatever else on the device answered the redirect."},"required":false,"description":"Required for `mobile`. `base64url(SHA-256(verifier))` of a verifier the app generated and keeps. Without it the code this flow issues could be redeemed by whatever else on the device answered the redirect.","name":"codeChallenge","in":"query"},{"schema":{"type":"string","enum":["S256"],"default":"S256","description":"`S256` only. `plain` is a challenge an interceptor can also compute."},"required":false,"description":"`S256` only. `plain` is a challenge an interceptor can also compute.","name":"codeChallengeMethod","in":"query"}],"responses":{"302":{"description":"A redirect to GitHub's authorization screen."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/github/handoff":{"get":{"operationId":"completeGithubSignInForApp","tags":["Account"],"summary":"Hand a finished sign-in back to the app","description":"Where `GET /auth/github/start?client=mobile` ends. It reads the session Better Auth has just established in this browser, mints a single-use code bound to the flow's PKCE challenge, and redirects to `pealboard://auth?token=<code>`. A flow that has expired, or a browser with no session, is sent to the application's own `/auth/error` instead — never to the app with an error the app cannot distinguish from a code.\n\n**Access.** **No credential.** Open to any caller. GitHub's callback has just redirected a browser here and no header this API controls survives that. The handler reads the session cookie itself and issues nothing without one.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":true,"name":"flow","in":"query"}],"responses":{"302":{"description":"A redirect to the app, or to the application's error page."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/github/exchange":{"post":{"operationId":"exchangeGithubSignInCode","tags":["Account"],"summary":"Exchange the code for a session","description":"Takes the `token` from `pealboard://auth?token=…` and the verifier whose challenge started the flow, and answers with a session token for `Authorization: Bearer`.\n\nThe code is spent by this call whether or not the verifier matches, so a wrong verifier is not something to retry — start the sign-in again. An application that intercepted the redirect holds a code and no verifier, and gets nothing.\n\n**Access.** **No credential.** Open to any caller. The code and the PKCE verifier are the credential. There is nothing else to present: the app has no session yet, which is the whole point of the exchange.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":1,"description":"The `token` from the redirect."},"codeVerifier":{"type":"string","minLength":43,"maxLength":128,"description":"The verifier the app generated before `start`."}},"required":["token","codeVerifier"]}}}},"responses":{"200":{"description":"The session.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Send as `Authorization: Bearer <token>`."},"expiresAt":{"type":"string","format":"date-time"}},"required":["token","expiresAt"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/auth/error":{"get":{"operationId":"portalAuthError","tags":["Portal"],"summary":"Where a failed portal sign-in lands","description":"Better Auth redirects a failed portal sign-in here with `?error=<code>`, and this hands the browser back to that portal's own sign-in screen. A customer of the Acme portal never sees a page that mentions Pealboard.\n\n**Access.** **No credential.** Open to any caller. A customer's sign-in has just failed, so there is no session. It reads one query parameter and the forwarded host, and answers with a redirect to that same host.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":false,"name":"error","in":"query"}],"responses":{"302":{"description":"A redirect to this portal's sign-in screen."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/account/me":{"get":{"operationId":"getAccount","tags":["Account"],"summary":"The signed-in person","description":"The person behind the session, their linked GitHub identity, and how many workspaces they belong to. No workspace is chosen, so this does not need `X-Workspace-Id`.\n\n**Access.** Requires a signed-in person. No workspace is chosen. A person reading their own name and address discloses nothing they do not have. Readable by every credential, whatever scopes it is narrowed to.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The person.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateAccount","tags":["Account"],"summary":"Change your name, or how your writes are authored","description":"Only the fields sent are changed.\n\n`writesAsSelf` is the standing choice `chooseCredential` in `github/writes.ts` reads: with it on, an issue or a comment made through Pealboard is authored **as you** in GitHub, with your own token, and carries no attribution block — GitHub already shows your face. With it off, the Pealboard App writes it and the body carries `> **Opened by** <name> through Pealboard.` and a marker, because no GitHub parameter sets the author of an issue.\n\nIt can only be turned on by somebody who has linked GitHub; without a token there is nothing to write as. The address is not editable here: changing it is a sign-in question and goes through `/auth`.\n\n**Access.** Requires a signed-in person. No workspace is chosen. A person editing their own name and their own choice about authorship. Readable by every credential, whatever scopes it is narrowed to.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"writesAsSelf":{"type":"boolean","description":"Needs a linked GitHub account. Refused with a 409 without one."}}}}}},"responses":{"200":{"description":"The person.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteAccount","tags":["Account"],"summary":"Delete my account","description":"Ends every session, removes the linked GitHub account and every membership, and empties the identity: the name becomes `a former member` and the address becomes an unroutable one at `.invalid`, so the address they used is free again and nothing can be sent to it.\n\n**Refused while this person is the sole owner of any workspace**, because a workspace with no owner is unreachable by everybody including its members. The refusal names the workspaces, so there is something to do about it: hand the ownership over, or delete the workspace.\n\nThe `memberships` row is kept, marked removed. Views, notifications, API keys, invitations and the authorship column on issues and comments all point at one, and the audit log records what a member did by naming theirs — dropping the row would leave a workspace's own history pointing at nothing.\n\nNothing in GitHub is touched. Issues and comments belong to the repository, and the attribution Pealboard wrote into a body is the record of who asked for the work rather than a copy of an account.\n\n**Access.** Requires a signed-in person. No workspace is chosen. A person asking to be erased, which nobody else may ask on their behalf. Readable by every credential, whatever scopes it is narrowed to.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The account is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/account/workspaces":{"get":{"operationId":"listWorkspaces","tags":["Account"],"summary":"Workspaces this person belongs to","description":"Every workspace the signed-in person is a member of, with their role in each and the state of its GitHub connection. The `id` of one of these is what goes in `X-Workspace-Id` on every `/v1` request.\n\n**Access.** Requires a signed-in person. No workspace is chosen. A person has to be able to discover which workspaces they may name. Readable by every credential, whatever scopes it is narrowed to.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The workspaces.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createWorkspace","tags":["Workspaces"],"summary":"Create a workspace","description":"Creates a workspace and makes the signed-in person its owner. The slug is derived from the name when it is not given. Reserved slugs are refused: app, api, mcp, www, mail, send, docs, help, status, admin, portal, portals, static, cdn, assets, dev, staging, test, demo, support.\n\n**Access.** Requires a signed-in person. No workspace is chosen. Anyone with an account may start a workspace; there is no tenant to have a role in yet. API-key scope: `workspace:write`.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"What the workspace is called.","example":"Acme"},"slug":{"type":"string","minLength":2,"maxLength":40,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","description":"A short identifier. Derived from the name when omitted.","example":"acme"}},"required":["name"]}}}},"responses":{"201":{"description":"The workspace, with the creator as owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/account/sign-out-everywhere":{"post":{"operationId":"signOutEverywhere","tags":["Account"],"summary":"Sign out on every device","description":"Revokes every session for this person, including the one making the request. API keys are not sessions and are not affected; revoke those individually.\n\n**Access.** Requires a signed-in person. No workspace is chosen. Ending every session is the action a person takes when they believe somebody else has one. Readable by every credential, whatever scopes it is narrowed to.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Every session is gone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/me":{"get":{"operationId":"getCurrentCredential","tags":["Account"],"summary":"The acting credential","description":"Who this request is acting as, in which workspace, with what role, and — for an API key — which scopes it was narrowed to. This is the one route no narrowing can take away.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). A credential must be able to read its own label, or every route documented as defaulting to the caller is unreachable by exactly the narrowed integration keys that default exists for. Readable by every credential, whatever scopes it is narrowed to.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The credential's own label.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspace":{"get":{"operationId":"getWorkspace","tags":["Workspaces"],"summary":"The current workspace","description":"The workspace named by `X-Workspace-Id`, or the only one this credential belongs to.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). The workspace's own name is not a secret from its members. API-key scope: `workspace:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"renameWorkspace","tags":["Workspaces"],"summary":"Rename the workspace","description":"Changes the workspace's name. The slug is not editable: it is an identifier other things point at, and changing it would break links that are already in people's mail.\n\n**Access.** Requires the **admin** role or above (admin, owner). Renaming the workspace changes what every member and every portal link shows. API-key scope: `workspace:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80}},"required":["name"]}}}},"responses":{"200":{"description":"The workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Workspace"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/{workspaceId}":{"delete":{"operationId":"deleteWorkspace","tags":["Workspaces"],"summary":"Delete the workspace","description":"Deletes the workspace and everything in it: portals, requests, companies, customers, views, saved filters, notifications, API keys, the audit log, and the cache of issues, comments, projects and labels. In one transaction, so a failure part-way through leaves the workspace as it was.\n\n**The workspace's own id is in the path**, which every other route in this family takes from `X-Workspace-Id`. That is deliberate and it is the only safety this route can offer: a destructive request should not be able to hit the wrong workspace because a header was stale, and the id being wrong is a `404` rather than a deletion.\n\n**Refused while a Stripe subscription is active or past due**, so nobody deletes a workspace and keeps being charged for it. Cancel the subscription from `POST /v1/billing/portal` first. A trial is cancelled here, because it costs nothing and there is nothing to cancel in Stripe.\n\n**The GitHub installation is unbound and never uninstalled.** The installation belongs to the GitHub account, and removing it is done from that account's settings; what this does is stop Pealboard using it. Nothing in GitHub changes: the issues, the comments and the projects are the repository's and stay exactly as they are, including the attribution Pealboard wrote into them.\n\n**Access.** Requires the **owner**. Deleting a workspace ends it for every member and every customer of every portal in it, and only the person who owns the bill may do that. API-key scope: `workspace:write`.\n\n**Billing.** This still works when the workspace has lapsed. Leaving is not a thing a workspace can be held to a card for, and a lapsed workspace is the one most likely to want to.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"workspaceId","in":"path"}],"responses":{"200":{"description":"The workspace is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/members":{"get":{"operationId":"listMembers","tags":["Members"],"summary":"List members","description":"Every member of the workspace, with their role and their linked GitHub identity when they have one. Removed members are not listed.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). Who else is in the workspace is not a secret from the people in it. API-key scope: `members:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The members.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Member"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/members/{membershipId}":{"patch":{"operationId":"changeMemberRole","tags":["Members"],"summary":"Change a member's role","description":"Only an owner may grant or revoke the owner role. A workspace always has at least one owner, so demoting the last one is refused.\n\n**Access.** Requires the **admin** role or above (admin, owner). A role decides what somebody may write, so granting one is an administrative act. API-key scope: `members:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/members`."},"required":true,"description":"From `GET /v1/members`.","name":"membershipId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["owner","admin","member","viewer"],"description":"owner: billing and deleting the workspace. admin: members, portals, the GitHub connection. member: issues, comments, project fields. viewer: read only."}},"required":["role"]}}}},"responses":{"200":{"description":"The member.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Member"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"removeMember","tags":["Members"],"summary":"Remove a member","description":"Removes a member from the workspace. Their API keys stop working with them. The last owner cannot be removed. Issues and comments they wrote in GitHub are untouched: they belong to GitHub, not to Pealboard.\n\n**Access.** Requires the **admin** role or above (admin, owner). Removing somebody ends their access to every issue and portal in the workspace. API-key scope: `members:write`.\n\n**Billing.** This still works when the workspace has lapsed. Removing a member lowers the bill, and a workspace that has lapsed has to be able to get back inside what it can afford.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/members`."},"required":true,"description":"From `GET /v1/members`.","name":"membershipId","in":"path"}],"responses":{"200":{"description":"The member is removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/invitations":{"post":{"operationId":"createInvitation","tags":["Members"],"summary":"Invite someone to the workspace","description":"Sends an invitation to an email address. The link works once and expires in seven days. An address that already has an open invitation to this workspace is refused rather than sent a second one.\n\n**Access.** Requires the **admin** role or above (admin, owner). An invitation grants a role, so issuing one is an administrative act. API-key scope: `members:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["owner","admin","member","viewer"],"default":"member","description":"owner: billing and deleting the workspace. admin: members, portals, the GitHub connection. member: issues, comments, project fields. viewer: read only."}},"required":["email"]}}}},"responses":{"201":{"description":"The invitation, and the mail is on its way.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invitation"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listInvitations","tags":["Members"],"summary":"Invitations that have not been used","description":"Every invitation to this workspace that is still open: not accepted, not revoked, not expired. An accepted one is a member and appears in `GET /v1/members`; a revoked or expired one is gone from both, because a list of dead invitations is a list nobody acts on.\n\nThe token is not here and cannot be: only a digest of it is stored, which is what makes a leaked database not a list of live invitations.\n\n**Access.** Requires the **admin** role or above (admin, owner). Who has been invited and not yet joined is the other half of the members list, and it is the half an administrator has to be able to take back. API-key scope: `members:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The open invitations.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invitation"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/invitations/{invitationId}":{"delete":{"operationId":"revokeInvitation","tags":["Members"],"summary":"Revoke an invitation","description":"The link in the email stops working immediately. The row is kept, marked revoked, because the audit log records who invited whom and a deleted row would leave that pointing at nothing — and because the partial unique index that stops two live invitations to one address reads `revoked_at`, so the address can be invited again straight away.\n\nAn invitation that has already been accepted is refused: the person is a member, and `DELETE /v1/members/{membershipId}` is what removes them.\n\n**Access.** Requires the **admin** role or above (admin, owner). Taking back a role somebody has been offered is the same act as granting it. API-key scope: `members:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"invitationId","in":"path"}],"responses":{"200":{"description":"The invitation is revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invitations/preview":{"post":{"operationId":"previewInvitation","tags":["Members"],"summary":"What an invitation is for","description":"Reads an invitation without accepting it, so the accept screen can say which workspace and which role before asking for a password. Wrong, used, withdrawn and expired tokens all return the same refusal.\n\n**Access.** **No credential.** Open to any caller. The invitee has no account yet, so there is no credential to require. The token is the credential: single-use, unguessable, expiring, and rate limited on both the token and the address.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":16,"description":"The token from the invitation link."}},"required":["token"]}}}},"responses":{"200":{"description":"The invitation.","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["owner","admin","member","viewer"],"description":"owner: billing and deleting the workspace. admin: members, portals, the GitHub connection. member: issues, comments, project fields. viewer: read only."},"workspaceName":{"type":"string"},"invitedBy":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"hasAccount":{"type":"boolean","description":"True when this address already has a Pealboard account, so the accept screen asks for nothing rather than for a password."}},"required":["email","role","workspaceName","invitedBy","expiresAt","hasAccount"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invitations/accept":{"post":{"operationId":"acceptInvitation","tags":["Members"],"summary":"Accept an invitation","description":"Adds the invited address to the workspace. When the address has no Pealboard account, a name and a password create one and the response signs it in; the address is marked verified, because the token was delivered to it and returned. When the address already has an account, the membership is created and the person signs in as they normally would.\n\n**Access.** **No credential.** Open to any caller. Accepting is the moment the account comes into being, so it cannot require one. Same token, same protections as the preview.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":16,"description":"The token from the invitation link."},"name":{"type":"string","minLength":1,"maxLength":80},"password":{"type":"string","minLength":12,"description":"At least 12 characters. Required only when the address has no account yet."}},"required":["token"]}}}},"responses":{"200":{"description":"The membership exists.","content":{"application/json":{"schema":{"type":"object","properties":{"workspaceId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"workspaceName":{"type":"string"},"role":{"type":"string","enum":["owner","admin","member","viewer"],"description":"owner: billing and deleting the workspace. admin: members, portals, the GitHub connection. member: issues, comments, project fields. viewer: read only."},"signedIn":{"type":"boolean","description":"True when this response carries a new session."}},"required":["workspaceId","workspaceName","role","signedIn"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/github/install-url":{"get":{"operationId":"getGithubInstallUrl","tags":["GitHub"],"summary":"Where to install the Pealboard App","description":"Returns the URL to send the person to. It carries a `state` that names this workspace and expires in 15 minutes. Only a GitHub organization owner can install an App on an organization; a member who tries will send the owner a request instead, and GitHub returns them with `setup_action=request`.\n\n**Access.** Requires the **admin** role or above (admin, owner). Connecting GitHub gives the workspace access to a customer's repositories. API-key scope: `github:write`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The install URL.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"state":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}},"required":["url","state","expiresAt"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/github/setup":{"get":{"operationId":"completeGithubSetup","tags":["GitHub"],"summary":"Finish connecting GitHub","description":"GitHub's setup URL. Verifies that the signed-in member really controls the installation before binding it to the workspace, then walks the repository list. Always answers with a redirect back to the application.\n\n**Access.** **No credential.** Open to any caller. GitHub redirects a browser here with no header this API controls. The handler requires a session itself, resolves the workspace from the one-time `state`, and refuses any installation the signed-in person's own GitHub token does not list.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":false,"name":"installation_id","in":"query"},{"schema":{"type":"string","enum":["install","update","request"]},"required":false,"name":"setup_action","in":"query"},{"schema":{"type":"string"},"required":false,"name":"state","in":"query"}],"responses":{"302":{"description":"A redirect back to the application."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/github/installations/available":{"get":{"operationId":"listAvailableInstallations","tags":["GitHub"],"summary":"GitHub installations you can connect","description":"Every installation of the Pealboard App that the signed-in person can see on GitHub, read with their own linked GitHub token from `GET /user/installations`.\n\nUse this rather than sending somebody to GitHub's install page when the App may already be installed: GitHub sends them to the installation's settings page in that case, and never back here.\n\n`boundTo` says what each one is already connected to: `null` for one that is free, `this_workspace` for the one this workspace holds, and `another_workspace` for one another Pealboard workspace has. A person cannot see which other workspace, only that there is one.\n\n**Access.** Requires the **admin** role or above (admin, owner). Connecting a workspace to a GitHub organization decides whose issues it holds, which is an administrative act. API-key scope: `github:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The installations, and whether each is already connected.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"installationId":{"type":"number"},"accountLogin":{"type":"string"},"accountType":{"type":"string","enum":["Organization","User"]},"accountAvatarUrl":{"type":["string","null"],"format":"uri"},"repositorySelection":{"type":"string","enum":["all","selected"],"description":"GitHub's own words: every repository, or the subset chosen at install."},"boundTo":{"type":["string","null"],"enum":["this_workspace","another_workspace",null],"description":"Null when nothing has claimed it yet."}},"required":["installationId","accountLogin","accountType","accountAvatarUrl","repositorySelection","boundTo"]}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/github/installation":{"post":{"operationId":"bindGithubInstallation","tags":["GitHub"],"summary":"Connect a GitHub installation","description":"Connects an installation from `GET /v1/github/installations/available` to this workspace, and reads its repository list so the next screen has something on it.\n\n**Nothing is enabled for sync.** Choosing what Pealboard mirrors is a deliberate act, and enabling every repository would start a backfill over a customer's entire history without being asked; use `PATCH /v1/github/repositories`.\n\nGitHub is asked again whether the caller can see the installation, so an id guessed or copied from somebody else is refused. An installation another Pealboard workspace holds is a `409`.\n\n**Access.** Requires the **admin** role or above (admin, owner). Binding an installation decides which organization's issues this workspace holds, which is an administrative act. API-key scope: `github:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"installationId":{"type":"integer","exclusiveMinimum":0,"description":"From `GET /v1/github/installations/available`."}},"required":["installationId"]}}}},"responses":{"200":{"description":"The installation is connected.","content":{"application/json":{"schema":{"type":"object","properties":{"accountLogin":{"type":"string"},"repositories":{"type":"number","description":"How many the installation covers. None is enabled for sync."}},"required":["accountLogin","repositories"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"getGithubInstallation","tags":["GitHub"],"summary":"The GitHub connection","description":"The installation this workspace is bound to, its granted permissions and events, and how many repositories it can see. `connected: false` when there is none.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). Whether the workspace is connected to GitHub, and to which account, is context every member needs to read a screen. API-key scope: `github:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The connection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Installation"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"disconnectGithub","tags":["GitHub"],"summary":"Disconnect GitHub","description":"Unbinds the installation from this workspace. Syncing stops, no webhook is applied to this workspace again, and every route that writes to GitHub answers `404` for the installation.\n\n**It does not uninstall the App on GitHub**, and it cannot: the installation belongs to the GitHub account, not to Pealboard, and removing it is done from that account's settings. What this changes is what Pealboard does with it. The sentence to give somebody is: Pealboard has let go of the connection, and GitHub still lists the App until you remove it there.\n\nThe cached issues, comments, projects and portals stay. They are what the workspace has already read, nothing about them is a live GitHub credential, and a workspace that reconnects the same installation finds its own history rather than an empty product. Reconnecting is `POST /v1/github/installation`, which revives the binding.\n\nAlready disconnected is a `200`: this says what the workspace's state is, not what this particular request changed.\n\n**Access.** Requires the **admin** role or above (admin, owner). Disconnecting GitHub stops every sync and every write in the workspace, which is an administrative act and not a member's. API-key scope: `github:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The connection, now disconnected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/github/callback":{"get":{"operationId":"githubOauthCallback","tags":["GitHub"],"summary":"GitHub's OAuth callback","description":"The App's registered callback URL. Forwards to the workspace auth instance's own handler.\n\n**Access.** **No credential.** Open to any caller. GitHub redirects a browser here. Better Auth verifies the `state` cookie and the PKCE verifier before anything is written; this route only rewrites the path.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":false,"name":"code","in":"query"},{"schema":{"type":"string"},"required":false,"name":"state","in":"query"}],"responses":{"302":{"description":"A redirect chosen by the sign-in that started this."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/github/repositories":{"get":{"operationId":"listRepositories","tags":["GitHub"],"summary":"Repositories the installation can see","description":"Every repository the installation grants access to, with whether Pealboard mirrors its issues. Repositories the installation has lost access to are listed with `removed: true` until the nightly cleanup, because their cached issues are still readable.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). The list of connected repositories is the map of what the workspace can see. API-key scope: `github:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The repositories.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Repository"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"setRepositorySync","tags":["GitHub"],"summary":"Choose which repositories to mirror","description":"Turns sync on or off per repository. Turning it on queues a backfill of that repository's labels, milestones, issues and comments; a repository already being walked is not walked twice. Turning it off stops future updates and leaves what is already cached, so a screen that was working does not empty.\n\n**Access.** Requires the **admin** role or above (admin, owner). Enabling a repository starts a walk of its whole history and puts its issues in front of every member. API-key scope: `github:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"enable":{"type":"array","items":{"type":"string","format":"uuid","description":"A UUID this API issued."},"default":[]},"disable":{"type":"array","items":{"type":"string","format":"uuid","description":"A UUID this API issued."},"default":[]}}}}}},"responses":{"200":{"description":"The repositories, after the change.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Repository"}},"queued":{"type":"array","items":{"type":"string","format":"uuid","description":"A UUID this API issued."},"description":"Backfill jobs started."}},"required":["data","queued"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/github/sync":{"post":{"operationId":"startSync","tags":["GitHub"],"summary":"Sync now","description":"Re-reads the repository list, then queues a walk of every enabled repository and of the organization's projects. Repositories already being walked are not queued twice. Work stands down on its own if the GitHub budget falls under 10%, and resumes when it recovers.\n\n**Access.** Requires the **admin** role or above (admin, owner). A manual sync spends the workspace's GitHub request budget. API-key scope: `github:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"responses":{"202":{"description":"The work is queued.","content":{"application/json":{"schema":{"type":"object","properties":{"repositories":{"type":"integer"},"queued":{"type":"array","items":{"type":"string","format":"uuid","description":"A UUID this API issued."}}},"required":["repositories","queued"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/github/sync-status":{"get":{"operationId":"getSyncStatus","tags":["GitHub"],"summary":"What sync is doing","description":"The twenty most recent sync jobs and the installation's GitHub request budget. `backfillPaused` is true when the budget is under 10%: history is waiting, and writes are not.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). Members can see when an object was last synced, because the product has to admit that a cache can lag. API-key scope: `github:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The state of sync.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncStatus"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/github":{"post":{"operationId":"receiveGithubWebhook","tags":["GitHub"],"summary":"GitHub webhooks","description":"The App's webhook endpoint. Verifies, records and enqueues, and answers within milliseconds — GitHub requires a 2xx within ten seconds and does not retry on its own.\n\n**Access.** **No Pealboard credential.** The request's own signature is verified by the handler. GitHub holds no Pealboard credential. Every delivery is verified with HMAC-SHA-256 over the raw body against `X-Hub-Signature-256` before it is parsed, and recorded against `X-GitHub-Delivery` so a redelivery cannot be applied twice.","security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Recorded and queued, or recognised as a redelivery.","content":{"application/json":{"schema":{"type":"object","properties":{"deliveryId":{"type":"string"},"duplicate":{"type":"boolean"}},"required":["deliveryId","duplicate"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/issues":{"get":{"operationId":"listIssues","tags":["Issues"],"summary":"List issues across every repository","description":"Issues from every synced repository in the workspace, from Pealboard's cache of GitHub. No GitHub request is made.\n\nA project field is filtered on with a parameter named `field:<fieldNodeId>`, whose value is the value the field must hold, or `none` for a field that is not set. Field node ids come from `GET /v1/projects/{projectId}`. For example, `?field:PVTSSF_lADO...=In progress`.\n\nThe value matches the word on the card, and also an option id or an iteration id for a caller that already asked GitHub. A number is compared as a number, so `3` matches a stored `3.0`. Five operators are written into the value, because the parameter's name is already the field's node id: `?field:PVTF_x=>=3`, `?field:PVTF_x=<2026-10-01`, `?field:PVTF_x=2026-09-01..2026-09-30`. A comparison operand has to be a number or a date as `YYYY-MM-DD`. A date comparison also matches an iteration, by its start date.\n\nOrdering is by the sort key and then by id, so a cursor never repeats or skips a row.\n\n**Carries a weak `ETag`.** Send it back as `If-None-Match` and an unchanged answer is a `304` with no body.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). Every member reads the issues of the repositories their workspace has connected. API-key scope: `issues:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","example":"MDAwMS0y"},"required":false,"description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"How many rows to return. At most 100."},"required":false,"description":"How many rows to return. At most 100.","name":"limit","in":"query"},{"schema":{"type":"string","description":"Full text across every synced repository, over title, issue number, label names and body. Accepts what a person types, including quoted phrases, `or`, and a leading `-` to exclude.","example":"board columns -duplicate"},"required":false,"description":"Full text across every synced repository, over title, issue number, label names and body. Accepts what a person types, including quoted phrases, `or`, and a leading `-` to exclude.","name":"q","in":"query"},{"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A repository id from `GET /v1/github/repositories`. Repeat for several."},"required":false,"description":"A repository id from `GET /v1/github/repositories`. Repeat for several.","name":"repository","in":"query"},{"schema":{"type":"string","enum":["open","closed","all"],"description":"Defaults to every state."},"required":false,"description":"Defaults to every state.","name":"state","in":"query"},{"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A label name. Repeat for several; an issue must carry every one."},"required":false,"description":"A label name. Repeat for several; an issue must carry every one.","name":"label","in":"query"},{"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A GitHub login, `@me` for the calling person, or `none` for unassigned. Repeat for several; any of them matches."},"required":false,"description":"A GitHub login, `@me` for the calling person, or `none` for unassigned. Repeat for several; any of them matches.","name":"assignee","in":"query"},{"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A milestone id, or `none`. Repeat for several."},"required":false,"description":"A milestone id, or `none`. Repeat for several.","name":"milestone","in":"query"},{"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A project id from `GET /v1/projects`. Repeat for several."},"required":false,"description":"A project id from `GET /v1/projects`. Repeat for several.","name":"project","in":"query"},{"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"An issue type name, or `none`. Repeat for several."},"required":false,"description":"An issue type name, or `none`. Repeat for several.","name":"type","in":"query"},{"schema":{"type":"string","description":"An ISO 8601 instant, or a window such as `-7d`, `-24h`, `-2w`.","example":"-7d"},"required":false,"description":"An ISO 8601 instant, or a window such as `-7d`, `-24h`, `-2w`.","name":"updated_since","in":"query"},{"schema":{"type":"string","enum":["updated","created","number","title","comments","field"],"description":"`field` orders by a project field and needs `sortField`. Everything else is a column on the issue."},"required":false,"description":"`field` orders by a project field and needs `sortField`. Everything else is a column on the issue.","name":"sort","in":"query"},{"schema":{"type":"string","description":"The project field's node id, when `sort` is `field`. From `GET /v1/projects/{projectId}`. A number sorts numerically, a date and an iteration chronologically, a single select in the project's own option order, and a text field alphabetically. An issue with no value for the field sorts last, whichever direction is asked for.","example":"PVTF_lADOBw8Xnc4AbcDzOAK1"},"required":false,"description":"The project field's node id, when `sort` is `field`. From `GET /v1/projects/{projectId}`. A number sorts numerically, a date and an iteration chronologically, a single select in the project's own option order, and a text field alphabetically. An issue with no value for the field sorts last, whichever direction is asked for.","name":"sortField","in":"query"},{"schema":{"type":"string","enum":["asc","desc"]},"required":false,"name":"order","in":"query"}],"responses":{"200":{"description":"One page of issues.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Issue"}},"nextCursor":{"type":["string","null"],"description":"Pass this back as `cursor` for the next page. Null on the last page."}},"required":["data","nextCursor"]}}}},"304":{"description":"Unchanged since the `ETag` the caller sent as `If-None-Match`. No body."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createIssue","tags":["Issues"],"summary":"Create an issue","description":"Creates the issue in GitHub, then caches it. Labels that the repository does not have are created first, because GitHub silently drops an unknown label rather than refusing — which would leave an issue missing the label a workflow depends on.\n\nSend `Idempotency-Key` to make a retry return the first answer rather than creating a second issue.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Creating an issue writes to the customer's GitHub repository under their installation. API-key scope: `issues:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"repositoryId":{"type":"string","format":"uuid","description":"From `GET /v1/github/repositories`."},"title":{"type":"string","minLength":1,"maxLength":256},"body":{"type":"string","maxLength":65536},"labels":{"type":"array","items":{"type":"string"},"description":"Created in the repository if missing."},"assignees":{"type":"array","items":{"type":"string"},"description":"GitHub logins. GitHub drops anybody without access to the repository."},"milestoneId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"issueType":{"type":"string","description":"An organization issue type by name, when the organization uses them."},"project":{"type":"object","properties":{"projectId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"fieldValues":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/FieldValueInput"},"description":"Keyed by field node id. Set after the issue joins the project."}},"required":["projectId"]}},"required":["repositoryId","title"]}}}},"responses":{"201":{"description":"The issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Issue"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/issues/bulk":{"post":{"operationId":"bulkEditIssues","tags":["Issues"],"summary":"Edit up to fifty issues","description":"Applies one change to every issue named. Each issue is a separate GitHub write, serialised through the workspace's installation limiter so a bulk edit cannot race itself into a secondary rate limit.\n\n**Partial success is the normal outcome and is reported as a 200.** The response lists every id with what happened to it; an issue in an archived repository or one GitHub refused fails on its own without stopping the rest. Read `results`, not the status code.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Editing many issues at once is editing issues, and is held to the same role. API-key scope: `issues:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"issueIds":{"type":"array","items":{"type":"string","format":"uuid","description":"A UUID this API issued."},"minItems":1,"maxItems":50,"description":"At most fifty, because each one is a GitHub write."},"change":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"string"},"description":"Replaces the label set on every issue named."},"assignees":{"type":"array","items":{"type":"string"},"description":"Replaces the assignees. GitHub drops anybody without repository access."},"milestoneId":{"type":["string","null"],"format":"uuid","description":"Null clears the milestone. A milestone belongs to one repository."},"state":{"type":"string","enum":["open","closed"],"description":"GitHub's own word for whether the issue is open or closed."},"stateReason":{"type":"string","enum":["completed","not_planned","duplicate","reopened"],"description":"Why the issue is in this state. Null while it is open. `duplicate` is what makes a portal show a request as Merged."},"field":{"type":"object","properties":{"projectId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"fieldNodeId":{"type":"string"},"value":{"$ref":"#/components/schemas/FieldValueInput"}},"required":["projectId","fieldNodeId","value"],"description":"Sets one project field on every issue that is an item of that project."}},"description":"Send exactly one kind of change."}},"required":["issueIds","change"]}}}},"responses":{"200":{"description":"What happened to each issue.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"issueId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"ok":{"type":"boolean"},"error":{"type":["object","null"],"properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"description":"Null when it succeeded."}},"required":["issueId","ok","error"]}},"succeeded":{"type":"number"},"failed":{"type":"number"}},"required":["results","succeeded","failed"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/issues/{issueId}":{"get":{"operationId":"getIssue","tags":["Issues"],"summary":"One issue","description":"The issue with its repository, labels, assignees, milestone, issue type, project field values, sub-issue summary, and the portal requests that point at it. From the cache; `syncedAt` says when that was last read from GitHub.\n\n**Carries a weak `ETag`.** Send it back as `If-None-Match` and an unchanged answer is a `304` with no body.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). Every member reads the issues of the repositories their workspace has connected. API-key scope: `issues:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/issues`."},"required":true,"description":"From `GET /v1/issues`.","name":"issueId","in":"path"}],"responses":{"200":{"description":"The issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Issue"}}}},"304":{"description":"Unchanged since the `ETag` the caller sent as `If-None-Match`. No body."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateIssue","tags":["Issues"],"summary":"Edit an issue","description":"Only the fields sent are changed. `labels` and `assignees` **replace** the sets rather than adding to them, because a client that sends a difference has to know the current one and the current one is a cache that may be stale.\n\n`state` and `stateReason` travel together: GitHub ignores a reason unless the state changes. `not_planned` is what makes a portal show a request as Closed rather than Completed, and `duplicate` is what makes it show Merged.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Editing an issue writes to the customer's GitHub repository under their installation. API-key scope: `issues:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/issues`."},"required":true,"description":"From `GET /v1/issues`.","name":"issueId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":256},"body":{"type":"string","maxLength":65536},"labels":{"type":"array","items":{"type":"string"}},"assignees":{"type":"array","items":{"type":"string"}},"milestoneId":{"type":["string","null"],"format":"uuid","description":"Null clears it."},"state":{"type":"string","enum":["open","closed"],"description":"GitHub's own word for whether the issue is open or closed."},"stateReason":{"type":"string","enum":["completed","not_planned","duplicate","reopened"],"description":"Why the issue is in this state. Null while it is open. `duplicate` is what makes a portal show a request as Merged."}}}}}},"responses":{"200":{"description":"The issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Issue"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/issues/{issueId}/comments":{"get":{"operationId":"listIssueComments","tags":["Issues"],"summary":"List an issue's comments","description":"Oldest first, which is reading order. `visibility` says whether a customer of the named portal can see the comment: `public` for one written in a portal or posted with `Reply to portal`, `internal` for every other comment on the issue.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). A comment on an issue is readable by anybody who can read the issue. API-key scope: `issues:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/issues`."},"required":true,"description":"From `GET /v1/issues`.","name":"issueId","in":"path"},{"schema":{"type":"string","description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","example":"MDAwMS0y"},"required":false,"description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"How many rows to return. At most 100."},"required":false,"description":"How many rows to return. At most 100.","name":"limit","in":"query"}],"responses":{"200":{"description":"One page of comments.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/IssueComment"}},"nextCursor":{"type":["string","null"],"description":"Pass this back as `cursor` for the next page. Null on the last page."}},"required":["data","nextCursor"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"commentOnIssue","tags":["Issues"],"summary":"Comment on an issue","description":"`visibility: \"internal\"` posts an ordinary GitHub comment that no customer ever sees.\n\n`visibility: \"public\"` is **Reply to portal**: the same GitHub comment with a hidden marker appended, and that marker is the only thing that makes it visible to customers of the named portal. Nothing typed into GitHub reaches a customer without it. A public comment needs `portalId`, because a marker names one portal and an issue can be shared to several.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Commenting writes to the customer's GitHub repository under their installation. API-key scope: `issues:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/issues`."},"required":true,"description":"From `GET /v1/issues`.","name":"issueId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":65536},"visibility":{"type":"string","enum":["internal","public"],"default":"internal","description":"`public` means a customer of the named portal can see it. Every other comment is internal and never reaches a customer."},"portalId":{"type":"string","format":"uuid","description":"Required when `visibility` is `public`. The portal whose customers will see it."}},"required":["body"]}}}},"responses":{"201":{"description":"The comment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueComment"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/issues/{issueId}/comments/{commentId}":{"patch":{"operationId":"updateIssueComment","tags":["Issues"],"summary":"Edit a comment you wrote","description":"Only the comment's own author may edit it. The Pealboard App can rewrite any comment in the repository, so this is enforced here rather than by GitHub: a member editing a colleague's words is refused.\n\nA public comment stays public, because the marker travels in the body Pealboard resends.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Editing a comment writes to the customer's GitHub repository under their installation. API-key scope: `issues:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/issues`."},"required":true,"description":"From `GET /v1/issues`.","name":"issueId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/issues/{issueId}/comments`."},"required":true,"description":"From `GET /v1/issues/{issueId}/comments`.","name":"commentId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":65536}},"required":["body"]}}}},"responses":{"200":{"description":"The comment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueComment"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteIssueComment","tags":["Issues"],"summary":"Delete a comment you wrote","description":"Only the comment's own author may delete it. GitHub deletes it outright, so the cached row goes with it; the `activity` row stays and is the only record that it existed.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Deleting a comment writes to the customer's GitHub repository under their installation. API-key scope: `issues:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/issues`."},"required":true,"description":"From `GET /v1/issues`.","name":"issueId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/issues/{issueId}/comments`."},"required":true,"description":"From `GET /v1/issues/{issueId}/comments`.","name":"commentId","in":"path"}],"responses":{"200":{"description":"The comment is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/issues/{issueId}/activity":{"get":{"operationId":"listIssueActivity","tags":["Issues"],"summary":"An issue's activity","description":"Newest first. Every write Pealboard made to this issue, with who made it and with which credential. Changes made in GitHub itself appear as the sync applies them.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). What happened to an issue is readable by anybody who can read the issue. API-key scope: `issues:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/issues`."},"required":true,"description":"From `GET /v1/issues`.","name":"issueId","in":"path"},{"schema":{"type":"string","description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","example":"MDAwMS0y"},"required":false,"description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"How many rows to return. At most 100."},"required":false,"description":"How many rows to return. At most 100.","name":"limit","in":"query"}],"responses":{"200":{"description":"One page of activity.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ActivityEntry"}},"nextCursor":{"type":["string","null"],"description":"Pass this back as `cursor` for the next page. Null on the last page."}},"required":["data","nextCursor"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects":{"get":{"operationId":"listProjects","tags":["Projects"],"summary":"List projects","description":"Every GitHub Project (v2) belonging to the connected organization or user that Pealboard has synced, with its fields and how many items it holds. Closed projects are listed too, and say so.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). A project is not a secret from the members whose issues are on it. API-key scope: `projects:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The projects.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{projectId}":{"get":{"operationId":"getProject","tags":["Projects"],"summary":"One project","description":"The project with every field it has, in the order the project shows them. A single-select field lists its options with their ids — those ids are what `PATCH /v1/projects/{projectId}/items/{itemId}` and the `field:` issue filter take.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). A project is not a secret from the members whose issues are on it. API-key scope: `projects:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/projects`."},"required":true,"description":"From `GET /v1/projects`.","name":"projectId","in":"path"}],"responses":{"200":{"description":"The project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{projectId}/items":{"get":{"operationId":"listProjectItems","tags":["Projects"],"summary":"List a project's items","description":"Most recently changed first. Each item carries a summary of what it points at and every field value it holds, keyed by field node id.\n\nThe **project's own ordering is not returned**: `project_items` holds no position column, because maintaining a total order under out-of-order webhook deliveries would be a cost paid for a value only GitHub's own board reads. A board built from a view orders its cards by the sort the view names.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). The cards on a project are the issues the members can already read. API-key scope: `projects:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/projects`."},"required":true,"description":"From `GET /v1/projects`.","name":"projectId","in":"path"},{"schema":{"type":"string","description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","example":"MDAwMS0y"},"required":false,"description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"How many rows to return. At most 100."},"required":false,"description":"How many rows to return. At most 100.","name":"limit","in":"query"}],"responses":{"200":{"description":"One page of items.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ProjectItem"}},"nextCursor":{"type":["string","null"],"description":"Pass this back as `cursor` for the next page. Null on the last page."}},"required":["data","nextCursor"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"addProjectItem","tags":["Projects"],"summary":"Add an issue to a project","description":"GitHub returns the existing item when the issue is already on the project, so sending this twice adds one card and not two.\n\nThe field values a project's built-in workflow sets on add are not invented here; they arrive with the `projects_v2_item` webhook or the next reconciliation.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Adding an issue to a project changes the customer's GitHub project. API-key scope: `projects:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/projects`."},"required":true,"description":"From `GET /v1/projects`.","name":"projectId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"issueId":{"type":"string","format":"uuid","description":"A UUID this API issued."}},"required":["issueId"]}}}},"responses":{"201":{"description":"The item.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectItem"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{projectId}/items/{itemId}":{"patch":{"operationId":"setProjectItemField","tags":["Projects"],"summary":"Set or clear a field value","description":"Sets one field on one item. This is what dragging a card between board columns does.\n\nA single select takes `optionId` or `optionName`; a date takes `YYYY-MM-DD`; a number takes a number; text takes a string; an iteration takes `iterationId`. Send `\"value\": null` to clear the field.\n\n`Assignees`, `Labels`, `Milestone` and `Repository` cannot be set this way — GitHub requires those to be changed on the issue itself, with `PATCH /v1/issues/{issueId}`.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Setting a field value changes the customer's GitHub project. API-key scope: `projects:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/projects`."},"required":true,"description":"From `GET /v1/projects`.","name":"projectId","in":"path"},{"schema":{"type":"string","minLength":1,"description":"The item's GitHub node id, from `GET /v1/projects/{projectId}/items`. A node id rather than a Pealboard id, because that is what every Projects v2 mutation takes.","example":"PVTI_lADOABCDEF4AbcDEzgAAAAA"},"required":true,"description":"The item's GitHub node id, from `GET /v1/projects/{projectId}/items`. A node id rather than a Pealboard id, because that is what every Projects v2 mutation takes.","name":"itemId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"fieldNodeId":{"type":"string","description":"From the project's `fields`, which is where every field's node id is."},"value":{"type":["object","null"],"properties":{"optionId":{"type":"string","description":"A single-select option id."},"optionName":{"type":"string","description":"A single-select option by name, matched case-insensitively."},"text":{"type":"string"},"number":{"type":"number"},"date":{"type":"string","format":"date","description":"`YYYY-MM-DD`."},"iterationId":{"type":"string"},"optionIds":{"type":"array","items":{"type":"string"},"description":"A multi-select field's options."}},"description":"Null clears the field."}},"required":["fieldNodeId","value"]}}}},"responses":{"200":{"description":"The item.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectItem"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"removeProjectItem","tags":["Projects"],"summary":"Remove an item from a project","description":"Removes the card. **The issue is untouched** — it stays in its repository, keeps its labels and its comments, and can be added to the project again.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Removing a card changes the customer's GitHub project. API-key scope: `projects:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/projects`."},"required":true,"description":"From `GET /v1/projects`.","name":"projectId","in":"path"},{"schema":{"type":"string","minLength":1,"description":"The item's GitHub node id, from `GET /v1/projects/{projectId}/items`. A node id rather than a Pealboard id, because that is what every Projects v2 mutation takes.","example":"PVTI_lADOABCDEF4AbcDEzgAAAAA"},"required":true,"description":"The item's GitHub node id, from `GET /v1/projects/{projectId}/items`. A node id rather than a Pealboard id, because that is what every Projects v2 mutation takes.","name":"itemId","in":"path"}],"responses":{"200":{"description":"The item is removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{projectId}/items/{itemId}/position":{"post":{"operationId":"moveProjectItem","tags":["Projects"],"summary":"Move an item within the project","description":"Places the item after the one named, or at the top when `afterItemId` is omitted — which is GitHub's own rule, not a default chosen here.\n\nThe new position is not cached and is not returned: Pealboard stores no ordering for project items, so the only place to read the result is GitHub's own board.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Moving a card changes the ordering in the customer's GitHub project. API-key scope: `projects:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/projects`."},"required":true,"description":"From `GET /v1/projects`.","name":"projectId","in":"path"},{"schema":{"type":"string","minLength":1,"description":"The item's GitHub node id, from `GET /v1/projects/{projectId}/items`. A node id rather than a Pealboard id, because that is what every Projects v2 mutation takes.","example":"PVTI_lADOABCDEF4AbcDEzgAAAAA"},"required":true,"description":"The item's GitHub node id, from `GET /v1/projects/{projectId}/items`. A node id rather than a Pealboard id, because that is what every Projects v2 mutation takes.","name":"itemId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"afterItemId":{"type":"string","description":"The item node id to place this one after. Omit to move to the top."}}}}}},"responses":{"200":{"description":"The item is moved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/labels":{"get":{"operationId":"listLabels","tags":["Labels"],"summary":"List labels","description":"Every label in the workspace's synced repositories, or in the ones named. **A label belongs to one repository**, so the same name appears once per repository that has it, each with its own id and colour. Sorted by name, then by repository.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). Labels are how issues are filtered, and every member filters issues. API-key scope: `issues:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A repository id from `GET /v1/github/repositories`. Repeat for several. Omit for every synced repository in the workspace."},"required":false,"description":"A repository id from `GET /v1/github/repositories`. Repeat for several. Omit for every synced repository in the workspace.","name":"repository","in":"query"}],"responses":{"200":{"description":"The labels.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Label"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createLabel","tags":["Labels"],"summary":"Create a label","description":"Creates the label in one repository. A name the repository already uses is a conflict, and is reported rather than quietly ignored — the alternative would leave the caller believing they had set the colour.\n\nTo apply a label to an issue, send its **name** in `PATCH /v1/issues/{issueId}`; a label the repository does not have is created there automatically.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Creating a label writes to the customer's GitHub repository under their installation. API-key scope: `issues:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"repositoryId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"name":{"type":"string","minLength":1,"maxLength":50},"color":{"type":"string","pattern":"^[0-9a-fA-F]{6}$","example":"0F3D34"},"description":{"type":"string","maxLength":100}},"required":["repositoryId","name","color"]}}}},"responses":{"201":{"description":"The label.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Label"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/milestones":{"get":{"operationId":"listMilestones","tags":["Milestones"],"summary":"List milestones","description":"Every milestone in the workspace's synced repositories, or in the ones named. **A milestone belongs to one repository**, so an issue can only be given one from its own. Open milestones first, then by due date, then by title.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). Milestones are how issues are filtered, and every member filters issues. API-key scope: `issues:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"anyOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"A repository id from `GET /v1/github/repositories`. Repeat for several. Omit for every synced repository in the workspace."},"required":false,"description":"A repository id from `GET /v1/github/repositories`. Repeat for several. Omit for every synced repository in the workspace.","name":"repository","in":"query"}],"responses":{"200":{"description":"The milestones.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Milestone"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createMilestone","tags":["Milestones"],"summary":"Create a milestone","description":"Creates the milestone in one repository. GitHub stores a due date as an instant at 08:00 UTC on the day named, so reading one back returns a time that was not sent; that is GitHub's rule and the cache records what GitHub returned.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Creating a milestone writes to the customer's GitHub repository under their installation. API-key scope: `issues:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"repositoryId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"title":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string","maxLength":1000},"dueOn":{"type":"string","format":"date","description":"`YYYY-MM-DD`.","example":"2026-12-01"}},"required":["repositoryId","title"]}}}},"responses":{"201":{"description":"The milestone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Milestone"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/views":{"get":{"operationId":"listViews","tags":["Views"],"summary":"List views","description":"The workspace's shared views and the calling member's own personal ones. Another member's personal views are never listed. Shared views first, then by name.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). A view is a saved filter over issues the member can already read. API-key scope: `views:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The views.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/View"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createView","tags":["Views"],"summary":"Create a view","description":"`shared: false` saves it for you alone. `shared: true` puts it in every member's sidebar, so it needs the admin role or above.\n\nA `board` layout needs a `groupBy`; without one there is nothing to make columns from.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Saving a view is a change to the workspace a viewer does not make. API-key scope: `views:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"layout":{"type":"string","enum":["list","board","table"],"default":"list","description":"`board` groups by the view's `groupBy`; the other two do not."},"filter":{"$ref":"#/components/schemas/IssueFilter"},"sort":{"$ref":"#/components/schemas/IssueSort"},"groupBy":{"$ref":"#/components/schemas/GroupBy"},"columns":{"type":"array","items":{"type":"string"},"default":[],"description":"The columns the `table` layout shows, in order."},"shared":{"type":"boolean","default":false,"description":"Shared with the workspace, so it appears for every member. A view that is not shared is visible only to you."}},"required":["name"]}}}},"responses":{"201":{"description":"The view.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/View"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/views/{viewId}":{"get":{"operationId":"getView","tags":["Views"],"summary":"One view","description":"A shared view, or one of your own. Another member's personal view is a 404.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). A view is a saved filter over issues the member can already read. API-key scope: `views:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/views`."},"required":true,"description":"From `GET /v1/views`.","name":"viewId","in":"path"}],"responses":{"200":{"description":"The view.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/View"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateView","tags":["Views"],"summary":"Edit a view","description":"Only the fields sent are changed. A shared view may be edited by an admin or by the member who made it; a personal view only by its owner.\n\nChanging `shared` moves the view between the workspace and the person: sharing one needs the admin role, and unsharing one gives it to the member doing the unsharing.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Editing a view is a change to the workspace a viewer does not make. API-key scope: `views:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/views`."},"required":true,"description":"From `GET /v1/views`.","name":"viewId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"layout":{"type":"string","enum":["list","board","table"],"default":"list","description":"`board` groups by the view's `groupBy`; the other two do not."},"filter":{"$ref":"#/components/schemas/IssueFilter"},"sort":{"$ref":"#/components/schemas/IssueSort"},"groupBy":{"$ref":"#/components/schemas/GroupBy"},"columns":{"type":"array","items":{"type":"string"},"default":[],"description":"The columns the `table` layout shows, in order."},"shared":{"type":"boolean","default":false,"description":"Shared with the workspace, so it appears for every member. A view that is not shared is visible only to you."}}}}}},"responses":{"200":{"description":"The view.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/View"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteView","tags":["Views"],"summary":"Delete a view","description":"A shared view may be deleted by an admin or by the member who made it; a personal view only by its owner. A built-in view can be deleted like any other, and is not re-created.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Deleting a view is a change to the workspace a viewer does not make. API-key scope: `views:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/views`."},"required":true,"description":"From `GET /v1/views`.","name":"viewId","in":"path"}],"responses":{"200":{"description":"The view is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/views/{viewId}/run":{"get":{"operationId":"runView","tags":["Views"],"summary":"Run a view","description":"The issues the view's filter and sort select, paged. Identical to `GET /v1/issues` with the view's own parameters, which is what makes a saved view and a hand-built query return the same issues.\n\n`@me` in a view's `assignee` filter resolves to **the caller**, not to whoever saved the view. That is what lets one shared `Assigned to me` answer a different question for each member.\n\n**Carries a weak `ETag`.** Send it back as `If-None-Match` and an unchanged answer is a `304` with no body.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). Running a view lists issues, which every member may read. API-key scope: `issues:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/views`."},"required":true,"description":"From `GET /v1/views`.","name":"viewId","in":"path"},{"schema":{"type":"string","description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","example":"MDAwMS0y"},"required":false,"description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"How many rows to return. At most 100."},"required":false,"description":"How many rows to return. At most 100.","name":"limit","in":"query"}],"responses":{"200":{"description":"One page of issues.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Issue"}},"nextCursor":{"type":["string","null"],"description":"Pass this back as `cursor` for the next page. Null on the last page."}},"required":["data","nextCursor"]}}}},"304":{"description":"Unchanged since the `ETag` the caller sent as `If-None-Match`. No body."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/views/{viewId}/board":{"get":{"operationId":"runViewBoard","tags":["Views"],"summary":"Run a view as a board","description":"The view's issues arranged into columns by its `groupBy`: a project field, a label prefix, assignees, milestones, or state.\n\n**A project field of any kind makes columns**, and where they come from depends on the kind: a single select's options in the order the project shows them; an iteration field's own iterations, current before completed; one column per month for a date; one column per distinct value for a number or a text field, drawn from the issues this board covers and capped at forty. **Every board has a final column for issues with no value**, because the work nobody has triaged is the work a board exists to surface.\n\nWhen the grouping names a project field **by name**, the project is resolved in this order: `groupBy.projectId` if the view names one; otherwise the view's own `project` filter; otherwise the project carrying the most of this board's issues, with the lowest-numbered project breaking a tie. `board.project` says which was chosen, so a screen can name it — two projects in one workspace can both have a `Status`.\n\nEach column carries its own `count` and its own `nextCursor`. To page one column, send `column` naming it together with `cursor`; the other columns then come back empty, because a client paging one column is not redrawing the board.\n\nA view whose layout is not `board` can still be run as one, provided it has a `groupBy`.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). A board is a way of arranging issues the member may already read. API-key scope: `issues:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/views`."},"required":true,"description":"From `GET /v1/views`.","name":"viewId","in":"path"},{"schema":{"type":"string","description":"Page only this column. Its id comes from a previous board."},"required":false,"description":"Page only this column. Its id comes from a previous board.","name":"column","in":"query"},{"schema":{"type":"string","description":"That column's `nextCursor` from the previous page."},"required":false,"description":"That column's `nextCursor` from the previous page.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"description":"Cards per column. Defaults to 20."},"required":false,"description":"Cards per column. Defaults to 20.","name":"limit","in":"query"}],"responses":{"200":{"description":"The board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Board"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notifications":{"get":{"operationId":"listNotifications","tags":["Notifications"],"summary":"The Inbox","description":"Your own notifications in this workspace, newest first, with the number still unread. `unread: true` returns only the unread ones; `unreadCount` counts them all either way, so a badge stays right while the list is filtered.\n\nEach row carries a `path` into the workspace application — the API does not know how a client routes, so it names where to go rather than a URL.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). A person reads their own notifications, and a credential that cannot read its own Inbox is unusable. Readable by every credential, whatever scopes it is narrowed to.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","example":"MDAwMS0y"},"required":false,"description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"How many rows to return. At most 100."},"required":false,"description":"How many rows to return. At most 100.","name":"limit","in":"query"},{"schema":{"type":"string","enum":["true","false"],"description":"`true` returns only the notifications you have not read."},"required":false,"description":"`true` returns only the notifications you have not read.","name":"unread","in":"query"}],"responses":{"200":{"description":"One page of notifications, and the unread count.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Notification"}},"nextCursor":{"type":["string","null"],"description":"Pass this back as `cursor` for the next page. Null on the last page."},"unreadCount":{"type":"number","description":"Every unread notification, not only the ones on this page."}},"required":["data","nextCursor","unreadCount"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/notifications/read":{"post":{"operationId":"markNotificationsRead","tags":["Notifications"],"summary":"Mark notifications read","description":"Send `notificationIds` to mark those, or `all: true` to mark every unread one. A notification already read is left with the time it was first read, so marking twice does not move the mark.\n\nAn id that names somebody else's notification is ignored rather than refused: the answer is the same either way, and refusing would confirm that the id exists.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). Marking your own notifications read is reading them, and a viewer has an Inbox like anybody else. Readable by every credential, whatever scopes it is narrowed to.\n\n**Billing.** This still works when the workspace has lapsed. Reads continue on a lapsed workspace, and an Inbox that cannot be marked read is a read surface that does not work.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"notificationIds":{"type":"array","items":{"type":"string","format":"uuid","description":"A UUID this API issued."},"maxItems":500},"all":{"type":"boolean","description":"Mark every unread notification read."}},"description":"Send one of `notificationIds` or `all`."}}}},"responses":{"200":{"description":"How many were marked, and how many remain unread.","content":{"application/json":{"schema":{"type":"object","properties":{"marked":{"type":"number"},"unreadCount":{"type":"number"}},"required":["marked","unreadCount"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals":{"get":{"operationId":"listPortals","tags":["Portals"],"summary":"List portals","description":"Every portal in the workspace, with the counts a list screen shows.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Which portals exist and how busy they are is context every member needs before they open an issue. API-key scope: `portals:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The portals.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Portal"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createPortal","tags":["Portals"],"summary":"Create a portal","description":"The slug becomes a hostname, so the reserved list applies and it cannot be changed afterwards. The portal starts invite-only with private requests, which is the setting a workspace can safely leave alone while it decides.\n\n**Access.** Requires the **admin** role or above (admin, owner). A portal exposes the workspace's work to people outside the company, so creating one is an administrative act. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"slug":{"type":"string","minLength":2,"maxLength":40,"pattern":"^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$","description":"Lowercase letters, digits and hyphens.","example":"acme"},"description":{"type":"string","maxLength":500},"requestPrefix":{"type":"string","pattern":"^[A-Z0-9]{2,6}$","description":"Defaults to the first letters of the name."}},"required":["name","slug"]}}}},"responses":{"201":{"description":"The portal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Portal"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}":{"get":{"operationId":"getPortal","tags":["Portals"],"summary":"A portal","security":[{"bearerAuth":[]}],"description":"**Access.** Requires the **member** role or above (member, admin, owner). A member opening a request needs the portal's name and its settings to read the screen. API-key scope: `portals:read`.","parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"}],"responses":{"200":{"description":"The portal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Portal"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updatePortal","tags":["Portals"],"summary":"Change a portal","description":"The slug cannot be changed: it is a hostname, and every link a customer has bookmarked and every marker in a GitHub issue body carries it. Turning on `shareGithubComments` makes every comment on every shared issue visible to this portal's customers, including ones written before it was turned on.\n\n**Access.** Requires the **admin** role or above (admin, owner). Access mode, default visibility and comment sharing decide what people outside the company can read. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":["string","null"],"maxLength":500},"branding":{"type":"object","properties":{"logoAttachmentId":{"type":["string","null"],"format":"uuid","description":"A UUID this API issued."},"accentColor":{"type":["string","null"],"pattern":"^#[0-9a-fA-F]{6}$"},"welcomeText":{"type":["string","null"],"maxLength":2000}}},"accessMode":{"type":"string","enum":["invite_only","domain_allowlist","open"],"description":"invite_only: a member adds each customer, and an unknown address is refused before a code is sent. domain_allowlist: anyone whose verified email domain is listed joins the company that domain is mapped to. open: anyone may sign in, and a new customer's first request waits in the hold queue."},"allowedDomains":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9.-]+\\.[a-z]{2,}$"},"maxItems":50},"defaultVisibility":{"type":"string","enum":["private","company","public"],"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says."},"shareGithubComments":{"type":"boolean"},"notifyStatusChange":{"type":"boolean"},"notifyPublicComment":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The portal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Portal"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deletePortal","tags":["Portals"],"summary":"Delete a portal","description":"The portal stops answering and its customers can no longer sign in. The issues behind its requests are untouched: they belong to GitHub. The slug is not released, because a marker in a GitHub issue body still names it.\n\n**Access.** Requires the **admin** role or above (admin, owner). Closing a portal ends every customer's access to it at once. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"}],"responses":{"200":{"description":"The portal is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/request-types":{"get":{"operationId":"listRequestTypes","tags":["Portals"],"summary":"List request types","security":[{"bearerAuth":[]}],"description":"**Access.** Requires the **member** role or above (member, admin, owner). A member sharing an issue to a portal chooses from the same list of request types. API-key scope: `portals:read`.","parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"}],"responses":{"200":{"description":"The request types, in the order the portal offers them.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RequestType"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createRequestType","tags":["Portals"],"summary":"Create a request type","description":"The form is what a customer fills in; the destination is where the issue goes. `statusMap` overrides the default status rules for requests of this type — leave it empty to use the defaults, which cover closed, project Status and the `needs-info` label.\n\n**Access.** Requires the **admin** role or above (admin, owner). A request type decides which repository a stranger's submission lands in, which is a decision about the workspace's GitHub account. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":["string","null"],"maxLength":500},"form":{"type":"array","items":{"$ref":"#/components/schemas/FormField"},"maxItems":30},"destination":{"type":"object","properties":{"repositoryId":{"type":"string","format":"uuid","description":"Where the issue is created. Required."},"labels":{"type":"array","items":{"type":"string","minLength":1,"maxLength":80},"maxItems":20,"default":[]},"projectNodeId":{"type":["string","null"],"minLength":1},"initialFieldValues":{"type":"object","additionalProperties":{},"default":{}},"issueType":{"type":["string","null"],"minLength":1}},"required":["repositoryId"]},"statusMap":{"type":"array","items":{"$ref":"#/components/schemas/StatusRule"},"maxItems":30,"default":[]},"position":{"type":"integer","minimum":0,"maximum":999,"default":0}},"required":["name","destination"]}}}},"responses":{"201":{"description":"The request type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestType"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/request-types/{requestTypeId}":{"patch":{"operationId":"updateRequestType","tags":["Portals"],"summary":"Change a request type","description":"Changing `statusMap` changes the status word every existing request of this type shows, at once and retroactively. That is the intended behaviour — status is derived and never stored — and it is recorded in the audit log.\n\n**Access.** Requires the **admin** role or above (admin, owner). Changing a destination changes where every future submission of this kind lands. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"requestTypeId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"description":{"type":["string","null"],"maxLength":500},"form":{"type":"array","items":{"$ref":"#/components/schemas/FormField"},"maxItems":30},"destination":{"type":"object","properties":{"repositoryId":{"type":"string","format":"uuid","description":"Where the issue is created. Required."},"labels":{"type":"array","items":{"type":"string","minLength":1,"maxLength":80},"maxItems":20,"default":[]},"projectNodeId":{"type":["string","null"],"minLength":1},"initialFieldValues":{"type":"object","additionalProperties":{},"default":{}},"issueType":{"type":["string","null"],"minLength":1}}},"statusMap":{"type":"array","items":{"$ref":"#/components/schemas/StatusRule"},"maxItems":30},"position":{"type":"integer","minimum":0,"maximum":999}}}}}},"responses":{"200":{"description":"The request type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestType"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteRequestType","tags":["Portals"],"summary":"Delete a request type","description":"Customers can no longer choose it. Requests already submitted through it keep their status rules, because a status word that changed when a form was retired would be a change nobody made.\n\n**Access.** Requires the **admin** role or above (admin, owner). Removing a request type takes it off the portal's form for everybody. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"requestTypeId","in":"path"}],"responses":{"200":{"description":"The request type is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/companies":{"get":{"operationId":"listCompanies","tags":["Portals"],"summary":"List companies","security":[{"bearerAuth":[]}],"description":"**Access.** Requires the **member** role or above (member, admin, owner). A member reading a request needs to know which company asked. API-key scope: `portals:read`.","parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"}],"responses":{"200":{"description":"The companies.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Company"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createCompany","tags":["Portals"],"summary":"Create a company","description":"`domains` places a customer in this company automatically when the portal's access mode is `domain_allowlist`. A domain belongs to one company per portal.\n\n**Access.** Requires the **admin** role or above (admin, owner). A company is the unit request visibility is scoped to, so creating one decides who can read whose requests. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"domains":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9.-]+\\.[a-z]{2,}$"},"maxItems":20,"default":[]}},"required":["name"]}}}},"responses":{"201":{"description":"The company.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Company"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/companies/{companyId}":{"patch":{"operationId":"updateCompany","tags":["Portals"],"summary":"Change a company","security":[{"bearerAuth":[]}],"description":"**Access.** Requires the **admin** role or above (admin, owner). Changing a company's domains changes who joins it automatically. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"domains":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9.-]+\\.[a-z]{2,}$"},"maxItems":20}}}}}},"responses":{"200":{"description":"The company.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Company"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteCompany","tags":["Portals"],"summary":"Delete a company","description":"Refused while customers still belong to it, because a customer with no company can see nothing but their own requests and would not be told why.\n\n**Access.** Requires the **admin** role or above (admin, owner). Deleting a company changes what its customers can see of each other's requests. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"companyId","in":"path"}],"responses":{"200":{"description":"The company is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/customers":{"get":{"operationId":"listCustomers","tags":["Portals"],"summary":"List customers","description":"Everyone who can sign in to this portal. Customers are never billed.\n\n**Access.** Requires the **member** role or above (member, admin, owner). A member answering a request needs to know who asked and which company they are in. API-key scope: `portals:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","example":"MDAwMS0y"},"required":false,"description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"How many rows to return. At most 100."},"required":false,"description":"How many rows to return. At most 100.","name":"limit","in":"query"}],"responses":{"200":{"description":"A page of customers.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}},"nextCursor":{"type":["string","null"],"description":"Pass this back as `cursor` for the next page. Null on the last page."}},"required":["data","nextCursor"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"inviteCustomer","tags":["Portals"],"summary":"Invite a customer","description":"Creates the customer and emails them a link to the portal. There is no password and no invitation token to redeem: the address is the identity, and signing in sends a six-digit code to it. Company is assigned here and a customer never chooses their own.\n\n**Access.** Requires the **admin** role or above (admin, owner). Inviting a customer grants somebody outside the company a way in. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"name":{"type":"string","minLength":1,"maxLength":120},"companyId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"role":{"type":"string","enum":["customer","manager"],"default":"customer","description":"customer: submits requests and sees their own, their company's and the portal's public ones. manager: a customer-side lead who sees every request in their company and can invite other customers into it."}},"required":["email","companyId"]}}}},"responses":{"201":{"description":"The customer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/customers/{customerId}":{"patch":{"operationId":"updateCustomer","tags":["Portals"],"summary":"Change a customer's role or company","description":"Moving a customer to another company changes what they can see immediately, in both directions: their old company's requests close to them and their new company's open.\n\n**Access.** Requires the **admin** role or above (admin, owner). A customer's company and role decide which of their colleagues' requests they can read. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"customerId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["customer","manager"],"description":"customer: submits requests and sees their own, their company's and the portal's public ones. manager: a customer-side lead who sees every request in their company and can invite other customers into it."},"companyId":{"type":"string","format":"uuid","description":"A UUID this API issued."}}}}}},"responses":{"200":{"description":"The customer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"removeCustomer","tags":["Portals"],"summary":"Remove a customer","description":"They can no longer sign in. Their requests stay, and so do the issues behind them: those belong to the workspace. Use `DELETE /portal/me` from the customer's own session to erase the person as well.\n\n**Access.** Requires the **admin** role or above (admin, owner). Removing a customer ends their access to every request in the portal. API-key scope: `portals:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"customerId","in":"path"}],"responses":{"200":{"description":"The customer is removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/requests":{"get":{"operationId":"listPortalRequests","tags":["Portals"],"summary":"List requests","description":"Filterable by status word, company, request type, visibility, and whether the request is waiting in the hold queue. `status` filters on the derived word, so it is applied after the rules run rather than as a column comparison.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Answering requests is the work a portal exists for, and it is ordinary member work. API-key scope: `requests:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","example":"MDAwMS0y"},"required":false,"description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"How many rows to return. At most 100."},"required":false,"description":"How many rows to return. At most 100.","name":"limit","in":"query"},{"schema":{"type":"string","enum":["open","planned","in_progress","waiting_on_you","completed","merged","closed"],"description":"The stable identifier a client filters on. The word is in `status.label`."},"required":false,"description":"The stable identifier a client filters on. The word is in `status.label`.","name":"status","in":"query"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":false,"description":"A UUID this API issued.","name":"companyId","in":"query"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":false,"description":"A UUID this API issued.","name":"requestTypeId","in":"query"},{"schema":{"type":"string","enum":["private","company","public"],"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says."},"required":false,"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says.","name":"visibility","in":"query"},{"schema":{"type":"string","enum":["true","false"],"description":"`true` returns only the hold queue: nothing here has reached GitHub."},"required":false,"description":"`true` returns only the hold queue: nothing here has reached GitHub.","name":"held","in":"query"}],"responses":{"200":{"description":"A page of requests.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PortalRequest"}},"nextCursor":{"type":["string","null"],"description":"Pass this back as `cursor` for the next page. Null on the last page."}},"required":["data","nextCursor"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"shareIssueToPortal","tags":["Portals"],"summary":"Share an issue to a portal","description":"Links an existing issue to this portal as a request. Nothing is duplicated and nothing is written to GitHub: the issue is the same object, and removing it from the portal later leaves it untouched. One issue maps to at most one request per portal.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Sharing an issue to a portal shows work the team already did to the customer who asked for it, which is ordinary member work. API-key scope: `requests:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"issueId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"companyId":{"type":["string","null"],"format":"uuid","description":"A UUID this API issued."},"visibility":{"type":"string","enum":["private","company","public"],"description":"Defaults to the portal's own default visibility."},"requestTypeId":{"type":["string","null"],"format":"uuid","description":"Only for its status rules. No form is filled in and no destination is used."}},"required":["issueId"]}}}},"responses":{"201":{"description":"The request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalRequest"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/requests/{requestId}":{"get":{"operationId":"getPortalRequest","tags":["Portals"],"summary":"A request","security":[{"bearerAuth":[]}],"description":"**Access.** Requires the **member** role or above (member, admin, owner). A member opening the request panel on an issue reads exactly this. API-key scope: `requests:read`.","parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"requestId","in":"path"}],"responses":{"200":{"description":"The request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalRequest"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updatePortalRequest","tags":["Portals"],"summary":"Change a request's visibility or company","description":"Visibility takes effect on the next read, in both directions: making a public request private hides it from everybody but the requester at once.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Changing a request's visibility decides which of the portal's customers can read it. API-key scope: `requests:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"requestId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"visibility":{"type":"string","enum":["private","company","public"],"description":"private: the requester and the workspace. company: everyone in the requester's company. public: everyone in the portal. A manager sees every request in their company whatever this says."},"companyId":{"type":["string","null"],"format":"uuid","description":"A UUID this API issued."}}}}}},"responses":{"200":{"description":"The request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalRequest"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"removeRequestFromPortal","tags":["Portals"],"summary":"Remove a request from a portal","description":"Deletes the request row and leaves the issue untouched in GitHub, along with every comment on it. The customer stops seeing it. The marker in the issue body still names the portal, so a later restore can reconnect the two.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Taking a request off a portal is undoing a share, which is the same kind of act. API-key scope: `requests:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"requestId","in":"path"}],"responses":{"200":{"description":"The request is removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/requests/{requestId}/release":{"post":{"operationId":"releaseHeldRequest","tags":["Portals"],"summary":"Release a request from the hold queue","description":"Creates the issue in GitHub from what the customer submitted, applies the request type's labels, project and initial field values, and emails the requester. Until this is called nothing about the request has reached GitHub.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Releasing a held request is what finally creates the issue in GitHub, which is ordinary member work. API-key scope: `requests:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"requestId","in":"path"}],"responses":{"200":{"description":"The request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalRequest"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/portals/{portalId}/requests/{requestId}/reject":{"post":{"operationId":"rejectHeldRequest","tags":["Portals"],"summary":"Reject a request from the hold queue","description":"Nothing is written to GitHub and the requester is not emailed: the hold queue is a spam control, and telling somebody they were treated as a possible spammer serves nobody. The request shows as Closed to the person who submitted it and to nobody else.\n\n**Access.** Requires the **member** role or above (member, admin, owner). Rejecting a held request is the other half of reviewing the hold queue. API-key scope: `requests:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/portals`."},"required":true,"description":"From `GET /v1/portals`.","name":"portalId","in":"path"},{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"requestId","in":"path"}],"responses":{"200":{"description":"The request is rejected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/site":{"get":{"operationId":"getPortalSite","tags":["Portal"],"summary":"The portal, before signing in","description":"Resolved from the forwarded host. A host that names no portal answers 404 with the same envelope as anything else, so a scan of the wildcard learns nothing but which slugs are taken — which a browser learns anyway.\n\n**Access.** **No credential.** Open to any caller. A portal's sign-in screen has to render before anybody has signed in, and this is the branding, the name and the forms — never a repository, a label or a destination.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The portal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalSite"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/me":{"get":{"operationId":"getPortalMe","tags":["Portal"],"summary":"Who this customer is","security":[{"bearerAuth":[]}],"description":"**Access.** Requires a signed-in **customer** of this portal. A customer reading their own name, company and role in this portal.","responses":{"200":{"description":"The customer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalMe"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deletePortalAccount","tags":["Portal"],"summary":"Delete my account","description":"Removes the account and rewrites, in every issue body and comment Pealboard authored, the line that names this person — to `a former customer`. The issues themselves stay in GitHub because they belong to the workspace, and nothing a member wrote is touched. Files this customer uploaded are deleted. This cannot be undone.\n\n**Access.** Requires a signed-in **customer** of this portal. A customer asking to be erased, which nobody else may ask on their behalf.\n\n**Billing.** This still works when the workspace has lapsed. Erasing yourself is your own right and not the workspace's to withhold, whatever its card is doing.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The account is deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/export":{"get":{"operationId":"exportPortalData","tags":["Portal"],"summary":"Download my data","description":"Every request this customer submitted and every comment they wrote, as JSON. It carries what Pealboard holds about them; it does not carry the workspace's internal comments, because those were never theirs.\n\n**Access.** Requires a signed-in **customer** of this portal. Everything a customer put into this portal, in one file, on their own say-so.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The export.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalExport"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/requests":{"get":{"operationId":"listCustomerRequests","tags":["Portal"],"summary":"My requests","description":"Every request this customer may see: their own at any visibility, their company's when the request is visible to the company, and the portal's public ones. A manager sees every request in their company whatever its visibility.\n\n**Access.** Requires a signed-in **customer** of this portal. The list a customer opens the portal to read.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["mine","company","public","all"],"default":"all","description":"Which of the three lists to return. `all` is everything visible."},"required":false,"description":"Which of the three lists to return. `all` is everything visible.","name":"scope","in":"query"}],"responses":{"200":{"description":"The requests, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomerRequest"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"submitRequest","tags":["Portal"],"summary":"Submit a request","description":"Creates an issue in the repository the request type names, with the form's answers as headed sections and an attribution line naming the requester and their company — never their email address. On an open portal a customer's first request waits in a hold queue and reaches GitHub only when a member releases it. Send `Idempotency-Key` and a retry of the same submission returns the same request rather than creating a second one.\n\n**Access.** Requires a signed-in **customer** of this portal. Submitting a request is what a portal is for, and every signed-in customer may do it.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"requestTypeId":{"type":"string","format":"uuid","description":"A UUID this API issued."},"title":{"type":"string","minLength":3,"maxLength":200,"description":"The issue's title, and the line the customer sees in their list."},"values":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"boolean"},{"type":"number"}]},"default":{},"description":"Keyed by the form field's `key`."},"attachmentIds":{"type":"array","items":{"type":"string","format":"uuid","description":"A UUID this API issued."},"maxItems":10,"default":[]},"visibility":{"type":"string","enum":["private","company","public"],"description":"Only `private` and `public` may be chosen by a customer, and only up to the portal's default. A company-visible request is a member's decision."},"turnstileToken":{"type":"string","description":"Required on an open portal, where anybody may sign in."}},"required":["requestTypeId","title"]}}}},"responses":{"201":{"description":"The request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerRequest"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/requests/{reference}":{"get":{"operationId":"getCustomerRequest","tags":["Portal"],"summary":"A request","description":"The request as it was submitted, its derived status, the public comments, and its attachments with links good for 30 days. Nothing else from the issue is here: labels, assignees, milestones, project fields and internal comments never reach a portal.\n\nEach attachment carries a `refreshUrl`: a link older than 30 days no longer works, and `GET`ting that path signs a new one.\n\n**Carries a weak `ETag`.** Send it back as `If-None-Match` and an unchanged answer is a `304` with no body.\n\n**Access.** Requires a signed-in **customer** of this portal. Reading one request the customer is allowed to see.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":40,"description":"`ACME-42`, or just the number.","example":"ACME-42"},"required":true,"description":"`ACME-42`, or just the number.","name":"reference","in":"path"}],"responses":{"200":{"description":"The request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerRequestDetail"}}}},"304":{"description":"Unchanged since the `ETag` the caller sent as `If-None-Match`. No body."},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/requests/{reference}/comments":{"post":{"operationId":"commentOnRequest","tags":["Portal"],"summary":"Add a comment","description":"Posted to the issue in GitHub as the Pealboard bot, ending with a line naming the customer and their company and a hidden marker that makes it readable in the portal. It is public: everyone who can see the request can read it.\n\n**Access.** Requires a signed-in **customer** of this portal. A comment written in a portal is public by definition; there is no internal surface here.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":40,"description":"`ACME-42`, or just the number.","example":"ACME-42"},"required":true,"description":"`ACME-42`, or just the number.","name":"reference","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":20000}},"required":["body"]}}}},"responses":{"201":{"description":"The comment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalComment"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/requests/{reference}/vote":{"post":{"operationId":"voteForRequest","tags":["Portal"],"summary":"Vote","description":"Public requests only: a private or company request is not something the portal is asking the room about. One vote per customer, and voting twice is the same as voting once.\n\n**Access.** Requires a signed-in **customer** of this portal. One vote per customer on a public request.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":40,"description":"`ACME-42`, or just the number.","example":"ACME-42"},"required":true,"description":"`ACME-42`, or just the number.","name":"reference","in":"path"}],"responses":{"200":{"description":"The vote is counted.","content":{"application/json":{"schema":{"type":"object","properties":{"voteCount":{"type":"integer"}},"required":["voteCount"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"withdrawVote","tags":["Portal"],"summary":"Take back a vote","security":[{"bearerAuth":[]}],"description":"**Access.** Requires a signed-in **customer** of this portal. Taking back a vote the customer cast.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":40,"description":"`ACME-42`, or just the number.","example":"ACME-42"},"required":true,"description":"`ACME-42`, or just the number.","name":"reference","in":"path"}],"responses":{"200":{"description":"The vote is withdrawn.","content":{"application/json":{"schema":{"type":"object","properties":{"voteCount":{"type":"integer"}},"required":["voteCount"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/requests/{reference}/subscribe":{"post":{"operationId":"subscribeToRequest","tags":["Portal"],"summary":"Email me about this request","security":[{"bearerAuth":[]}],"description":"**Access.** Requires a signed-in **customer** of this portal. Asking to hear about a request they can see.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":40,"description":"`ACME-42`, or just the number.","example":"ACME-42"},"required":true,"description":"`ACME-42`, or just the number.","name":"reference","in":"path"}],"responses":{"200":{"description":"Subscribed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"unsubscribeFromRequest","tags":["Portal"],"summary":"Stop emailing me about this request","security":[{"bearerAuth":[]}],"description":"**Access.** Requires a signed-in **customer** of this portal. Stopping mail about a request they follow.\n\n**Billing.** This still works when the workspace has lapsed. Stopping mail is never refused. A person who has asked not to be written to is not a write the workspace's billing gets a say in.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":40,"description":"`ACME-42`, or just the number.","example":"ACME-42"},"required":true,"description":"`ACME-42`, or just the number.","name":"reference","in":"path"}],"responses":{"200":{"description":"Unsubscribed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/unsubscribe":{"post":{"operationId":"unsubscribeByToken","tags":["Portal"],"summary":"Stop emailing me, from a link in an email","security":[{"bearerAuth":[]}],"description":"**Access.** **No credential.** Open to any caller. The link at the bottom of every notification, clicked by somebody who is not signed in. The signed token names one request and one customer and grants nothing but silence.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","minLength":10,"maxLength":400}},"required":["token"]}}}},"responses":{"200":{"description":"Unsubscribed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/attachments":{"post":{"operationId":"uploadPortalAttachment","tags":["Portal"],"summary":"Upload a file","description":"Multipart, one file per call, at most 10 MB. The type is read from the file's own bytes and not from its name. Use the id it returns in `attachmentIds` when submitting a request.\n\n**Access.** Requires a signed-in **customer** of this portal. A screenshot is most of what a customer has to offer, and every customer may attach one.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"The attachment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attachment"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/attachments/{attachmentId}":{"get":{"operationId":"getPortalAttachment","tags":["Portal"],"summary":"A fresh link to a file","description":"Signs a new 30-day link. The bytes are served from the link, not from here.\n\n**Access.** Requires a signed-in **customer** of this portal. Re-signing a link the customer already had.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"attachmentId","in":"path"}],"responses":{"200":{"description":"The attachment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attachment"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/portal/customers":{"post":{"operationId":"inviteColleague","tags":["Portal"],"summary":"Invite a colleague","description":"Adds somebody to the manager's own company, as a customer. A manager cannot choose another company, cannot make another manager, and cannot see the portal's other companies at all.\n\n**Access.** Requires a portal **manager**. A manager is a customer-side lead: bringing a colleague into their own company is the one administrative act the role carries.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"name":{"type":"string","minLength":1,"maxLength":120}},"required":["email"]}}}},"responses":{"201":{"description":"The customer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/integrations/slack":{"get":{"operationId":"getSlackIntegration","tags":["Integrations"],"summary":"The Slack webhook","description":"Whether a webhook is configured, which notification kinds reach it, when it last delivered, and Slack's own words from the last failure. **The URL itself is never returned**; `hostHint` and `pathLast4` are what identifies it.\n\n**Access.** Requires the **admin** role or above (admin, owner). An integration posts as the workspace, so its settings are administrative. API-key scope: `workspace:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The integration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlackIntegration"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"setSlackIntegration","tags":["Integrations"],"summary":"Set the Slack webhook","description":"Replaces whatever was stored. The URL must be a Slack incoming webhook on `hooks.slack.com` — an arbitrary host is refused, because a Worker that will POST workspace content to any URL an admin names is a request forwarder.\n\nSend `webhookUrl` to change the URL, or omit it and send only `enabledKinds` to change which notifications reach a webhook that is already stored.\n\n**Access.** Requires the **admin** role or above (admin, owner). A webhook URL posts into the customer's own Slack channel as the workspace. API-key scope: `workspace:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"webhookUrl":{"type":"string","description":"The Slack incoming webhook URL. Omit to keep the one already stored.","example":"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"},"enabledKinds":{"type":"array","items":{"type":"string","enum":["issue.assigned","issue.mentioned","portal.request_received","portal.status_changed","portal.public_comment","sync.failed"],"description":"Why this was sent."},"default":["issue.assigned","issue.mentioned","portal.request_received","portal.status_changed","portal.public_comment","sync.failed"],"description":"The notification kinds that reach Slack. Empty means none, which is a workspace that saved a URL and then turned everything off — different from having no webhook at all."}}}}}},"responses":{"200":{"description":"The integration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlackIntegration"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteSlackIntegration","tags":["Integrations"],"summary":"Remove the Slack webhook","description":"Deletes the stored URL. Notifications continue to reach the Inbox and email; only Slack stops.\n\n**Access.** Requires the **admin** role or above (admin, owner). Removing an integration stops the workspace posting into a customer's channel. API-key scope: `workspace:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The webhook is removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/integrations/slack/test":{"post":{"operationId":"testSlackIntegration","tags":["Integrations"],"summary":"Send a test message","description":"Posts one message to the stored webhook and records what Slack answered. **A refusal is a 200 with `delivered: false` and Slack's own reason**, not an error: whether the message arrived is the answer being asked for, and a 502 would make the caller guess at it.\n\n`no_service` means the webhook was revoked in Slack; `channel_not_found` means the channel was archived. Both are fixed by pasting a new URL.\n\n**Access.** Requires the **admin** role or above (admin, owner). A test message posts into the customer's channel, which is a write to Slack. API-key scope: `workspace:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"What Slack answered.","content":{"application/json":{"schema":{"type":"object","properties":{"delivered":{"type":"boolean"},"reason":{"type":["string","null"],"description":"Slack's own words when it refused. Null on success."}},"required":["delivered","reason"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing":{"get":{"operationId":"getBilling","tags":["Billing"],"summary":"The workspace's billing","description":"The standing, the plan, what the plan includes and what the workspace is using, and the plan table itself so a client does not repeat the prices. `standing` is the only field that decides anything: only `lapsed` refuses a write.\n\n**Access.** Requires the **admin** role or above (admin, owner). The plan, what it includes and how much of it the workspace is using is what an administrator needs before they decide anything about it. API-key scope: `workspace:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The billing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing/checkout":{"post":{"operationId":"startCheckout","tags":["Billing"],"summary":"Start a checkout","description":"Returns a `checkout.stripe.com` URL to send the browser to. The quantity is the billable-member count at the moment of asking; Stripe prorates every change after that. The subscription is created when payment completes, not when this returns, so nothing about the workspace changes until the `checkout.session.completed` webhook arrives.\n\n**Access.** Requires the **owner**. Committing the workspace to a recurring charge is the owner's, and only the owner's. API-key scope: `workspace:write`.\n\n**Billing.** This still works when the workspace has lapsed. Paying is how a lapse ends, so the route that takes the payment cannot be one of the things a lapse refuses.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["team","business"]},"interval":{"type":"string","enum":["monthly","annual"],"default":"monthly"}},"required":["plan"]}}}},"responses":{"200":{"description":"Where to send the browser.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSession"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing/portal":{"post":{"operationId":"openBillingPortal","tags":["Billing"],"summary":"Open the billing portal","description":"Returns a Stripe Customer Portal URL where the owner can change the plan or interval, update the card, and read every invoice. Cancelling there takes effect at the end of the period, and the workspace lapses then rather than immediately.\n\n**Access.** Requires the **owner**. The billing portal shows and can change the card and the invoices, which is the owner's. API-key scope: `workspace:write`.\n\n**Billing.** This still works when the workspace has lapsed. Adding a card is how a lapse ends, and the card lives in Stripe's own portal.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Where to send the browser.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSession"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing/trial":{"post":{"operationId":"startTrial","tags":["Billing"],"summary":"Start the 14-day trial","description":"14 days of Team, with no card. A workspace gets one, once. When it ends without a payment method the subscription pauses rather than cancelling, so adding a card later resumes it instead of needing a new checkout — and until then writes are refused with a 402 while reads continue.\n\n**Access.** Requires the **owner**. Starting the trial spends the one a workspace gets, so it is the owner's to spend. API-key scope: `workspace:write`.\n\n**Billing.** This still works when the workspace has lapsed. A workspace gets one trial and this route refuses a second with a 409, so the lapse gate would only ever change which refusal a lapsed workspace reads.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The billing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/stripe":{"post":{"operationId":"receiveStripeWebhook","tags":["Billing"],"summary":"Stripe's webhook","description":"Verified with `constructEventAsync`, recorded by event id, then applied. A redelivery of an event already recorded is answered 200 and does nothing.\n\n**Access.** **No Pealboard credential.** The request's own signature is verified by the handler. Stripe signs every delivery with `Stripe-Signature` and this handler verifies it before the body is parsed. No Pealboard credential exists that could be presented here.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Recorded.","content":{"application/json":{"schema":{"type":"object","properties":{"received":{"type":"boolean","enum":[true]},"applied":{"type":"boolean"}},"required":["received","applied"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing/reconcile":{"post":{"operationId":"reconcileBilling","tags":["Billing"],"summary":"Read the subscription back from Stripe","description":"Re-reads this workspace's subscription from Stripe and writes what it says: plan, interval, quantity, status and the trial's end. For the case where a webhook was dropped or failed and the workspace is looking at a plan it is not on. It changes nothing in Stripe. The same repair runs nightly for every workspace, so this is the impatient version of waiting, not a different mechanism. A workspace that has never been billed has nothing to read back and is refused with a 409.\n\n**Access.** Requires the **owner**. Reading the subscription back from Stripe and writing what it says is the owner's, because what it writes is what the workspace is charged for. API-key scope: `workspace:write`.\n\n**Billing.** This still works when the workspace has lapsed. A workspace that has lapsed because a delivery was dropped is exactly the workspace that needs this, and refusing it would make the defect its own defence.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The billing, after the repair.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/attachments":{"post":{"operationId":"uploadAttachment","tags":["Attachments"],"summary":"Upload a file","description":"Multipart, one file per call, at most 10 MB. The type is read from the file's own bytes, never from its name. The id it returns is what a portal's `logoAttachmentId` takes.\n\n**Access.** Requires the **member** role or above (member, admin, owner). A member attaching a file to a reply or to a portal's branding is doing the same kind of work as writing the reply. API-key scope: `issues:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"The attachment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attachment"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/attachments/{attachmentId}":{"get":{"operationId":"getAttachment","tags":["Attachments"],"summary":"A fresh link to a file","description":"Signs a new 30-day link. Links in a GitHub issue body go stale after 30 days by design; opening the issue in Pealboard re-signs them, which is what the line beside each link in the body says.\n\n**Access.** Requires the **viewer** role or above (viewer, member, admin, owner). A member reading an issue needs a live link to whatever is attached to it. API-key scope: `issues:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"attachmentId","in":"path"}],"responses":{"200":{"description":"The attachment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attachment"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/attachments/{attachmentId}":{"get":{"operationId":"downloadAttachment","tags":["Attachments"],"summary":"Download a file","description":"Verifies the signature and the expiry, checks that whatever the file is attached to is still readable by somebody holding only a link, and streams the bytes from R2. Everything else is a 404 in the same envelope: an expired link, a forged one, a deleted file, and a file on a request that has been made private all read identically.\n\n**Access.** **No credential.** Open to any caller. The link is the credential: an HMAC over the id and the expiry, issued by this deployment. It is opened from a GitHub issue body by people who hold no Pealboard credential, which is the whole reason attachments are served rather than linked.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"A UUID this API issued."},"required":true,"description":"A UUID this API issued.","name":"attachmentId","in":"path"},{"schema":{"type":"string","description":"The workspace. From the signed link, and covered by its signature."},"required":true,"description":"The workspace. From the signed link, and covered by its signature.","name":"ws","in":"query"},{"schema":{"type":"string","description":"Unix seconds. From the signed link."},"required":true,"description":"Unix seconds. From the signed link.","name":"exp","in":"query"},{"schema":{"type":"string","description":"The signature. From the signed link."},"required":true,"description":"The signature. From the signed link.","name":"sig","in":"query"}],"responses":{"200":{"description":"The file.","content":{"application/octet-stream":{"schema":{}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/api-keys":{"get":{"operationId":"listApiKeys","tags":["API keys"],"summary":"List API keys","description":"Every key that has not been revoked, newest first, with who issued it, what it is scoped to, and when it was last used. **The key itself is never returned** — only a SHA-256 digest is stored.\n\n`lastUsedAt` is recorded at most once a minute, so a key making a hundred requests a second does not write a hundred rows a second. Treat it as \"used around then\", not as a request log.\n\n**Access.** Requires the **admin** role or above (admin, owner). A list of a workspace's credentials, even without their values, is administrative information. API-key scope: `keys:read`.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The keys.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}},"required":["data"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createApiKey","tags":["API keys"],"summary":"Issue an API key","description":"**The key is in this response and is never shown again.** Copy it now; Pealboard keeps only a digest of it.\n\nThe key acts with the role of the member who issued it, narrowed by `scopes`. An empty `scopes` is not narrowed and may do anything that member may do. `keys:read` and `keys:write` cannot be granted: a credential that can mint credentials makes revoking a leaked one a matter of enumerating its descendants rather than deleting a row.\n\nOnly a signed-in person may issue one, for the same reason.\n\n`Idempotency-Key` is accepted and deliberately not honoured here: recording the answer would keep the key itself in storage for a day, which is the one thing this route promises not to do. A retry issues a second key; revoke whichever you did not keep.\n\n**Access.** Requires the **admin** role or above (admin, owner). Issuing a credential that acts for the workspace is an administrative act. API-key scope: `keys:write`.\n\n**Billing.** Refused with `402` while the workspace's billing has lapsed. Reads continue and nothing is deleted.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"What it is for. Shown in the list and in the audit log.","example":"Deploy script"},"scopes":{"type":"array","items":{"type":"string","enum":["workspace:read","workspace:write","members:read","members:write","github:read","github:write","issues:read","issues:write","projects:read","projects:write","portals:read","portals:write","requests:read","requests:write","views:read","views:write"]},"default":[],"description":"What the key may do. Empty means anything the member who issued it may do."},"expiresAt":{"type":"string","format":"date-time","description":"When it stops working. Omit for a key that does not expire."}},"required":["name"]}}}},"responses":{"201":{"description":"The key, once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyIssued"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace's billing has lapsed, so writes are refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/api-keys/{keyId}":{"delete":{"operationId":"revokeApiKey","tags":["API keys"],"summary":"Revoke an API key","description":"The key stops working immediately and stops being listed. A revoked key reads exactly like one that never existed, so nothing here tells a holder whether their key was revoked or was never valid.\n\n**Access.** Requires the **admin** role or above (admin, owner). Revoking a credential that acts for the workspace is an administrative act. API-key scope: `keys:write`.\n\n**Billing.** This still works when the workspace has lapsed. A leaked credential is contained by revoking it, and an unpaid invoice is no reason to leave one working.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"From `GET /v1/api-keys`."},"required":true,"description":"From `GET /v1/api-keys`.","name":"keyId","in":"path"}],"responses":{"200":{"description":"The key is revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audit":{"get":{"operationId":"listAuditLog","tags":["Audit log"],"summary":"Read the audit log","description":"Every write made in this workspace, newest first, with the actor, the credential kind, the API key when one was used, the request id, and the before and after of anything that changed.\n\nFilter by `action` for one kind of event, or by `since` for a window. What happened to a single issue is `GET /v1/issues/{issueId}/activity`, which every member can read; this is the workspace's own record and is limited to admins.\n\n**Access.** Requires the **admin** role or above (admin, owner). The audit log names who did what with which credential, which is administrative information about people. API-key scope: `workspace:read`.","security":[{"bearerAuth":[]}],"parameters":[{"schema":{"type":"string","description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","example":"MDAwMS0y"},"required":false,"description":"The `nextCursor` from the previous page, unchanged. Omit it to start at the beginning.","name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":50,"description":"How many rows to return. At most 100."},"required":false,"description":"How many rows to return. At most 100.","name":"limit","in":"query"},{"schema":{"type":"string","description":"Exactly one action, such as `member.role_changed`.","example":"api_key.issued"},"required":false,"description":"Exactly one action, such as `member.role_changed`.","name":"action","in":"query"},{"schema":{"type":"string","description":"An ISO 8601 instant, or a window such as `-7d`.","example":"-30d"},"required":false,"description":"An ISO 8601 instant, or a window such as `-7d`.","name":"since","in":"query"}],"responses":{"200":{"description":"One page of the log.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AuditEntry"}},"nextCursor":{"type":["string","null"],"description":"Pass this back as `cursor` for the next page. Null on the last page."}},"required":["data","nextCursor"]}}}},"401":{"description":"No valid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated, but not permitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it is not visible to you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request did not match the expected shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited. Retry after the interval in the header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"GitHub or another dependency refused or did not answer. Nothing in the workspace changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{}}