Planissue-criteria
POST/repositories/{repo_id}/issues/{issue_id}/criteria
Add an acceptance criterion to a work item's Definition of Done
create_issue_criterion
issue:update
A token needs theissue:updatescope: Update work items — status, fields, hierarchyGranted bymcp_agent_write,write_api,write_repository.Every operation this scope unlocksAudited · mutation
x-gitmoap-audit: {"kind":"audited","arg":"mutation"}
Quota · criteria per issue
x-gitmoap-quota: {"kind":"enforced","arg":"criteria_per_issue"}
Adds a criterion with its text, and optionally a binding — a test, metric, coverage floor or pipeline node whose result decides it. An unbound criterion is a manual checkbox; a bound one starts unverified and cannot be created already ticked. It goes last unless you give a position, and is required to close unless you say otherwise. Developers and above may add criteria, up to the per-work-item and per-repository limits.
Parameters
Path
- repo_idRepositoryId (string · uuid)Repository ID
- issue_idIssueId (integer · int64)Work-item iid
Request body
application/json- textstringThe human statement — the criterion's title.
- checkedboolean or nulloptionalFor a manual criterion, whether it starts ticked. Must be
falseor absent for a bound one. - descriptionstring or nulloptional
- ordinalinteger · int32 or nulloptional0–∞Where to place it; appended to the end when absent.
- required_for_closeboolean or nulloptionalWhether this criterion must be satisfied before the work item can be closed or verified. Defaults to
true; setfalsefor an aspirational one.
Request body exampleSynthesized example
{
"text": "text-962d"
}
Responses
200The created criterionapplication/json
- created_atstring
- criterion_idCriterionId (string · uuid)The criterion's identifier. It never changes, so rewording or re-binding a criterion keeps its identity and its evidence.
- issue_uidIssueUid (string · uuid)
- satisfiedboolean
- stateCriterionStateunverifiedpassingfailingstalemanualA criterion's verdict.
- textstringThe human statement — the criterion's title. The label, not the identity.
- updated_atstring
- binding_exprstring or nulloptionalThe binding's
@-expression, as written. Absent for a manual criterion. - binding_keystring or nulloptionalThe normalised form of
binding_expr, which two equivalent spellings share. Absent for a manual criterion. - checkedbooleanoptionalWhether the box is ticked. Meaningful only for a manual criterion — a bound one is ticked by its
state, and ticking it by hand is refused with a409. - descriptionstring or nulloptionalAn optional longer description (markdown) — the per-criterion detail a flat checkbox could never hold.
evidenceEvidence or nulloptional
- The Evidence schema
- commit_shaCommitHash (string)
- observed_atstringWhen the run that produced the evidence finished, RFC 3339.
- pipeline_idPipelineId (string · uuid)
- pipeline_run_idPipelineRunId (string · uuid)
- node_run_idNodeRunId (string · uuid) or nulloptional
- valuenumber · double or nulloptional
- facet_categoryFacetCategory (string) or nulloptional
- facet_keyFacetKey (string) or nulloptionalThe blueprint facet this criterion belongs to, when a blueprint added it. Absent for a criterion written directly. A facet's state is rolled up from the criteria carrying its key.
- last_evidence_atstring or nulloptional
- last_evidence_commit_shaCommitHash (string) or nulloptional
- last_evidence_node_run_idNodeRunId (string · uuid) or nulloptional
- last_evidence_pipeline_idPipelineId (string · uuid) or nulloptionalThe pipeline whose run last provided evidence for this criterion. Only a later run of the same pipeline can make the criterion
stale. - last_evidence_run_idPipelineRunId (string · uuid) or nulloptional
- last_evidence_valuenumber · double or nulloptionalThe observed value, for a
MetricorCoveragebinding. - ordinalinteger · int32optional0–∞Position in the description, so the list round-trips in authoring order.
- required_for_closebooleanoptional
@optionalopts out. Everything else in a Definition of Done is required by definition.
200 exampleSynthesized example
{
"created_at": "2026-10-13T22:27:32Z",
"criterion_id": "d5789762-dd6d-47f7-a865-e008d1283342",
"issue_uid": "abab5ae3-7a9b-47a4-b9f0-48e6bd8afe0b",
"satisfied": false,
"state": "unverified",
"text": "text-eaee",
"updated_at": "2026-10-12T12:54:55Z",
"binding": {
"kind": "test",
"test_id": "fd4ee03f-93f3-402d-a425-1a1e65a4b82d",
"suite": "suite-d008"
},
"binding_expr": "binding-expr-a484",
"binding_key": "binding-key-ed3e",
"checked": false,
"description": "Renders each region as density contours of its operations.",
"evidence": {
"commit_sha": "ccda5159578950281f615117fda3777980ac0662",
"observed_at": "2026-10-13T06:00:27Z",
"pipeline_id": "a1e755f5-c3eb-4630-90e1-0494ef782ea5",
"pipeline_run_id": "9e2b7c41-3d5a-4f8e-b1c2-7a6d5e4f3c2b",
"node_run_id": "085cf3f6-9dd4-4b7f-82c5-bc6ddb52d1d9",
"value": 22
},
"facet_category": "facet-category-1643",
"facet_key": "facet-key-6ff0",
"last_evidence_at": "2026-10-13T12:11:25Z",
"last_evidence_commit_sha": "0e020e1cf86c21469c63b93e095ce32c47769c12",
"last_evidence_node_run_id": "3c4c933a-ca7c-4584-a80c-8745f8a73858",
"last_evidence_pipeline_id": "7f0d0251-1759-4068-91aa-c59ebfcb8f58",
"last_evidence_run_id": "9e2b7c41-3d5a-4f8e-b1c2-7a6d5e4f3c2b",
"last_evidence_value": 69,
"ordinal": 70,
"required_for_close": false
}
400Invalid text or bindingapplication/json
Error codes
- BAD_REQUESTThe request is malformed: a parameter or a body field is not a well-formed value of its type
- INVALID_BINDINGThe criterion binding is not well-formed
errorErrorBody
- The ErrorBody schema
- codestringThe coarse category, tied to the status:
NOT_FOUND,CONFLICT,INSUFFICIENT_SCOPE. Every error has one; the API reference's error catalogue lists them all. - messagestringWhat went wrong, for a person. Written to be read, not parsed: match on
reasonorcode. - fieldstring or nulloptionalOn a
400that refused one field's value, the field, as its JSON path:email,items[0].name. Put the message under that input. Absent on every other error. - reasonstring or nulloptionalThe specific code, when the error has one:
LAST_ORGANIZATION_OWNER,BRANCH_PROTECTED. The message starts with it too. Match on this when it is present, and oncodeotherwise. - required_scopestring or nulloptionalThe scope the access token was missing, on
INSUFFICIENT_SCOPE. - violationsanyoptionalPresent on the structured-validation errors (
PROVIDER_VALIDATION_FAILEDper-node,PIPELINE_VALIDATION_FAILEDDAG): a JSON array of the offending elements, so a caller can fix every one in a single round-trip. Absent (omitted) on every other error.
400 exampleSynthesized example
{
"error": {
"code": "code-0d3e",
"message": "Pin a page's payload to the build that rendered it"
}
}
401Unauthenticated — no credential, or an invalid oneapplication/json
Error codes
- UNAUTHORIZEDNo credential was sent, or the one sent is invalid, expired or revoked
errorErrorBody
- The ErrorBody schema
- codestringThe coarse category, tied to the status:
NOT_FOUND,CONFLICT,INSUFFICIENT_SCOPE. Every error has one; the API reference's error catalogue lists them all. - messagestringWhat went wrong, for a person. Written to be read, not parsed: match on
reasonorcode. - fieldstring or nulloptionalOn a
400that refused one field's value, the field, as its JSON path:email,items[0].name. Put the message under that input. Absent on every other error. - reasonstring or nulloptionalThe specific code, when the error has one:
LAST_ORGANIZATION_OWNER,BRANCH_PROTECTED. The message starts with it too. Match on this when it is present, and oncodeotherwise. - required_scopestring or nulloptionalThe scope the access token was missing, on
INSUFFICIENT_SCOPE. - violationsanyoptionalPresent on the structured-validation errors (
PROVIDER_VALIDATION_FAILEDper-node,PIPELINE_VALIDATION_FAILEDDAG): a JSON array of the offending elements, so a caller can fix every one in a single round-trip. Absent (omitted) on every other error.
401 exampleSynthesized example
{
"error": {
"code": "code-5cce",
"message": "Label every synthesized example"
}
}
403Forbidden — the caller's access level or token scope is insufficientapplication/json
Error codes
- FORBIDDENYou are signed in but may not do this here;
reasonnames the permission or the rule that refused it - INSUFFICIENT_SCOPEThe access token lacks the scope this operation needs;
required_scopenames it - PERMISSION_REQUIREDYou need a permission you do not hold here; the message names it
errorErrorBody
- The ErrorBody schema
- codestringThe coarse category, tied to the status:
NOT_FOUND,CONFLICT,INSUFFICIENT_SCOPE. Every error has one; the API reference's error catalogue lists them all. - messagestringWhat went wrong, for a person. Written to be read, not parsed: match on
reasonorcode. - fieldstring or nulloptionalOn a
400that refused one field's value, the field, as its JSON path:email,items[0].name. Put the message under that input. Absent on every other error. - reasonstring or nulloptionalThe specific code, when the error has one:
LAST_ORGANIZATION_OWNER,BRANCH_PROTECTED. The message starts with it too. Match on this when it is present, and oncodeotherwise. - required_scopestring or nulloptionalThe scope the access token was missing, on
INSUFFICIENT_SCOPE. - violationsanyoptionalPresent on the structured-validation errors (
PROVIDER_VALIDATION_FAILEDper-node,PIPELINE_VALIDATION_FAILEDDAG): a JSON array of the offending elements, so a caller can fix every one in a single round-trip. Absent (omitted) on every other error.
403 exampleSynthesized example
{
"error": {
"code": "code-638a",
"message": "Inline scalar newtypes but keep their brand"
}
}
404Work item not foundapplication/json
Error codes
- NOT_FOUNDThe resource does not exist — or exists and you cannot see it: a private resource you have no access to answers exactly as an absent one does
errorErrorBody
- The ErrorBody schema
- codestringThe coarse category, tied to the status:
NOT_FOUND,CONFLICT,INSUFFICIENT_SCOPE. Every error has one; the API reference's error catalogue lists them all. - messagestringWhat went wrong, for a person. Written to be read, not parsed: match on
reasonorcode. - fieldstring or nulloptionalOn a
400that refused one field's value, the field, as its JSON path:email,items[0].name. Put the message under that input. Absent on every other error. - reasonstring or nulloptionalThe specific code, when the error has one:
LAST_ORGANIZATION_OWNER,BRANCH_PROTECTED. The message starts with it too. Match on this when it is present, and oncodeotherwise. - required_scopestring or nulloptionalThe scope the access token was missing, on
INSUFFICIENT_SCOPE. - violationsanyoptionalPresent on the structured-validation errors (
PROVIDER_VALIDATION_FAILEDper-node,PIPELINE_VALIDATION_FAILEDDAG): a JSON array of the offending elements, so a caller can fix every one in a single round-trip. Absent (omitted) on every other error.
404 exampleSynthesized example
{
"error": {
"code": "code-7905",
"message": "Stream pipeline status into the run header"
}
}
409A bound criterion cannot be created already tickedapplication/json
Error codes
- CONFLICTThe request conflicts with the resource's current state;
reasonsays how - CRITERION_IS_BOUNDThe criterion is bound to evidence a pipeline produces, so it cannot be ticked by hand; unbind it first
- LEGAL_ACCEPTANCE_REQUIREDAccept the current Terms of Service before changing anything; reading stays open
errorErrorBody
- The ErrorBody schema
- codestringThe coarse category, tied to the status:
NOT_FOUND,CONFLICT,INSUFFICIENT_SCOPE. Every error has one; the API reference's error catalogue lists them all. - messagestringWhat went wrong, for a person. Written to be read, not parsed: match on
reasonorcode. - fieldstring or nulloptionalOn a
400that refused one field's value, the field, as its JSON path:email,items[0].name. Put the message under that input. Absent on every other error. - reasonstring or nulloptionalThe specific code, when the error has one:
LAST_ORGANIZATION_OWNER,BRANCH_PROTECTED. The message starts with it too. Match on this when it is present, and oncodeotherwise. - required_scopestring or nulloptionalThe scope the access token was missing, on
INSUFFICIENT_SCOPE. - violationsanyoptionalPresent on the structured-validation errors (
PROVIDER_VALIDATION_FAILEDper-node,PIPELINE_VALIDATION_FAILEDDAG): a JSON array of the offending elements, so a caller can fix every one in a single round-trip. Absent (omitted) on every other error.
409 exampleSynthesized example
{
"error": {
"code": "code-a0e0",
"message": "Stream pipeline status into the run header"
}
}
429Per-issue or per-repository criteria limit reachedapplication/json
Error codes
- TOO_MANY_REQUESTSRetryableA quota, a rate limit or a transfer budget is spent. When the response carries
Retry-After, retry after that many seconds
errorErrorBody
- The ErrorBody schema
- codestringThe coarse category, tied to the status:
NOT_FOUND,CONFLICT,INSUFFICIENT_SCOPE. Every error has one; the API reference's error catalogue lists them all. - messagestringWhat went wrong, for a person. Written to be read, not parsed: match on
reasonorcode. - fieldstring or nulloptionalOn a
400that refused one field's value, the field, as its JSON path:email,items[0].name. Put the message under that input. Absent on every other error. - reasonstring or nulloptionalThe specific code, when the error has one:
LAST_ORGANIZATION_OWNER,BRANCH_PROTECTED. The message starts with it too. Match on this when it is present, and oncodeotherwise. - required_scopestring or nulloptionalThe scope the access token was missing, on
INSUFFICIENT_SCOPE. - violationsanyoptionalPresent on the structured-validation errors (
PROVIDER_VALIDATION_FAILEDper-node,PIPELINE_VALIDATION_FAILEDDAG): a JSON array of the offending elements, so a caller can fix every one in a single round-trip. Absent (omitted) on every other error.
429 exampleSynthesized example
{
"error": {
"code": "code-6466",
"message": "Inline scalar newtypes but keep their brand"
}
}
CLI
gitmoap issue-criteria create
- --repo-ididRepository ID (id or owner/repo path)
- --issue-ididWork-item iid
- --bindingjsonoptional
- --checkedflagoptionalFor a manual criterion, whether it starts ticked. Must be `false` or absent for a bound one.
- --descriptionstringoptional
- --ordinalnumberoptionalWhere to place it; appended to the end when absent.
- --required-for-closeflagoptionalWhether this criterion must be satisfied before the work item can be closed or verified. Defaults to `true`; set `false` for an aspirational one.
- --textstringThe human statement — the criterion's title.