{"openapi":"3.1.0","info":{"title":"Sekura API for agents","version":"1.0.0","summary":"Security scans of GitHub repositories, started, paid for and read by an agent on the person's behalf.","description":"Every operation the Sekura dashboard has, for an agent acting for one person. The person connects once, in their browser (POST /connect); every call after that carries their API key.\n\nEvery write that commits money or consent is two calls: a GET quote or preview that changes nothing and says exactly what will happen, then the POST that repeats its `disclosureVersion` and `amountCents`. Show the quote's `text` to the person as it stands and confirm only when they agree. A changed version or amount is refused with 409 and the current quote.\n\nA paid confirm is paid by a Stripe Shared Payment Token the person approved in the chat (`payment: { sharedPaymentToken }`, Sekura Once only), by the card on file the quote named (`payment: { savedCard: true }`), or, with no payment, through a Stripe Checkout link answered as 402. A Shared Payment Token cannot pay Sekura Always: it pays one charge, and Always renews monthly.\n\nEvery refusal is `{ code, error, link }`: a code to branch on, one sentence for the person, and where they take the next step. Limits: 120 requests a minute per key, 10 confirmed writes an hour per account, 30 connection starts an hour per address; every 429 carries Retry-After. Bodies over 64 KB are refused with 413.","termsOfService":"https://sekura.ai/terms/","contact":{"name":"Sekura","email":"hello@sekura.ai","url":"https://sekura.ai/connect/#help"}},"externalDocs":{"description":"What the connector does, connecting, paid actions, limits and data handling","url":"https://sekura.ai/connect/#developers"},"servers":[{"url":"https://sekura.ai/api/muse/v1"}],"security":[{"bearerKey":[]}],"tags":[{"name":"connect","description":"Connecting an account, and this document. No key."},{"name":"read","description":"Scope `read`. Changes nothing."},{"name":"write","description":"Scope `write`. Quotes and previews change nothing; the POSTs and the PATCH act."}],"paths":{"/connect":{"post":{"operationId":"startConnect","summary":"Start connecting an account","description":"Answers a secret `code` for the agent to poll with and a `verificationUrl` for the person to open, sign in and approve on. The code is never in the link. At most 30 an hour from one address.","tags":["connect"],"x-scope":"none","security":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"agent":{"description":"The agent's name as the person will see it on the approve page and in Settings (cut to 80 characters). Defaults to \"Meta Muse\".","type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"A connection request. Show the person `verificationUrl` (or `userCode`); poll POST /connect/token with `code`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectStart"}}}},"413":{"description":"The body is over 64 KB. Refused before it is parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectError"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectError"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectError"}}}}}}},"/connect/token":{"post":{"operationId":"pollConnect","summary":"Poll for the API key","description":"Poll with the `code` from POST /connect no faster than its `interval`. 400 `authorization_pending` while the person is on the page, `slow_down` when polled too fast, `access_denied` when they declined, `expired_token` after ten minutes or once the key was collected; `{ apiKey, keyId, scopes }` exactly once.","tags":["connect"],"x-scope":"none","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","pattern":"^[A-Za-z0-9]{40}$","description":"The `code` from POST /connect. Never shown to the person."}},"required":["code"]}}}},"responses":{"200":{"description":"The API key, answered once. Send it as `Authorization: Bearer …` on every other call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectKey"}}}},"400":{"description":"authorization_pending, slow_down, access_denied, expired_token or invalid_request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectError"}}}}}}},"/connect/resume":{"get":{"operationId":"resumeConnect","summary":"Where the Sekura app resumes a connection (browser only)","description":"Used by Sekura's own app after sign-in, with the person's session cookie: answers `{ path }` of the approve page when a connection was interrupted by signing in, or 204. Agents never call it.","tags":["connect"],"x-scope":"none","security":[],"responses":{"200":{"description":"The approve page to return to.","content":{"application/json":{"schema":{"type":"object","required":["path"],"properties":{"path":{"type":"string","pattern":"^/connect/muse\\?code="}}}}}},"204":{"description":"Nothing to resume."}}}},"/account":{"get":{"operationId":"getAccount","summary":"Get Sekura account status","description":"The Sekura account behind this key: who it is, whether the Terms are accepted and the email verified, whether the free first scan is still available, where the Sekura GitHub App is installed, and what is being billed. Call this first; it says what the person must do in the browser before a scan can start.","tags":["read"],"x-scope":"read","x-kind":"read","responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/repositories":{"get":{"operationId":"listRepositories","summary":"List repositories Sekura can scan","description":"The GitHub repositories this account can scan: those the Sekura GitHub App is installed on AND the person's own GitHub account can read, grouped by GitHub account or organisation. Empty repositories are flagged; they cannot be scanned. Up to 100 per installation.","tags":["read"],"x-scope":"read","x-kind":"read","responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/plans":{"get":{"operationId":"getPlans","summary":"Get repository plans","description":"Every repository on this account's dashboard with its plan (Sekura Once or Sekura Always), status, schedule, last scan and plan id (which updateSchedule and cancelPlan take). Pending means waiting on a payment; a cancelled Always repository starts no further scheduled scans and stays on the dashboard to the end of the period already paid for.","tags":["read"],"x-scope":"read","x-kind":"read","responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scans":{"get":{"operationId":"listScans","summary":"List recent scans","description":"This account's most recent Sekura scans, newest first: repository, status, findings by severity and the scan id to pass to getScan, getScanFindings or getReportLink. 1–20 scans, 10 by default.","tags":["read"],"x-scope":"read","x-kind":"read","parameters":[{"name":"limit","in":"query","required":false,"description":"How many scans, 1 to 20. Default 10.","schema":{"description":"How many scans, 1 to 20. Default 10.","type":"integer","minimum":1,"maximum":20}}],"responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"startScan","summary":"Start a Sekura Once scan","description":"Confirms the scan quoteScan described, after the person has read its authorization wording and price and agreed. Carries the disclosureVersion and amountCents quoteScan returned, and the payment the person chose: { sharedPaymentToken } approved in the chat for that amount, { savedCard: true } for the card on file the quote named, or none for a Stripe Checkout link (402). The free first scan needs no payment. Records the authorization in the person's own name and starts the scan, charges the token or the card, or answers 402 with the link. A version or amount that no longer matches is refused with 409 and the current quote, and nothing happens.","tags":["write"],"x-scope":"write","x-kind":"confirm","x-counts-against":"the 10 confirmed writes an hour per account","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"repository":{"type":"string","minLength":3,"maxLength":200,"description":"The repository as owner/name (or its GitHub URL), as listRepositories shows it."},"disclosureVersion":{"type":"string","minLength":1,"maxLength":40,"description":"The disclosureVersion the quote returned, echoed only after the person has read that wording and agreed."},"amountCents":{"description":"The amountCents the quote returned. Required with payment; a different amount is refused with 409 and the current quote, and nothing is charged.","type":"integer","minimum":0,"maximum":10000000},"payment":{"description":"How to pay. Leave out to be answered 402 with a Stripe Checkout link; the free first scan needs none.","anyOf":[{"type":"object","properties":{"savedCard":{"type":"boolean","const":true,"description":"Charge the card on file the quote named by brand and last four."}},"required":["savedCard"],"additionalProperties":false},{"type":"object","properties":{"sharedPaymentToken":{"type":"string","maxLength":255,"pattern":"^spt_[A-Za-z0-9]+$","description":"A Stripe Shared Payment Token the person approved for at least amountCents, in USD."}},"required":["sharedPaymentToken"],"additionalProperties":false}]}},"required":["repository","disclosureVersion"],"additionalProperties":false}}}},"responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment is needed. `link` is a Stripe Checkout page for the person to open (`payment_required`), or the Shared Payment Token could not pay (`spt_*`). Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"413":{"description":"The body is over 64 KB. Refused before it is parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scans/{id}":{"get":{"operationId":"getScan","summary":"Get one scan","description":"One scan in full, brought up to date with the scanner if it is queued or running: status, phase, findings by severity, error, report emails and whether the report can still be opened.","tags":["read"],"x-scope":"read","x-kind":"read","parameters":[{"name":"id","in":"path","required":true,"description":"The scan id, as GET /scans returns it (a dashboard link's ss-… form is accepted too).","schema":{"type":"string","minLength":1,"maxLength":100}}],"responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scans/{id}/findings":{"get":{"operationId":"getScanFindings","summary":"Get a scan's findings","description":"A finished scan's findings, summarised: counts by severity and the most severe findings with title, file and a one-line description (1–50, 20 by default). The full report, with evidence and fixes, is at getReportLink.","tags":["read"],"x-scope":"read","x-kind":"read","parameters":[{"name":"id","in":"path","required":true,"description":"The scan id, as GET /scans returns it (a dashboard link's ss-… form is accepted too).","schema":{"type":"string","minLength":1,"maxLength":100}},{"name":"limit","in":"query","required":false,"description":"How many findings to list, most severe first, 1 to 50. Default 20.","schema":{"description":"How many findings to list, most severe first, 1 to 50. Default 20.","type":"integer","minimum":1,"maximum":50}}],"responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scans/{id}/report-link":{"get":{"operationId":"getReportLink","summary":"Get a scan's report link","description":"The link to a finished scan's full report on sekura.ai. It opens in the person's browser while they are signed in to Sekura, and works until the report's retention window closes.","tags":["read"],"x-scope":"read","x-kind":"read","parameters":[{"name":"id","in":"path","required":true,"description":"The scan id, as GET /scans returns it (a dashboard link's ss-… form is accepted too).","schema":{"type":"string","minLength":1,"maxLength":100}}],"responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/billing":{"get":{"operationId":"getBilling","summary":"Get billing","description":"What this account pays Sekura: the card on file by brand and last four (never more of it), the Sekura Always subscription (repositories, monthly total, next charge, any trial) and Sekura Once scans this month. Changes nothing.","tags":["read"],"x-scope":"read","x-kind":"read","responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scans/quote":{"get":{"operationId":"quoteScan","summary":"Quote a Sekura Once scan","description":"What a Sekura Once security scan of one GitHub repository would cost and what the person would be agreeing to, or the same for scanning again a repository already on the dashboard as Sekura Once. Starts nothing and charges nothing. Returns the authorization wording with its disclosureVersion, the price (free for the account's first scan, otherwise $199) and how it would be paid: the saved card by brand and last four, or a Stripe Checkout link when there is none. Show all of it to the person as it stands and ask whether they agree; only then call startScan with the arguments this answer names. The repository must be granted to the Sekura GitHub App; listRepositories shows which are.","tags":["write"],"x-scope":"write","x-kind":"quote","parameters":[{"name":"repository","in":"query","required":true,"description":"The repository as owner/name (or its GitHub URL), as GET /repositories shows it.","schema":{"type":"string","minLength":3,"maxLength":200,"description":"The repository as owner/name (or its GitHub URL), as listRepositories shows it."}}],"responses":{"200":{"description":"What the confirming call would do, changing nothing: show `text` to the person as it stands, and call `confirmOperation` with `confirmArguments` (and the payment they chose) only once they agree.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scans/{id}/cancel-preview":{"get":{"operationId":"previewCancelScan","summary":"Preview cancelling a scan","description":"Cancels one Sekura scan inside its five-minute cancellation window, in two calls. Without confirm it cancels nothing: it names the scan, its status, until when it can be cancelled, and what cancelling means — a scan no scanner has taken stops for certain; one already running is asked to stop and may still finish; what happens to the money. Show that to the person, and only if they agree, call again with confirm: true. After the window closes the scan cannot be cancelled. listScans gives the scan id.","tags":["write"],"x-scope":"write","x-kind":"quote","parameters":[{"name":"id","in":"path","required":true,"description":"The scan id, as GET /scans returns it (a dashboard link's ss-… form is accepted too).","schema":{"type":"string","minLength":1,"maxLength":100}}],"responses":{"200":{"description":"What the confirming call would do, changing nothing: show `text` to the person as it stands, and call `confirmOperation` with `confirmArguments` (and the payment they chose) only once they agree.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scans/{id}/cancel":{"post":{"operationId":"cancelScan","summary":"Cancel a running Sekura scan","description":"Cancels one Sekura scan inside its five-minute cancellation window, in two calls. Without confirm it cancels nothing: it names the scan, its status, until when it can be cancelled, and what cancelling means — a scan no scanner has taken stops for certain; one already running is asked to stop and may still finish; what happens to the money. Show that to the person, and only if they agree, call again with confirm: true. After the window closes the scan cannot be cancelled. listScans gives the scan id.","tags":["write"],"x-scope":"write","x-kind":"cancel","x-counts-against":"the 10 confirmed writes an hour per account","parameters":[{"name":"id","in":"path","required":true,"description":"The scan id, as GET /scans returns it (a dashboard link's ss-… form is accepted too).","schema":{"type":"string","minLength":1,"maxLength":100}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"scanId":{"description":"Optional; must match the id in the path.","type":"string","maxLength":100},"confirm":{"description":"Optional; the POST is the confirmation.","type":"boolean","const":true}},"additionalProperties":false}}}},"responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"413":{"description":"The body is over 64 KB. Refused before it is parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/always/quote":{"get":{"operationId":"quoteAlways","summary":"Quote Sekura Always for a repository","description":"What subscribing one GitHub repository to Sekura Always ($49 a month, scanned on a schedule) would cost and what the person would be agreeing to, or the same for moving a repository already on the dashboard as Sekura Once onto Always. Changes nothing and charges nothing. Returns the authorization wording with its disclosureVersion, the price and any free trial, the schedule in words, and how it would be paid — the saved card by brand and last four, the Always subscription the account already pays, or a Stripe Checkout link. Show all of it to the person as it stands and ask whether they agree; only then call startAlways with the arguments this answer names. The repository must be granted to the Sekura GitHub App; listRepositories shows which are.","tags":["write"],"x-scope":"write","x-kind":"quote","parameters":[{"name":"repository","in":"query","required":true,"description":"The repository as owner/name (or its GitHub URL), as GET /repositories shows it.","schema":{"type":"string","minLength":3,"maxLength":200,"description":"The repository as owner/name (or its GitHub URL), as listRepositories shows it."}},{"name":"schedule","in":"query","required":false,"description":"The schedule as JSON, e.g. {\"kind\":\"weekly\",\"weekday\":1,\"minute\":540,\"tz\":\"Europe/London\"}. Or send it as the schedule.* parameters instead; one or the other is required.","content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["daily","weekly","monthly"],"description":"daily, weekly, or monthly (the Nth weekday of each month)."},"weekday":{"description":"0 = Sunday … 6 = Saturday. Required for weekly and monthly; ignored for daily.","type":"integer","minimum":0,"maximum":6},"ordinal":{"description":"Monthly only: 1 = first … 4 = fourth, 5 = last, as in \"the last Friday\".","type":"integer","minimum":1,"maximum":5},"minute":{"type":"integer","minimum":0,"maximum":1439,"description":"Minutes after local midnight in tz: 540 is 09:00, 1380 is 23:00."},"tz":{"type":"string","minLength":1,"maxLength":64,"description":"IANA time zone the time is in, e.g. Europe/London or America/New_York."}},"required":["kind","minute","tz"],"additionalProperties":false,"description":"When the repository is scanned, in the person's own time zone."}}}},{"name":"schedule.kind","in":"query","required":false,"description":"daily, weekly or monthly.","schema":{"type":"string","enum":["daily","weekly","monthly"]}},{"name":"schedule.weekday","in":"query","required":false,"description":"0 = Sunday … 6 = Saturday; weekly and monthly.","schema":{"type":"integer","minimum":0,"maximum":6}},{"name":"schedule.ordinal","in":"query","required":false,"description":"Monthly only: 1 = first … 4 = fourth, 5 = last.","schema":{"type":"integer","minimum":1,"maximum":5}},{"name":"schedule.minute","in":"query","required":false,"description":"Minutes after local midnight: 540 is 09:00.","schema":{"type":"integer","minimum":0,"maximum":1439}},{"name":"schedule.tz","in":"query","required":false,"description":"IANA time zone, e.g. Europe/London.","schema":{"type":"string","minLength":1,"maxLength":64}}],"responses":{"200":{"description":"What the confirming call would do, changing nothing: show `text` to the person as it stands, and call `confirmOperation` with `confirmArguments` (and the payment they chose) only once they agree.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/always":{"post":{"operationId":"startAlways","summary":"Subscribe a repository to Sekura Always","description":"Confirms the Sekura Always subscription quoteAlways described, after the person has read its authorization wording, price and schedule and agreed. Carries the same repository and schedule, the disclosureVersion and amountCents quoteAlways returned, and payment: { savedCard: true } to charge the card on file the quote named, or no payment for a Stripe Checkout link (402). A Shared Payment Token cannot pay for Always: it pays one charge and Always renews monthly. Starts the subscription and the first scan, or answers 402 with the link. A version or amount that no longer matches is refused with 409 and the current quote, and nothing happens.","tags":["write"],"x-scope":"write","x-kind":"confirm","x-counts-against":"the 10 confirmed writes an hour per account","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"repository":{"type":"string","minLength":3,"maxLength":200,"description":"The repository as owner/name (or its GitHub URL), as listRepositories shows it."},"schedule":{"type":"object","properties":{"kind":{"type":"string","enum":["daily","weekly","monthly"],"description":"daily, weekly, or monthly (the Nth weekday of each month)."},"weekday":{"description":"0 = Sunday … 6 = Saturday. Required for weekly and monthly; ignored for daily.","type":"integer","minimum":0,"maximum":6},"ordinal":{"description":"Monthly only: 1 = first … 4 = fourth, 5 = last, as in \"the last Friday\".","type":"integer","minimum":1,"maximum":5},"minute":{"type":"integer","minimum":0,"maximum":1439,"description":"Minutes after local midnight in tz: 540 is 09:00, 1380 is 23:00."},"tz":{"type":"string","minLength":1,"maxLength":64,"description":"IANA time zone the time is in, e.g. Europe/London or America/New_York."}},"required":["kind","minute","tz"],"additionalProperties":false,"description":"The schedule quoteAlways was called with."},"disclosureVersion":{"type":"string","minLength":1,"maxLength":40,"description":"The disclosureVersion the quote returned, echoed only after the person has read that wording and agreed."},"amountCents":{"description":"The amountCents the quote returned. Required with payment; a different amount is refused with 409 and the current quote, and nothing is charged.","type":"integer","minimum":0,"maximum":10000000},"payment":{"description":"How to pay. Leave out to be answered 402 with a Stripe Checkout link; the free first scan needs none.","anyOf":[{"type":"object","properties":{"savedCard":{"type":"boolean","const":true,"description":"Charge the card on file the quote named by brand and last four."}},"required":["savedCard"],"additionalProperties":false},{"type":"object","properties":{"sharedPaymentToken":{"type":"string","maxLength":255,"pattern":"^spt_[A-Za-z0-9]+$","description":"A Stripe Shared Payment Token the person approved for at least amountCents, in USD."}},"required":["sharedPaymentToken"],"additionalProperties":false}]}},"required":["repository","schedule","disclosureVersion"],"additionalProperties":false}}}},"responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Payment is needed. `link` is a Stripe Checkout page for the person to open (`payment_required`), or the Shared Payment Token could not pay (`spt_*`). Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"413":{"description":"The body is over 64 KB. Refused before it is parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/plans/{id}/schedule":{"patch":{"operationId":"updateSchedule","summary":"Change a Sekura Always schedule","description":"Changes when a repository on Sekura Always is scanned, in one call. It charges nothing and changes nothing else; the answer states the new schedule and when the next scan runs. Name the repository by planId or repository (getPlans shows both). Only a repository on Sekura Always has a schedule.","tags":["write"],"x-scope":"write","x-kind":"change","x-counts-against":"the 10 confirmed writes an hour per account","parameters":[{"name":"id","in":"path","required":true,"description":"The plan id, as GET /plans returns it.","schema":{"type":"string","minLength":1,"maxLength":100}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"schedule":{"type":"object","properties":{"kind":{"type":"string","enum":["daily","weekly","monthly"],"description":"daily, weekly, or monthly (the Nth weekday of each month)."},"weekday":{"description":"0 = Sunday … 6 = Saturday. Required for weekly and monthly; ignored for daily.","type":"integer","minimum":0,"maximum":6},"ordinal":{"description":"Monthly only: 1 = first … 4 = fourth, 5 = last, as in \"the last Friday\".","type":"integer","minimum":1,"maximum":5},"minute":{"type":"integer","minimum":0,"maximum":1439,"description":"Minutes after local midnight in tz: 540 is 09:00, 1380 is 23:00."},"tz":{"type":"string","minLength":1,"maxLength":64,"description":"IANA time zone the time is in, e.g. Europe/London or America/New_York."}},"required":["kind","minute","tz"],"additionalProperties":false,"description":"The new schedule, in the person's own time zone."},"planId":{"description":"Optional; must match the id in the path.","type":"string","maxLength":100}},"required":["schedule"],"additionalProperties":false}}}},"responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"413":{"description":"The body is over 64 KB. Refused before it is parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/plans/{id}/cancel-preview":{"get":{"operationId":"previewCancelPlan","summary":"Preview cancelling Sekura Always","description":"Cancels Sekura Always on one repository, in two calls. Without confirm it cancels nothing: it names the repository, that scheduled scans stop at once, the date the paid period ends, how the monthly charge changes, and any scan in progress that the cancel does not stop. Show that to the person, and only if they agree, call again with confirm: true. Name the repository by planId or repository (getPlans shows both). Sekura Once is a single scan with no plan to cancel; cancelScan stops a scan inside its five-minute window.","tags":["write"],"x-scope":"write","x-kind":"quote","parameters":[{"name":"id","in":"path","required":true,"description":"The plan id, as GET /plans returns it.","schema":{"type":"string","minLength":1,"maxLength":100}}],"responses":{"200":{"description":"What the confirming call would do, changing nothing: show `text` to the person as it stands, and call `confirmOperation` with `confirmArguments` (and the payment they chose) only once they agree.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/plans/{id}/cancel":{"post":{"operationId":"cancelPlan","summary":"Cancel a Sekura plan","description":"Cancels Sekura Always on one repository, in two calls. Without confirm it cancels nothing: it names the repository, that scheduled scans stop at once, the date the paid period ends, how the monthly charge changes, and any scan in progress that the cancel does not stop. Show that to the person, and only if they agree, call again with confirm: true. Name the repository by planId or repository (getPlans shows both). Sekura Once is a single scan with no plan to cancel; cancelScan stops a scan inside its five-minute window.","tags":["write"],"x-scope":"write","x-kind":"cancel","x-counts-against":"the 10 confirmed writes an hour per account","parameters":[{"name":"id","in":"path","required":true,"description":"The plan id, as GET /plans returns it.","schema":{"type":"string","minLength":1,"maxLength":100}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"planId":{"description":"Optional; must match the id in the path.","type":"string","maxLength":100},"confirm":{"description":"Optional; the POST is the confirmation.","type":"boolean","const":true}},"additionalProperties":false}}}},"responses":{"200":{"description":"The answer. `text` is prose to relay to the person as it stands; the other fields are the same answer for the agent to act on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answer"}}}},"400":{"description":"The request could not be used: a field the operation does not take, a missing one, or a value out of range. `code` names which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No API key, or one that is unknown, revoked or expired. Reconnect through POST /connect.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`, naming it), the account is suspended, or the account or its GitHub side is not ready; `link` is where the person fixes it.","headers":{"WWW-Authenticate":{"description":"Bearer realm=\"sekura\", with RFC 6750's error and scope where they apply.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such scan or plan on this account. A foreign id gets the same answer as an unknown one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The state moved under the quote: a stale disclosure version or a changed amount (the current quote is attached as `quote`), a scan already in flight, or a plan not in a state this can act on. Nothing happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conflict"}}}},"413":{"description":"The body is over 64 KB. Refused before it is parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"A limit was reached. `Retry-After` says how many seconds to wait.","headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Sekura could not answer. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"GitHub or Stripe did not answer. Nothing was charged; try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApi","summary":"This OpenAPI document","description":"Readable without a key, because an agent reads it before anyone has connected.","tags":["connect"],"x-scope":"none","security":[],"responses":{"200":{"description":"This document.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"bearerKey":{"type":"http","scheme":"bearer","bearerFormat":"sek_live_…","description":"A per-person API key from the connect flow (POST /connect, then POST /connect/token), or one minted in Sekura under Settings, Agents and API keys. Scopes are `read` and `write`; each operation names the one it needs in `x-scope`."}},"schemas":{"Error":{"type":"object","required":["code","error","link"],"properties":{"ok":{"const":false},"code":{"type":"string","enum":["account_not_ready","account_suspended","already_always","already_owned","already_terminal","always_cancelled","amount_changed","charge_not_confirmed","consent_required","disclosure_version_required","disclosure_version_stale","empty_repository","findings_unavailable","free_scan_used","github_sign_in_expired","github_unavailable","insufficient_scope","invalid_input","invalid_json","invalid_repository","invalid_schedule","invalid_token","missing_key","no_repositories","not_active","not_always","not_finished","not_found","not_granted","payload_too_large","payment_required","pending","rate_limited","refused","report_expired","report_missing","scan_in_flight","signature_required","spt_amount_too_low","spt_currency","spt_deactivated","spt_declined","spt_expired","spt_not_for_subscriptions","spt_not_found","stripe_unavailable","unavailable","unknown_operation","unsupported_host","window_closed"],"description":"What went wrong, for the agent to branch on."},"error":{"type":"string","description":"One sentence for the person."},"link":{"type":"string","format":"uri","description":"Where the person takes the next step: a Stripe Checkout page on 402 payment_required, otherwise a Sekura page."},"retryAfterSeconds":{"type":"integer","description":"On 429, the same as Retry-After."}},"additionalProperties":true},"Conflict":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"quote":{"$ref":"#/components/schemas/Quote","description":"On disclosure_version_stale, amount_changed, charge_not_confirmed and free_scan_used: the current quote, to show the person before confirming again."}}}]},"Answer":{"type":"object","required":["ok","text"],"properties":{"ok":{"const":true},"text":{"type":"string","description":"Prose for the person, to relay as it stands: prices, dates and links in it are exact."}},"additionalProperties":true},"Quote":{"allOf":[{"$ref":"#/components/schemas/Answer"},{"type":"object","properties":{"needsConfirmation":{"const":true},"amountCents":{"type":"integer","description":"Echo this on the confirming call."},"currency":{"const":"usd"},"payment":{"type":"object","description":"How it would be paid by default: free, the card on file, the Always subscription, or Checkout."},"paymentOptions":{"type":"array","description":"Every way the confirm can pay, each with the exact `payment` value that selects it; null means leave payment out (Checkout, 402). Always never lists a Shared Payment Token.","items":{"type":"object","required":["method","payment"],"properties":{"method":{"enum":["shared_payment_token","saved_card","subscription","checkout"]},"brand":{"type":["string","null"]},"last4":{"type":["string","null"]},"payment":{}}}},"disclosure":{"type":"object","properties":{"version":{"type":"string"},"text":{"type":"string"}},"description":"The authorization wording the person agrees to; echo `version` as disclosureVersion."},"confirmOperation":{"type":"string","description":"The operationId to call once the person agrees."},"confirmArguments":{"type":"object","description":"Its arguments, without the payment."}}}]},"ConnectStart":{"type":"object","required":["code","userCode","verificationUrl","expiresIn","interval"],"properties":{"code":{"type":"string","description":"Secret. Poll POST /connect/token with it; never show it to the person. It is not in the link."},"userCode":{"type":"string","description":"Eight letters for the person to compare with the page, e.g. BCDF-GHJK."},"verificationUrl":{"type":"string","format":"uri","description":"The approve page, for the person to open."},"expiresIn":{"type":"integer","description":"Seconds the request lives."},"interval":{"type":"integer","description":"Seconds between polls."}}},"ConnectKey":{"type":"object","required":["apiKey","scopes"],"properties":{"apiKey":{"type":"string","description":"sek_live_…, answered once."},"keyId":{"type":["string","null"]},"scopes":{"type":"array","items":{"enum":["read","write"]}},"expiresAt":{"type":["string","null"]}}},"ConnectError":{"type":"object","required":["code","error"],"properties":{"code":{"enum":["invalid_request","authorization_pending","slow_down","access_denied","expired_token","payload_too_large","rate_limited","temporarily_unavailable"]},"error":{"type":"string"},"link":{"type":"string","format":"uri"}}}}}}