API compatibility
The public API
Section titled “The public API”The App Service publishes a subset of its HTTP API as the public API, described by an OpenAPI
document served unauthenticated at GET /api/v1/openapi.json. Only routes in that document are
covered by the compatibility promise below, and the CLI calls only those routes.
Each CLI release carries a copy of the document it was built against. Request bodies are validated
against its schemas before they are sent (INPUT_INVALID, exit 2), and answers after
(UNEXPECTED_RESPONSE, exit 1). A command that needs a route or a field the server does not
publish waits for it and is marked planned (Not yet published).
pidgen skills status compares this document with the configured server's: it reads
GET /api/v1/openapi.json without a credential and reports in data.contract whether the two
are the same or different — by info.version and by a sha256: digest of the document with
its keys sorted — and the operations (METHOD /path) only one side has. A server that cannot be
read leaves the contract unchecked, with the reason, and the command still exits 0. The two differ
when the server has moved on since the CLI was released; a newer CLI release catches up.
The Ticket actions routes are GET /tickets/{ticket_id}/actions (filters family — recovery,
run_control or ticket_control — run_id, node_id and prerequisite_id) answering
{ticket_id, actions: [ActionDecision]}; POST /tickets/{ticket_id}/actions/preview with
{action, target} answering ActionPreviewResult (preview_digest,
required_acknowledgements, action, target, consequence, effects); and
POST /tickets/{ticket_id}/actions/execute with ActionExecuteRequest (preview_digest,
idempotency_key of 1–255 characters, at most 20 acknowledgements, reason of at most 10240
characters, note of at most 4000, action, target) answering 200 when settled or 202 while
applying, each with ActionExecuteResult (command_id, ticket_id, status,
disposition_code, workflow_run_id, status_url, action, target; no result object). An
ActionTarget names its kind (ticket, run, node or dependency) and ticket_id, and
optionally run_id, node_id, fan_out_key and prerequisite_id. Each GET /attention finding
carries target, label, prerequisite_id and its actions, and has no action field. The
older recovery routes, whose request names its action (not a mode), stay in the document; the
CLI does not call them.
Domains, contracts, Requests and worker releases
Section titled “Domains, contracts, Requests and worker releases”The domain, Domain Epic, contract, contract publishing, Request and worker release routes are in the published document, and the CLI validates what it sends to them and what they answer against it. What the commands read:
- Domains (
/api/v1/domains,/api/v1/domains/{domain_id}and itsprojects,parentsandpeopleroutes): a list answersDomainResources and one domain aDomainDetail(the resource andgranted, the people granted on it). A domain counts its people underpeople(members,maintainers) and its Epics underepics(open,total). The people routes take the person's user id (a UUID). The domain key's format is stated in prose only, so a key the server refuses is itsDOMAIN_KEY_INVALID(exit 2), not a client-side check. - Domain Epics:
POST /api/v1/ticketswithdomain_idinstead ofproject_id(the publishedCreateTicketRequestcarriesdomain_id) andGET /api/v1/domains/{domain_id}/epics, answering aDomainEpicBoard. - Contracts:
GET /api/v1/projects/{project_ref}/contractsanswersprovidesandbuilds_against(a Project's list has no coverage);GET /api/v1/domains/{domain_id}/contractsanswersown,coverage(byprovider) andbuilds_against. Each entry names itsproviderbyproject_id,kind(projectordomain),domain_id,keyandname, and its owner byowner_user_id.GET /api/v1/contracts/{contract_id},POST …/revisionsandPOST …/revisions/{revision_number}/acceptanswer the whole contract (ContractDetail); a revision'soperationsare{name, types?}and its request requiresschema_format. A contract read states each operation's state inoperation_states({name, state},statelanded,pendingorremoved). The link routesPOST /api/v1/tickets/{ticket_id}/contractsandDELETE /api/v1/tickets/{ticket_id}/contracts/{contract_id}answer the Ticket's contracts. - Creating a contract:
POST /api/v1/projects/{project_ref}/contractsandPOST /api/v1/domains/{domain_id}/contractstake aContractCreateRequest(title,kind,markdown,schema_format,native_schema,operations,owner_user_id,describes_existing) and answer 201 with the created contract (ContractResponse). - Publishing contracts:
POST /api/v1/projects/{project_ref}/contracts/publishandPOST /api/v1/domains/{domain_id}/contracts/publishtake multipartfile(the entry) and repeatedfilesnamed by their relative paths, withsource_path,format,commit,ref,source_kind(uploadorci),breaking,titleanddry_run, and answer aContractPublishResult: theoutcome(created,revisedorunchanged),contract_idandcontract_key,revision,acceptance,change(additiveorbreaking) andchange_reason, the operations and types added and removed, the pending operationslandedandstill_pending, andresolved_refusals.POST /api/v1/contracts/{contract_id}/proposalsandPOST /api/v1/projects/{project_ref}/contracts/proposalstake a fragment file (and a new contract'stitle) and answer aContractProposalResultwith the operations madependingand alreadylanded. A refused file is aPublicError:CONTRACT_FILE_INVALID,CONTRACT_FORMAT_UNSUPPORTED,CONTRACT_EXTERNAL_REF,CONTRACT_CONVERSION_TIMEOUT(422) orFILE_TOO_LARGE(413), witherror.details.errors({message, location}) and, for a publish that is not a dry run,error.details.refusal_id. - Requests:
POST /api/v1/requests(aRequestSubmitRequest; 201 new or 200 deduplicated, answering aRequestReceivedwith theoutcome,entry_idandoccurrence_id),GET /api/v1/requests(filtered bystate,kind,target_kind,target_id,source_idandq, paged),GET /api/v1/requests/{request_id},GET /api/v1/requests/{request_id}/timeline(each entry with itsauthor, the ids of itsattachmentsand itsfiles), the triage writesacknowledge,ask,reply,accept,closeandpromote(each carrying theversionit read), andPOST /api/v1/requests/{request_id}/attachments(multipart). A Request'srequester,source,targetandurgencymay be null;closetakes areasonfrom a fixed list, andpromotegoes to the Project the Request targets,project_idonly confirming it. - Worker releases:
GET /api/v1/workers/releases, read with no credential and no Account, answering aWorkerReleaseList.
Not yet published
Section titled “Not yet published”These features wait for the public API to publish what they need, and are marked planned where they would appear:
- Artifact uploads.
SteerRequestand ticket creation takeupload_ids, but the route that stages an upload (POST /artifact-uploads) is not in the public document.pidgen session steer --uploadis planned until the server publishes it; the CLI has no--uploadflag. pidgen ticket list --epicremains planned. The published ticket list supports aparent_ticket_idfilter; the CLI does not yet expose it as--epic.
Version negotiation
Section titled “Version negotiation”- The CLI sends
PIDGEN-API-Version: <major>.<minor>on every request. - The server accepts its current minor version and the previous minor of the same major, and echoes
the negotiated version in the response header of the same name.
meta.api_versionin the envelope shows it. - Anything else is refused with
426andAPI_VERSION_UNSUPPORTED, which names the supported range and how to upgrade. The CLI exits 7. GET /api/v1/healthreportsapi_version.currentandapi_version.minimum.pidgen auth loginchecks it before sending credentials, andpidgen auth statusshows it. Only after that check doespidgen auth loginsend the new credential, to verify it before storing it (GET /api/v1/mefor a CLI token,GET /api/v1/projectsfor a Personal API Token).pidgen auth statusandpidgen account listsend a CLI token only toGET /api/v1/me, andpidgen auth listandpidgen auth revokesend one toGET /api/v1/me/sessionsandDELETE /api/v1/me/sessions/{session_id}; none of them sends a Personal API Token there.- Breaking changes to the public API are announced at least 30 days ahead.
CLI releases
Section titled “CLI releases”Each CLI release states the API version range it supports, in pidgen --version and the changelog.
The CLI follows semantic versioning: a major release changes or removes commands, flags, envelope
fields, exit codes, CLI error codes, or skills in an incompatible way.