Reference
CLI reference
descant is one binary generated from the same contract as the API: every command word, flag, help sentence and exit code below is read from it, so the binary cannot drift from the API it drives. Install it with the one-line installer.
Setup
descant login is the documented way to hold a key. It reads the key at a prompt that does not echo and puts it in the machine's own credential store: the macOS keychain, the system secret service on Linux, and otherwise a plain file. The command names the store it used, and says so plainly when this machine has no keychain tool and it used the file instead.
It does not downgrade in the other direction. When a store is present but refuses the write — a locked keychain, a secret service that will not unlock — descant login fails and says why. It does not quietly put the key in a file you did not choose, so a machine that looks like it has a keychain never turns out to be keeping your key on disk instead.
Where that file lands depends on the platform, so check the path the command printed rather than assuming one — this is the path to audit or delete by hand. The platform is decided first, and only then the variable:
- On Windows,
%APPDATA%\descant\credential— and~\AppData\Roaming\descant\credentialwhenAPPDATAis unset, as it can be for a service account or a stripped image.XDG_CONFIG_HOMEis not read on Windows, set or not. - Everywhere else,
$XDG_CONFIG_HOME/descant/credentialwhen that variable is set, and~/.config/descant/credentialwhen it is not. There the file is written0600, owner-only.
On Windows that file is not permission-protected, and it is the ordinary path rather than a fallback. No Windows store can be read back once written, so descant login uses the file there by default. The binary asks for mode 0600 when it writes it, but on NTFS that sets the read-only attribute rather than an owner-only ACL — so the mode is not the protection it is on the other two platforms, and any account that can read your profile directory can read the key.
It is still the better of the two options there. Reaching for DESCANT_API_KEY instead would be a step down, not up: an exported variable is inherited by every process the shell starts, lands in shell history, and is visible in the process table — none of which is true of the file. So keep descant login on a Windows desktop, treat the profile directory as the thing actually protecting the key, and run descant logout when the machine no longer needs it. The variable's place is the next section: a CI runner or a container, which is ephemeral and holds it as a job secret.
descant logout removes it from whichever store holds it and names any it could not clear — including one it could not read, which it will not quietly treat as empty. A store it could not read but could still clear is reported as removed, because nothing is left in it. A store it could neither read nor clear is named, and logout tells you that you are not signed out rather than reporting success over a credential that would go live again the moment that store opens.
descant login export DESCANT_API_BASE_URL=https://app.descant.run descant run list
The key is never echoed, never written to shell history and never placed in a command argument — the store is fed it on standard input. descant whoami reports which credential is in use and which store it came from, identified by a short fingerprint rather than by any part of its value.
DESCANT_API_BASE_URL is the origin every request is sent to — set it to https://app.descant.run. It is not a secret, so it stays an ordinary environment variable.
--base-url overrides the variable for one invocation: The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Keys in CI and containers
A CI runner or a container has no keychain, so DESCANT_API_KEY keeps working and is the path to use there. It carries a tenant API key (dsc_…), created in the dashboard and shown once; the tenant is taken from the key, never from the request. Hand it to the job as a secret — not as a line in a script you commit.
export DESCANT_API_KEY=dsc_… export DESCANT_API_BASE_URL=https://app.descant.run descant run list
A stored credential wins. When both exist, the one descant login stored is used and the variable is ignored. Nothing switches silently: descant whoami names the one in use and says the variable is not being used, and --dry-run prints where the bearer came from on any of the generated commands below. descant login, descant logout and descant whoami take no flags at all — they refuse anything but --help and change nothing — so ask them the question by running descant whoami itself.
A store that exists but cannot be read — a locked keychain, a credential file whose permissions were changed — refuses the command with exit 3 rather than quietly using something else. That holds both ways round, which is the part worth knowing on a machine that has had more than one store written to it: it will not fall back to the variable, and it will not fall back to a key sitting in a weaker store underneath the one that faulted. Either would present a different key, possibly another tenant's, and a mutation is the wrong place to discover it. The refusal names the store that actually failed, so you fix the right one.
Commands
What descant --help prints for the commands a tenant key can run:
descant — the Descant CLI. Every command is generated from the API contract.
Usage:
descant <command> [arguments] [flags]
Command words and positional arguments come BEFORE flags.
Commands:
descant run list List runs for the authenticated tenant.
descant run show Read one run's timeline and findings, as its page shows them.
descant run cancel Stop a run that should not be running.
descant repo list List the authenticated tenant's repositories.
descant repo update Pause a repository, or change what it picks and in what order.
descant repo settings-audit Show who paused or resumed a repository, and when.
descant repo poller-status Show why a repository is or is not being polled.
descant repo config Show what a repository is configured to pick, and in what order.
descant repo issues List a repository's open issues, as the enqueue picker sees them.
descant repo labels List a repository's label names for the eligibility-label setting.
descant repo diagnostics Explain why a repository's issues would or would not be picked.
descant audit list List the authenticated tenant's API calls, newest first.
descant key list List the authenticated tenant's API keys.
descant key create Mint a key holding a subset of this key's scopes.
descant key revoke Revoke one of the authenticated tenant's API keys.
descant key rotate Replace a key with a successor; the old one lives on for a grace window.
descant invite list List this account's invites, with the budget left.
descant invite create Mint one invite and reveal its code once.
descant invite revoke Revoke one invite and reclaim its budget slot.
descant repo grooming candidates Read the relationships Descant would propose for a repository's backlog.
descant repo grooming document Read the relationship document a repository's issues encode.
descant repo grooming dispatch Land one relationship edit in a repository's issue bodies.
descant repo order-preview Preview the whole ordering walk under an unsaved draft.
descant repo ordering-preview Preview what one ordering query matches.
Credentials:
descant login Store an API key so it is not in your shell.
descant logout Remove the stored API key.
descant whoami Show which credential is in use, and from where.
descant version Show the contract version this binary was generated from.
Agents:
descant mcp Serve this account's API to an agent as MCP tools, over stdio.
descant plugin install Install the Descant plugin — Skills plus an MCP server entry — into an agent client.
This binary:
descant update Replace this binary with the newest published release, checksum-verified. `upgrade` is the same command.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
descant <command> --help shows that command's arguments, response fields and errors.
descant version --spec lists every command this binary reaches.descant run list
List runs for the authenticated tenant. Drives GET /api/v1/runs.
descant run list [flags]
List runs for the authenticated tenant.
List this key's tenant's runs, newest first.
Read-only. The key's tenant is taken from the matched key row and never from the request, so a
key cannot read another tenant's runs. Rate limited per source before authentication, so an
unauthenticated caller cannot probe cheaply. Ownership-scoped (#3307): runs of a repository
the tenant has since removed are still returned, because `customer.repos.list` still shows
that repository and eliding its runs would lose audit history. Paged by an opaque page token —
a run history is unbounded, and this list ALREADY stopped at fifty rows, silently, with no way
to ask for the fifty-first; follow `nextPageToken` until it is `null`. The rows arrive as
`items`, not `runs`: the array was never going to stay complete, and a renamed field is a
break a caller can see rather than a truncation it cannot. `billing` is a tenant fact read
once per request and rides every page unchanged. `repositoryId`, `state` and the
`createdAfter`/`createdBefore` window each narrow the page and are each optional; absent means
unfiltered. `invalid_query` covers a malformed page token — one this server did not mint, or
one minted by another paged read — which is refused rather than read as `start over`.
Flags:
--limit <number>
Page size. Defaults to 50, clamped to 200; the applied value is echoed.
--page-token <string>
Opaque, server-minted. Pass the previous response's `nextPageToken` verbatim; never construct one.
--repository-id <string>
Only this repository's runs. The id `customer.repos.list` returns, which is the only programmatic source of one. A repository belonging to another tenant matches nothing rather than erroring — the ownership join answers before the filter does.
--state <idle|picking|planning|implementing|opening_pr|under_review|resolving|merging|capturing_learnings|reviewing_learnings|resolving_learnings|merging_learnings|filing_residuals|grooming_issues|refreshing_wiki|verifying|positioning_pr|positioning_resolve|self_reviewing|fixing_ci|decomposing|done|failed|cancelled> (repeatable)
Repeatable. The run's CURRENT state, not its outcome — `failed` and `cancelled` are terminal, `under_review` is not. Absent or empty means every state.
--created-after <string>
Runs created at or after this instant, INCLUSIVE. Filters the column the list orders by.
--created-before <string>
Runs created at or before this instant, INCLUSIVE. Filters the column the list orders by.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
items[].issue
The issue's external identifier, as the provider names it.
items[].issueTitle
Snapshotted on the run (#3262). `null` on legacy runs and on seed paths that could not fetch it.
items[].state
The pipeline state. Open by design: states are added as the pipeline grows.
items[].dispatchBlockedReason
`slot`: the run's account is at its concurrency cap (#1995). `vendor_budget`: the fleet vendor budget, or this tenant's fair share of it, is exhausted (#7553), and only ever present when the operator has armed that budget. `null` on a run that is not parked.
items[].dispatchWaiting
The dashboard's own reading of why this run rests. `null` when it is not waiting on any of these.
nextPageToken
`null` when this is the last page.
limit
The page size actually applied, after clamping.
billing.pauseReason
The persisted pause reason, narrowed to the known set — an unrecognised future value reads as `null` rather than leaking.
billing.outOfCredits
Derived: the pause is a balance pause rather than an operator or cap pause. `false` with no billing signal.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_query (400) → exit 1
A query parameter failed validation.
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/runs
operation customer.runs.listdescant run show
Read one run's timeline and findings, as its page shows them. Drives GET /api/v1/runs/{runId}.
descant run show <runId>
Read one run's timeline and findings, as its page shows them.
Read one run's timeline and findings, exactly as its own page shows them.
The run page's live snapshot under the key's own tenant: the five canonical phases as
`deriveTimeline` derives them, their statuses and mapped copy, the server clocks a stall is
measured against, the review findings and the follow-up counts. IT PUBLISHES WHAT THE PAGE
RENDERS, through the same projection — so the two cannot tell a customer different stories
about one run, and a test pins that they call the same function. The MAPPED failure copy
crosses and the raw failure code does not; the derived findings view crosses and the raw
review checkpoint does not. Measure every age against `serverNowMs` rather than the caller's
clock. A missing run and another tenant's run are one indistinguishable `not_found`, so this
is not an oracle for what somebody else owns.
Arguments:
<runId>
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
state
The pipeline state. Open by design, as the list read's is.
steps
The five canonical phases as `deriveTimeline` derives them — the SAME call the page makes.
steps[].errorMessage
The MAPPED customer-facing copy, never the raw failure code — the projection strips that before it reaches any surface.
steps[].reviewIterations
How many review passes actually RAN. Present on the review step whenever that count is at least one, so `1` is an ordinary reviewed run rather than an anomaly; absent when no pass ran, and absent on every other step. The count is not a raw tally of review states: the pre-review positioning step maps to the same phase and is not a pass, and a drift-reposition abort re-enters the review state without running the reviewer, so it is subtracted rather than counted.
stepDetails
Sub-steps keyed by step `key`, for the steps that have them.
serverNowMs
The server clock this response was built at. Anchor every age against THIS, not the caller's clock.
stallAnchorMs
When the current customer-VISIBLE phase was entered — what the stall clock measures from. A multi-state visible phase deliberately does not reset it.
stateEnteredAtMs
The INTERNAL state's entry, verbatim — not bumped on a resolve self-loop.
currentStepNotStarted
The run is at a phase that has produced no heartbeat, so it renders `stalled` rather than `current`. Derived ONCE server-side so every pane reads the same answer.
awaitingManualMerge
A done run whose deliberately-unmerged PR still awaits a human.
skippedStepNote
The note a `skipped` Merge step renders.
mergeStepNote
Non-null only where the merge took the host-backstop path because the Checks permission was never granted.
heldStepNote
Non-null only where the timeline actually carries a `held` Merge step.
seededFromRunId
The run this one was seeded from, where it was.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
not_found (404) → exit 1
No such resource for this caller.
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/runs/{runId}
operation customer.runs.getdescant run cancel
Stop a run that should not be running. Drives POST /api/v1/runs/{runId}/cancel.
descant run cancel <runId>
Stop a run that should not be running.
Stop a run that should not be running.
The write that stops spend. Answers WHICH ending it reached rather than a boolean:
`cancel_requested` is recorded-but-not-yet-stopped and calls for a re-read, while
`already_finished` is final and calling again will never change it. Refusals keep their own
discriminant, and an `outcome_unknown` answer means the cancel MAY have landed: verify the run
before calling again. A deployment that is not wired to the runner answers `internal_error`,
which is an operator problem and not something a caller can fix by retrying.
Arguments:
<runId>
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
outcome
How the cancel ended. `cancelled` is stopped. `cancel_requested` is RECORDED BUT NOT YET STOPPED — re-read the run rather than calling again. `already_finished` means there was nothing to stop and never will be. `privately_dispatched` means the work left this estate and cannot be recalled from here.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
rate_limited (429) → exit 4
Too many requests for this credential or source.
not_found (404) → exit 1
No such resource for this caller.
outcome_unknown (504) → exit 6
The action may have taken effect; verify before retrying.
upstream_auth (502) → exit 7
An upstream service rejected this deployment's credential.
upstream_timeout (503) → exit 7
An upstream service did not answer in time.
upstream_unavailable (503) → exit 7
A service this operation depends on did not answer.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
POST /api/v1/runs/{runId}/cancel
operation customer.runs.canceldescant repo list
List the authenticated tenant's repositories. Drives GET /api/v1/repos.
descant repo list
List the authenticated tenant's repositories.
List this key's tenant's repositories.
Read-only, and the entry point for every repo-scoped operation: the `id` each of those takes
is obtainable nowhere else on the programmatic surface. The key's tenant is taken from the
matched key row and never from the request, so a key cannot list another tenant's
repositories. Rate limited per source BEFORE authentication, so an unauthenticated caller
cannot probe cheaply. Unpaginated — the whole list comes back.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
repos[].id
The tenant-repo id. What every repo-scoped operation takes.
repos[].owner
The account or organisation that owns the repository on its provider.
repos[].repo
The repository name, without the owner.
repos[].onboardedStatus
Where this repository is in onboarding, as the column stores it. `pending` until both installation ids are populated and the validation smoke test succeeds, then `active`; `disconnected` is the terminal state after self-serve offboarding. NOT a closed set — the column carries no constraint, so treat an unrecognised value as unknown rather than as an error.
repos[].paused
Whether the customer has paused this repository. A paused repository is not dispatched.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/repos
operation customer.repos.listdescant repo update
Pause a repository, or change what it picks and in what order. Drives PATCH /api/v1/repos/{id}.
descant repo update <id> [flags]
Pause a repository, or change what it picks and in what order.
Change whether a repository is paused, and what it picks.
A PARTIAL update: send only the fields you mean to change. An empty object is an accepted
no-op answering `changed: false`, not a refusal — the published schema accepts it, so refusing
it would make this contract disagree with the server about what is a valid body. An ABSENT
field is left alone; a `null` `maxConcurrentRunsThisRepo` CLEARS the sub-cap, which is a real
write rather than an absence. A sub-cap above the account cap is inert, not refused — the
effective cap is the lower of the two. `changed: false` is a success: re-sending the current
state is an accepted no-op. An eligibility label must already exist on the repository — this
operation does not create it, and a missing label makes pickup find nothing without an error.
A label spelled like a label-mapping value is refused. Read the stored pickup settings back
with the repo config read; this response carries the repository's own fields only.
Arguments:
<id>
Flags:
--paused / --no-paused
Whether this repository is paused. A paused repository is not dispatched.
--order-strategy <oldest-first|newest-first>
The direction the picker walks a tier. Built from the same `@descant/types` constant the runtime narrows against, so the contract and the picker cannot disagree about the vocabulary.
--max-concurrent-runs-this-repo <number|null>
This repository's concurrency sub-cap. `null` CLEARS it, which is a real write and not an absence — the account cap alone then governs. The effective cap is `min(account, repo)`, so a value ABOVE the account cap is INERT rather than rejected: it will not raise anything. The upper bound is the column's own storage range, not a product ceiling.
--eligibility-label <string>
The label an issue must carry to be eligible in `labeled` mode. Stored trimmed. THE LABEL MUST ALREADY EXIST ON THE REPOSITORY: this write does not create it, and a label the repository lacks makes pickup find nothing, with no error. Refused: empty, a `P<digits>` priority label, a comma, `blocked` or `waiting`, or a spelling a label-mapping value already uses.
--eligibility-mode <labeled|all-issues>
Which issues are candidates at all: `labeled` requires the eligibility label, `all-issues` considers every open issue. Switching to `all-issues` keeps the stored label, so switching back restores it.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
changed
Whether this request actually moved anything. FALSE is a success, not a refusal: re-sending the state a repository is already in is an accepted no-op, and the audit trail records the intent either way.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_body (400) → exit 1
The request body failed validation.
not_found (404) → exit 1
No such resource for this caller.
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
PATCH /api/v1/repos/{id}
operation customer.repos.updatedescant repo settings-audit
Show who paused or resumed a repository, and when. Drives GET /api/v1/repos/{id}/settings-audit.
descant repo settings-audit <id>
Show who paused or resumed a repository, and when.
Read one repository's recorded settings intents, newest first.
Who paused or resumed this repository, when, and whether the flip changed anything — readable
by a key-holding program without a browser session. System pauses appear alongside operator
ones, told apart by `actor`. A FIXED WINDOW of the 50 newest rows with no paging, so an empty
`entries` is NOT proof the repository was never paused: it means nothing was recorded within
that window. This is not a `paused` state oracle; the current pause reason is on the
poller-status read. A malformed `id` answers `not_found`, exactly as an unknown or unreadable
one does — never a validation error, so a caller cannot use the status to learn which ids are
well-formed.
Arguments:
<id>
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
entries[].actor
Who recorded the intent, as an OPAQUE string. The write side owns this vocabulary and a reader must not branch on its prefix shape. System pauses appear here alongside operator ones — the scheduler's evidence-driven auto-pause appends its own row actored `service:scheduler-autopause` — and `actor` is what tells them apart.
entries[].action
The intent recorded, as the write side names it.
entries[].changed
Whether this intent was a REAL change rather than a no-op re-assertion of the state already held.
entries[].detail
The row's detail, crossing the wire VERBATIM. ALWAYS PRESENT: a pause or resume carries its field transition; a `detach` records `null`, and that explicit null IS the persisted state — never an absence, never collapsed or dropped. The values are unbounded on purpose: the contract does not own this shape.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
not_found (404) → exit 1
No such resource for this caller.
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/repos/{id}/settings-audit
operation customer.repos.settings-auditdescant repo poller-status
Show why a repository is or is not being polled. Drives GET /api/v1/repos/{id}/poller-status.
descant repo poller-status <id>
Show why a repository is or is not being polled.
Read why this repository is or is not being polled.
The read that answers 'why is nothing happening'. `status` is a discriminated union over
SIXTEEN causes, because 'not polling' has four different remedies and a boolean loses the
difference: a repository HELD on its candidates, a worker that never consulted the ticket
source, a billing pause, and a page already filed by earlier runs all look the same through
`paused: true`. Branch on `status.kind`. `repo.paused` and `billing.paused` are the raw flags
beneath that reading, and `lastPoll` is the stored tick beneath both. A malformed `id` answers
`not_found`, exactly as an unknown or unreadable one does — never a validation error, so a
caller cannot use the status to learn which ids are well-formed.
Arguments:
<id>
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
lastPoll
The last tick's stored fields, beneath the classification. Use `status` unless you need what was persisted.
lastPoll.outcome
The RAW stored outcome, unclassified. `status` is the reading of it.
lastPoll.errorClass
The raw stored error class, unnarrowed — unlike `status.errorClass`.
billing.reason
Why billing paused this tenant, from the closed set the handler narrows to. `null` is 'paused, reason not established' — absent or unrecognised — never 'not paused'.
billing.degraded
Whether the billing read itself was degraded — so `paused` and `reason` may be stale rather than wrong.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
not_found (404) → exit 1
No such resource for this caller.
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/repos/{id}/poller-status
operation customer.repos.poller-statusdescant repo config
Show what a repository is configured to pick, and in what order. Drives GET /api/v1/repos/{id}/config.
descant repo config <id>
Show what a repository is configured to pick, and in what order.
Read what this repository is configured to pick, and in what order.
The rule behind the other two repo reads: which issues are candidates, which label makes one
eligible, whether assigned issues are excluded, and the order the picker walks. Values are
EFFECTIVE — the defaults are composed in when no row is stored, and no flag distinguishes the
two, because they behave identically. `effectiveOrderQueries` is derived and must never be
written back. A malformed `id` answers `not_found`, exactly as an unknown or unreadable one
does.
Arguments:
<id>
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
config.eligibilityLabel
The label an issue must carry to be eligible in `labeled` mode. Not consulted in `all-issues` mode.
config.eligibilityMode
Which issues are candidates at all. `labeled` requires the eligibility label; `all-issues` considers every open issue, and is why the diagnostics read reports no label-bucketed counts in that mode.
config.excludeAssigned
Whether assigned issues are excluded. NOT durable operator policy: this is an AUTO-MANAGED recovery knob the reconciler can flip and restore without any operator write, so `false` must not be read as 'someone chose this'.
config.orderStrategy
The direction the picker walks a tier: oldest issue first, or newest first.
config.orderQueries
The persisted walk, verbatim, or `null` meaning the mode's default walk. `null` is the common case and is not an error.
config.effectiveOrderQueries
DERIVED, NOT STORED — what the picker actually walks: `orderQueries` when set, otherwise the mode-aware default (the P0-P3 tiers plus the unlabeled final tier). Never write this back as configuration; `orderQueries` is the field that is set.
config.autoMergeEnabled
Whether a run's pull request is merged automatically once it is green.
config.commitPlanDocument
Whether runs also commit the plan document into the repository.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
not_found (404) → exit 1
No such resource for this caller.
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/repos/{id}/config
operation customer.repos.configdescant repo issues
List a repository's open issues, as the enqueue picker sees them. Drives GET /api/v1/repos/{id}/issues.
descant repo issues <id>
List a repository's open issues, as the enqueue picker sees them.
List a repository's open issues, as the enqueue picker sees them.
The issues a run can be started for, read through this deployment's author App on the
repository the path names — the same listing the console's picker renders. `configured: false`
is a SUCCESS and means this deployment cannot enumerate (no author-App credentials, or this
repository's author installation never landed); enqueue still works with an issue number you
supply, so branch on the field rather than on the status. A malformed id, an unknown id and
another tenant's id are one indistinguishable `not_found`, answered with no upstream call.
Upstream failures are classified rather than proxied: `upstream_rate_limited` carries its
interval and is worth retrying, `upstream_auth` is this deployment's credential and not yours
to fix.
Arguments:
<id>
The `tenant_repos` row, as `customer.repos.list` returns it.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
not_found (404) → exit 1
No such resource for this caller.
upstream_rate_limited (503) → exit 4
An upstream service is rate limiting us; retry after the stated interval.
upstream_auth (502) → exit 7
An upstream service rejected this deployment's credential.
upstream_timeout (503) → exit 7
An upstream service did not answer in time.
upstream_unavailable (503) → exit 7
A service this operation depends on did not answer.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/repos/{id}/issues
operation customer.repos.issues.listdescant repo labels
List a repository's label names for the eligibility-label setting. Drives GET /api/v1/repos/{id}/labels.
descant repo labels <id>
List a repository's label names for the eligibility-label setting.
List a repository's label names, as the eligibility-label setting reads them.
The label NAMES this repository carries — what `customer.repos.update` stores as the
eligibility label, and what the poller matches an issue against, so this is the read that
makes that setting writable without guessing. `configured: false` is a SUCCESS on the same
terms as the issue picker: setting a label by name still works when enumeration is
unavailable. The `not_found` and upstream-classification rules are that operation's; see it.
Arguments:
<id>
The `tenant_repos` row, as `customer.repos.list` returns it.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
not_found (404) → exit 1
No such resource for this caller.
upstream_rate_limited (503) → exit 4
An upstream service is rate limiting us; retry after the stated interval.
upstream_auth (502) → exit 7
An upstream service rejected this deployment's credential.
upstream_timeout (503) → exit 7
An upstream service did not answer in time.
upstream_unavailable (503) → exit 7
A service this operation depends on did not answer.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/repos/{id}/labels
operation customer.repos.labels.listdescant repo diagnostics
Explain why a repository's issues would or would not be picked. Drives GET /api/v1/repos/{id}/poller-diagnostics.
descant repo diagnostics <id>
Explain why a repository's issues would or would not be picked.
Explain why this repository's issues would or would not be picked.
A LIVE read: one provider call per request, never cached. Answers what the picker would do
right now — how many issues carry the eligibility label, how many would be picked, and which
buckets hold the rest. TWO of the three success shapes report NO counts (`configured: false`,
and `mode: "all-issues"`), because neither can be measured in label buckets and a fabricated
zero would read as "nothing is eligible" rather than "not measured". Branch on `configured`,
then on `mode`. Counts are bounded by one page: check `truncated` before reading any of them
as a repository total, and by the DEFAULT tier semantics: when `customOrdering` is true the
picker walks the repository's own queries and the buckets describe the default walk rather
than what it would pick. A malformed `id` answers `not_found`, exactly as an unknown or
unreadable one does.
Arguments:
<id>
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
rate_limited (429) → exit 4
Too many requests for this credential or source.
not_found (404) → exit 1
No such resource for this caller.
upstream_rate_limited (503) → exit 4
An upstream service is rate limiting us; retry after the stated interval.
upstream_auth (502) → exit 7
An upstream service rejected this deployment's credential.
upstream_timeout (503) → exit 7
An upstream service did not answer in time.
upstream_unavailable (503) → exit 7
A service this operation depends on did not answer.
Wire:
GET /api/v1/repos/{id}/poller-diagnostics
operation customer.repos.poller-diagnosticsdescant audit list
List the authenticated tenant's API calls, newest first. Drives GET /api/v1/audit.
descant audit list [flags]
List the authenticated tenant's API calls, newest first.
List this tenant's authenticated API calls, newest first.
One row per authenticated call against this tenant, including the ones that did not succeed: a
403 for a key outside its scope, and a 500 for a call that failed after it was admitted. A 429
is never here — the only one this surface answers is the pre-authentication per-source bound,
which refuses before a credential is read and so has no tenant to attribute a row to.
`principal` says who: `api-key:<keyId>` when one of this tenant's credentials called,
`user:<userId>` when a signed-in person exercised the same capability from the console — a key
minted on the settings page is the same capability as a mint over this API, and both leave a
row. A call with no tenant to attribute is not recorded, so an empty page does not mean
nothing was tried. Paged by an opaque page token; `principal` narrows to one caller.
`invalid_query` covers a malformed page token — one this server did not mint, or one minted by
another paged read — which is refused rather than read as `start over`.
Flags:
--limit <number>
Page size. Defaults to 50, clamped to 200; the applied value is echoed.
--page-token <string>
Opaque, server-minted. Pass the previous response's `nextPageToken` verbatim; never construct one.
--principal <string>
Narrow the page to one credential's calls.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
items[].principal
Who made the call, as an OPAQUE string the write side owns; do not branch on its prefix shape. A live row reads `api-key:<keyId>`, or `user:<userId>` for a signed-in person exercising the same capability from the console; a row whose subject has been erased reads a redaction marker instead.
items[].operationId
The operation the route serves, or `null` for a route the contract has not declared yet.
items[].method
The HTTP method, upper case.
items[].path
The request path without its query string. A segment may be a resource id, never prose.
items[].scope
The scope the operation required, `<family>:<read|write>`.
items[].status
The HTTP status answered.
items[].outcome
Derived from `status`: below 400 ok, 4xx refused, 5xx failed.
items[].requestId
The `X-Request-Id` the response carried.
nextPageToken
`null` when this is the last page.
limit
The page size actually applied, after clamping.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_query (400) → exit 1
A query parameter failed validation.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/audit
operation customer.audit.listdescant key list
List the authenticated tenant's API keys. Drives GET /api/v1/keys.
descant key list [flags]
List the authenticated tenant's API keys.
List this tenant's API keys, newest first.
Every key the tenant has minted, active and revoked, newest first — a revoke is a soft one, so
a revoked key stays in this list with `revokedAt` set. Never the secret: only the display
prefix. Paged by an opaque page token, because a key holding `keys:write` mints without a
person and nothing bounds the row count; follow `nextPageToken` until it is `null`.
`invalid_query` covers a malformed page token — one this server did not mint, or one minted by
another paged read — which is refused rather than read as `start over`.
Flags:
--limit <number>
Page size. Defaults to 50, clamped to 200; the applied value is echoed.
--page-token <string>
Opaque, server-minted. Pass the previous response's `nextPageToken` verbatim; never construct one.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
items[].keyPrefix
`dsc_` plus the first eight characters of the secret: how a key is named after it is minted.
items[].scopes[]
`<family>:<read|write>`, from the closed set the contract publishes.
items[].expiresAt
`null` never expires.
items[].revokedAt
`null` while the key is active.
nextPageToken
`null` when this is the last page.
limit
The page size actually applied, after clamping.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_query (400) → exit 1
A query parameter failed validation.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/keys
operation customer.keys.listdescant key create
Mint a key holding a subset of this key's scopes. Drives POST /api/v1/keys.
descant key create [flags]
Mint a key holding a subset of this key's scopes.
Mint a key holding a subset of this key's scopes.
Mints a key for the same tenant, attributed to the same creating account as the minting key.
The requested scopes must be a subset of the minting key's (a key cannot widen itself);
`conflict` names the scope that is not held. A minting key with no creating account on record
(a legacy key whose creator was erased) is refused with `conflict` rather than minting a child
nothing is attributed to. The secret appears in this response and nowhere else.
Flags:
--label <string> (required)
--scopes <string> (required, repeatable)
The scopes the new key holds. Must be a subset of the minting key's own — a key cannot widen itself.
--expires-at <string>
When the key stops authenticating. Omit for a key that does not expire. Must be in the future.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
key.keyPrefix
`dsc_` plus the first eight characters of the secret: how a key is named after it is minted.
key.scopes[]
`<family>:<read|write>`, from the closed set the contract publishes.
key.expiresAt
`null` never expires.
key.revokedAt
`null` while the key is active.
key.rawKey
The secret, shown exactly once. It is stored hashed and cannot be recovered; a caller that loses it mints a new key.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_body (400) → exit 1
The request body failed validation.
conflict (409) → exit 1
The resource is not in a state that admits this.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
POST /api/v1/keys
operation customer.keys.createdescant key revoke
Revoke one of the authenticated tenant's API keys. Drives DELETE /api/v1/keys/{keyId}.
descant key revoke <keyId>
Revoke one of the authenticated tenant's API keys.
Revoke one of this tenant's keys.
Soft-revokes: the row stays so the list can show it, and the key stops authenticating at once.
A repeat reports `already-revoked`. A key id that belongs to another tenant, or to nothing, is
one indistinguishable `not_found`. A key may revoke ITSELF; the call that does so is its last.
Arguments:
<keyId>
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
key.outcome
`already-revoked` on a repeat: the key was not active, so nothing changed.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
not_found (404) → exit 1
No such resource for this caller.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
DELETE /api/v1/keys/{keyId}
operation customer.keys.revokedescant key rotate
Replace a key with a successor; the old one lives on for a grace window. Drives POST /api/v1/keys/{keyId}/rotate.
descant key rotate <keyId> [flags]
Replace a key with a successor; the old one lives on for a grace window.
Replace a key with a successor; the old one lives on for a grace window.
Mints a successor holding the target's label and scopes, attributed to the minting key's
creating account, and sets the target to stop authenticating at the end of the grace window
(`graceSeconds`: default one hour, `0` at once) or at its own expiry, whichever is earlier —
an expiry is never extended. A rotation is a mint, so the minting key must hold every scope
the target does; `conflict` names the scope that is not held. A target already revoked is
`conflict` too: it has nothing to rotate. So is a target holding a scope the server answering
does not recognise — rotating it would drop that scope from the successor permanently, so it
is refused rather than silently narrowed; this can happen briefly while a deployment is
rolling, and a retry clears it. A key may rotate itself. The successor's secret appears in
this response and nowhere else.
Arguments:
<keyId>
Flags:
--grace-seconds <number>
How long the retired key keeps authenticating once the successor exists, in seconds. `0` retires it at once; the default is one hour; at most one day.
--expires-at <string>
When the SUCCESSOR stops authenticating. Omit for a successor that does not expire; the target's own expiry is not inherited. Must be in the future.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
key.keyPrefix
`dsc_` plus the first eight characters of the secret: how a key is named after it is minted.
key.scopes[]
`<family>:<read|write>`, from the closed set the contract publishes.
key.expiresAt
`null` never expires.
key.revokedAt
`null` while the key is active.
key.rawKey
The secret, shown exactly once. It is stored hashed and cannot be recovered; a caller that loses it mints a new key.
retired.expiresAt
When the retired key stops authenticating: the earlier of its own expiry and the end of the grace window.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
invalid_body (400) → exit 1
The request body failed validation.
not_found (404) → exit 1
No such resource for this caller.
conflict (409) → exit 1
The resource is not in a state that admits this.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
POST /api/v1/keys/{keyId}/rotate
operation customer.keys.rotatedescant invite list
List this account's invites, with the budget left. Drives GET /api/v1/invites.
descant invite list
List this account's invites, with the budget left.
List the key's own account's invites, with the budget and referral status.
The invites belonging to the account that CREATED THIS KEY — not the tenant's current owner;
see the family note, because which key you use decides whose budget you read. Metadata only:
the invite code and its hash never appear here, only in the create response. A key with no
creating account (minted before attribution, or one whose creator was erased) resolves to no
account and answers `not_found`, the same answer as asking about somebody else's.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
invites
METADATA ONLY — never the code or its hash. A list is re-readable, and a secret that can be re-read is not reveal-once.
invites[].id
The invite's id — what `customer.invites.revoke` takes.
invites[].status
Open by design, as the run state is: statuses are added as the invite lifecycle grows.
invites[].redeemedAt
`null` while the invite is unredeemed.
referralStatus.creditsEarnedMicros
Micros as a decimal STRING, not a number: the value is exact and JSON numbers are not, which is the convention this API already uses for exact ids.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
not_found (404) → exit 1
No such resource for this caller.
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/invites
operation customer.invites.listdescant invite create
Mint one invite and reveal its code once. Drives POST /api/v1/invites.
descant invite create
Mint one invite and reveal its code once.
Mint one invite, and reveal its code once.
REVEAL-ONCE: the `code` and `path` exist in this response and nowhere else. Surface the link
and do not persist the code — no read can return it, and a lost code means minting another.
The body carries no fields, so an ABSENT or empty body is the natural call and is read as
`{}`; a body with an unknown field is refused rather than ignored. Spending the account's last
slot answers `invite_limit_reached` — a well-formed request that a rule refused, which is why
it is not the malformed-body code. It spends the CREATING account's budget, which is the
binding that keeps one co-owner's key from spending another's.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
invite.code
THE CODE, and this is the ONLY response that carries it. Surface it, hand it over, and do not persist it — nothing can return it again.
invite.path
The redemption path the code belongs to.
budget
The budget AFTER this mint, so a caller need not re-list to know what is left.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_body (400) → exit 1
The request body failed validation.
invite_limit_reached (422) → exit 1
The tenant's invite limit is reached.
not_found (404) → exit 1
No such resource for this caller.
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
POST /api/v1/invites
operation customer.invites.createdescant invite revoke
Revoke one invite and reclaim its budget slot. Drives DELETE /api/v1/invites/{id}.
descant invite revoke <id>
Revoke one invite and reclaim its budget slot.
Revoke one invite, reclaiming its budget slot.
Kills the link and returns the slot to the account's budget — the same primitive the
dashboard's per-row Revoke uses, so the two surfaces cannot drift. A missing invite, an
already-terminal one and ANOTHER ACCOUNT'S are one indistinguishable `not_found`: the
underlying predicate is on the acting account, so nothing here discloses that an invite exists
for somebody else. That also means a revoke is safe to repeat in the sense that matters — the
second call answers `not_found` rather than doing something new — but it is not idempotent in
the `Idempotency-Key` sense and does not claim to be.
Arguments:
<id>
The invite id, as the list read returns it.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
not_found (404) → exit 1
No such resource for this caller.
rate_limited (429) → exit 4
Too many requests for this credential or source.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
DELETE /api/v1/invites/{id}
operation customer.invites.revokedescant repo grooming candidates
Read the relationships Descant would propose for a repository's backlog. Drives GET /api/v1/repos/{id}/grooming/candidates.
descant repo grooming candidates <id>
Read the relationships Descant would propose for a repository's backlog.
Read the relationships Descant would propose for this repository's backlog.
PROPOSALS WITH THEIR EVIDENCE, not edges: nothing exists until
`customer.repos.grooming.dispatch` lands it, and each candidate names the phrase in the
tenant's own issue body that suggested it, because a relationship asserted with no reason
cannot be reviewed. Served from the local copy of the backlog rather than a live walk — a
proposal is a proposed WRITE against the copy the caller is looking at, so it must be derived
from that same copy. `asOf`, `truncated` and `capped` say how current and how complete that
copy was. WHEN THE COPY CANNOT SERVE, this answers `upstream_unavailable` and NEVER an empty
list: an empty list is what a working detector returns over a backlog with no relationships,
and the two must not be spelled the same.
Arguments:
<id>
The `tenant_repos` row, as `customer.repos.list` returns it.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
candidates
PROPOSALS, not edges. Nothing exists until `dispatch` lands it.
candidates[].id
The candidate's own id, derived from its content — what a caller cites when acting on it.
candidates[].from
An issue, as the store names it — OPAQUE by design: the store compares references and never parses, reformats or resolves one, and this deployment spells a home-repository reference differently from a cross-repository one. So the only shape promised here is non-empty. A well-formed reference the document does not hold is not a malformed request: it answers `rejected` naming the reference, because only the document can know.
candidates[].to
An issue, as the store names it — OPAQUE by design: the store compares references and never parses, reformats or resolves one, and this deployment spells a home-repository reference differently from a cross-repository one. So the only shape promised here is non-empty. A well-formed reference the document does not hold is not a malformed request: it answers `rejected` naming the reference, because only the document can know.
candidates[].evidence
WHY the detector proposed this. `token` names the detector; `text` is the phrase from the tenant's own issue body that triggered it — free text, and the only free text this family publishes. It is the tenant's own backlog returned to that tenant, and without it a proposal is an assertion nobody can review.
asOf
When this copy was last known to have no unprocessed backlog. A candidate is only as current as the copy it was derived from.
truncated
The copy cut the backlog off, so the proposal set is over a partial view.
capped
The detector stopped at its own ceiling, so there may be proposals it never reached.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
not_found (404) → exit 1
No such resource for this caller.
upstream_unavailable (503) → exit 7
A service this operation depends on did not answer.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/repos/{id}/grooming/candidates
operation customer.repos.grooming.candidatesdescant repo grooming document
Read the relationship document a repository's issues encode. Drives GET /api/v1/repos/{id}/grooming/document.
descant repo grooming document <id>
Read the relationship document a repository's issues encode.
Read the relationship document this repository's issues currently encode.
The issues and the edges together, as the workspace renders them — what IS, where the
candidates read is what COULD be. `cycleGraphIncomplete` is non-null when the cycle check
could not see the whole graph: do not read 'no cycles' from an incomplete one.
`encodingRefused` names issues whose relationships could not be written back into their
bodies, so their edges are readable here and NOT persisted upstream — a distinction that
decides whether another reader will see them. Mirror-served and typed-refusal on the same
terms as the candidates read; see it.
Arguments:
<id>
The `tenant_repos` row, as `customer.repos.list` returns it.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Response fields:
document
The whole document — issues and edges together. Never a patch or a slice; see the family header.
document.issues[].ref
An issue, as the store names it — OPAQUE by design: the store compares references and never parses, reformats or resolves one, and this deployment spells a home-repository reference differently from a cross-repository one. So the only shape promised here is non-empty. A well-formed reference the document does not hold is not a malformed request: it answers `rejected` naming the reference, because only the document can know.
document.issues[].priority
The RESOLVED tier. See `priorityDisagreement` when the issue's two carriers disagree.
document.issues[].updatedAt
THE CONFLICT TOKEN. Send it back in `dispatch`'s `expected` as `ref=updatedAt`; an issue that moved since you read it answers `conflict` rather than overwriting an edit you never saw.
document.issues[].order
`null` on a closed issue: it holds no position.
document.issues[].order.rank
`null` when the issue holds no rank.
document.issues[].order.holds[].label
The runner's one-word chip for a hold it imposes. `tracker` holds only, and not all of them — a cut-short readiness verdict and a unit over its member cap carry none.
document.issues[].priorityDisagreement
Non-null only when the issue's label and its frontmatter name different tiers. Recorded rather than derived — by the time `priority` exists the two have been collapsed into one number.
document.issues[].priorityDisagreement.usedLabel
The tier label AS WRITTEN ON THE ISSUE, never the configured spelling.
document.issues[].priorityDisagreement.ignoredPriority
The frontmatter tier that was set aside; the format resolves label-first.
document.edges[].id
Derived from the edge's content rather than assigned, so the same relationship has the same id wherever it is read.
document.edges[].from
An issue, as the store names it — OPAQUE by design: the store compares references and never parses, reformats or resolves one, and this deployment spells a home-repository reference differently from a cross-repository one. So the only shape promised here is non-empty. A well-formed reference the document does not hold is not a malformed request: it answers `rejected` naming the reference, because only the document can know.
document.edges[].to
An issue, as the store names it — OPAQUE by design: the store compares references and never parses, reformats or resolves one, and this deployment spells a home-repository reference differently from a cross-repository one. So the only shape promised here is non-empty. A well-formed reference the document does not hold is not a malformed request: it answers `rejected` naming the reference, because only the document can know.
asOf
When this copy was last known to have no unprocessed backlog.
truncated
The open enumeration was CUT at the row cap — surfaced rather than laundered into a shorter backlog.
cycleGraphIncomplete
Non-null when the cycle check could not see the whole graph, naming why — a caller must not read 'no cycles' from an incomplete one. A write that could close a cycle is refused while this is set.
encodingRefused
Issues whose relationships could not be encoded back into their bodies. Their edges are readable here and are NOT persisted upstream.
host
What the HOST is doing right now — the runs in flight and the cap over them. An object, not a name: an earlier cut declared this a string and the strict schema rejected every real answer.
host.running[].key
The document key of the issue being worked — `owner/repo#N`.
host.running[].phase
The runner's phase word, in the operator's vocabulary.
host.truncated
The open enumeration was cut, so the host's own counts are omitted rather than stated as totals.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
not_found (404) → exit 1
No such resource for this caller.
upstream_unavailable (503) → exit 7
A service this operation depends on did not answer.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
GET /api/v1/repos/{id}/grooming/document
operation customer.repos.grooming.documentdescant repo grooming dispatch
Land one relationship edit in a repository's issue bodies. Drives POST /api/v1/repos/{id}/grooming/dispatch.
descant repo grooming dispatch <id> [flags]
Land one relationship edit in a repository's issue bodies.
Land one relationship edit in the issue bodies, fenced on what the caller read.
ONE mutation per call — create, delete, retype or flip — written into the GitHub ISSUE BODY
through this repository's App installation. It does NOT seed a run and never reaches the
runner, so no capacity answer applies. FENCED ON `expected`: issue ref to the `updatedAt` you
hydrated with. An issue that moved since then answers `outcome: "conflict"` carrying the
authoritative document, so a caller re-derives rather than overwriting an edit it never saw.
WHEN AN ANSWER CARRIES A DOCUMENT IT IS THE WHOLE ONE, never a patch — `applied`, `unchanged`
and `conflict` each carry it, and `unchanged` carries it precisely because 'nothing to do' is
often said when your copy is stale and somebody else already created the edge. A `rejected`
carries NO document: it carries a reason, and the `landed` flag if the write reached the issue
before the failure. `rejected` means the write was refused upstream and nothing changed there
— WITH ONE EXCEPTION that inverts it: a `rejected` carrying `landed: true` means the write DID
land and only the local copy is behind, so drop the document and the `updatedAt` tokens you
hold and re-read before your next edit. WHICH FIELDS GO WITH WHICH `op`, because the body is
flat rather than a union: `create` takes `kind`, `from` and `to`; `delete` and `flip` take
`edgeId`; `retype` takes `edgeId` and `nextKind`. `mutationId` is yours to mint and is
required on every op. A field belonging to a different op is REFUSED rather than ignored — a
body carrying both describes two edits and applying either is a guess — and a missing one
answers `invalid_body` naming the field.
Arguments:
<id>
The `tenant_repos` row, as `customer.repos.list` returns it.
Flags:
--op <create|delete|retype|flip> (required)
WHICH edit. The fields that go with it are listed in this operation's description and checked by the route.
--mutation-id <string> (required)
YOURS TO MINT, required on every op, and echoed in this deployment's logs so one edit can be traced end to end. It is NOT replay protection: nothing here deduplicates on it, and resending the same id performs the edit again. `expected` is what makes a replay safe — a stale stamp answers `conflict` rather than writing twice.
--kind <blocked-by|decomposed-from|duplicate-of|serialize-with>
`create` only: the relationship to add. `together-with` is absent deliberately — the writer cannot splice it, so offering it would advertise a create that always fails.
--from <string>
`create` only.
--to <string>
`create` only.
--edge-id <string>
`delete`, `retype` and `flip`: the relationship being changed, as the document returns it.
--next-kind <blocked-by|decomposed-from|duplicate-of|serialize-with>
`retype` only: what the relationship becomes. Not `together-with`, for the reason `kind` gives — and a retype OUT of an existing `together-with` is refused by the writer too, which no enum here can say.
--expected <string> (required, repeatable)
THE CONFLICT FENCE, as `issueRef=updatedAt` entries — the stamps you hydrated with. An issue that moved upstream since then answers `conflict` with the authoritative document rather than overwriting an edit you never saw. Entries rather than a map because the flag generator has no spelling for a map; the route splits them on the first `=`.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
invalid_body (400) → exit 1
The request body failed validation.
not_found (404) → exit 1
No such resource for this caller.
upstream_auth (502) → exit 7
An upstream service rejected this deployment's credential.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
POST /api/v1/repos/{id}/grooming/dispatch
operation customer.repos.grooming.dispatchdescant repo order-preview
Preview the whole ordering walk under an unsaved draft. Drives POST /api/v1/repos/{id}/order-preview.
descant repo order-preview <id> [flags]
Preview the whole ordering walk under an unsaved draft.
Preview the whole ordering walk under an unsaved draft.
What this repository's backlog WOULD be worked in, under a draft you have not saved — the
point being to see the draft's effect before committing to it. THE BODY IS A PARTIAL OVERLAY
and omission is meaningful: leaving a field out keeps the SAVED value, and `orderQueries`
additionally distinguishes `null` (clear them) from absent (keep them). Sending `null` when
you meant 'no opinion' previews a backlog with no queries. `partial` non-null means the walk
did not finish and `rows` is a prefix rather than the answer. `configured: false` is a success
meaning this deployment cannot reach the provider — the settings stay writable. It WRITES
NOTHING: a preview computes and discards.
Arguments:
<id>
The `tenant_repos` row, as `customer.repos.list` returns it.
Flags:
--eligibility-mode <labeled|all-issues>
OMIT to keep the saved mode. `labeled` requires the eligibility label; `all-issues` considers every open issue. The same closed set `customer.repos.config` takes.
--eligibility-label <string>
OMIT to keep the saved label. Validated against the stored label exactly as the save path validates it.
--order-strategy <oldest-first|newest-first>
OMIT to keep the saved strategy. The direction the picker walks a tier — the same closed set `customer.repos.config` takes.
--exclude-assigned / --no-exclude-assigned
OMIT to keep the saved value. `false` MEANS false — no presence marker is needed here, because unlike an HTML checkbox a JSON key that is present is present.
--label-map <string|null> (repeatable)
The label map as `KEY=label` entries, split on the FIRST `=` so a label containing one survives. Keys: `P0`, `P1`, `P2`, `P3`, `type:bug`, `type:feature`, `type:chore`. OMIT to keep the saved map. Send `null` — or an empty array — to preview with NO mapping at all: `null` is the spelling that survives the generated CLI, where a repeatable flag given zero times is indistinguishable from omitting the field and so cannot express an empty list. An entry with an empty value (`P1=`) leaves THAT key unmapped, as the settings form reads a blank control. An unknown or repeated key is `invalid_body` naming the entry.
--order-queries <string|null> (repeatable)
THREE STATES, and the one field where absent and `null` differ. OMIT to keep the saved queries; send `null` to CLEAR them and preview with none; send an array to replace them. A caller that sends `null` meaning "nothing to say about queries" will preview a backlog with no queries at all.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
invalid_body (400) → exit 1
The request body failed validation.
not_found (404) → exit 1
No such resource for this caller.
upstream_auth (502) → exit 7
An upstream service rejected this deployment's credential.
upstream_timeout (503) → exit 7
An upstream service did not answer in time.
upstream_unavailable (503) → exit 7
A service this operation depends on did not answer.
upstream_rate_limited (503) → exit 4
An upstream service is rate limiting us; retry after the stated interval.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
conflict (409) → exit 1
The resource is not in a state that admits this.
Wire:
POST /api/v1/repos/{id}/order-preview
operation customer.repos.order-previewdescant repo ordering-preview
Preview what one ordering query matches. Drives POST /api/v1/repos/{id}/ordering-preview.
descant repo ordering-preview <id> [flags]
Preview what one ordering query matches.
Preview what one ordering query matches.
The per-query half of the pair: what a single search actually matches, as a count plus a
sample. An author checking a query they are writing wants this; an author checking the RESULT
wants `customer.repos.order-preview`. `executedQuery` is the query as actually sent after the
product's own scoping was folded in — read it when a result surprises you, because the
difference is usually there. `items` is a sample and `totalCount` is the count; do not read
the length of one as the other. It WRITES NOTHING.
Arguments:
<id>
The `tenant_repos` row, as `customer.repos.list` returns it.
Flags:
--query <string> (required)
The one search to preview, in the provider's own query syntax.
Global flags:
--json / --no-json
Print the API's response unmodified. The default when stdout is not a terminal.
--help / --no-help
Show this command's arguments, flags and response fields.
--dry-run / --no-dry-run
Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.
--spec / --no-spec
On `descant version`: report the contract version this binary was generated from, and every command it reaches.
--base-url <string>
The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.
Credential:
Read from the credential `descant login` stored, else $DESCANT_API_KEY.
A tenant API key (`dsc_…`). The tenant is taken from the key, never from the request.
`descant whoami` names which of the two is in use, and never prints it.
Errors:
invalid_path (400) → exit 1
A path parameter failed validation.
invalid_body (400) → exit 1
The request body failed validation.
not_found (404) → exit 1
No such resource for this caller.
upstream_auth (502) → exit 7
An upstream service rejected this deployment's credential.
upstream_timeout (503) → exit 7
An upstream service did not answer in time.
upstream_unavailable (503) → exit 7
A service this operation depends on did not answer.
upstream_rate_limited (503) → exit 4
An upstream service is rate limiting us; retry after the stated interval.
internal_error (500) → exit 7
The request failed for a reason the caller cannot fix.
Wire:
POST /api/v1/repos/{id}/ordering-preview
operation customer.repos.ordering-previewScripting and agents
The global flags, as the binary describes them. What a program should know beyond them — machine output, dry runs, what a retry costs, the exit codes and every refusal a command can meet — is on the page for agents and scripts.
--json— Print the API's response unmodified. The default when stdout is not a terminal.--help— Show this command's arguments, flags and response fields.--dry-run— Print the request that would be sent — method, URL, headers, body — and exit 0 without sending it. The bearer is replaced by where it came from — a variable name, or the store `descant login` put it in. Works on every command that builds a request, reads as well as mutations; `login`, `logout` and `whoami` take no flags at all, and `version` sends nothing. A credential must still resolve, because the request is built in full.--spec— On `descant version`: report the contract version this binary was generated from, and every command it reaches.--base-url— The API origin to call. Defaults to $DESCANT_API_BASE_URL, then https://app.descant.run — the origin the deployment answers on, not the marketing site at the apex.