Skip to content
GitmoapdocsBeta
Guide· How do I…?

Blueprints

Overview

Every team has a shape for a kind of work that never reaches the tracker: a new endpoint also needs a metric, a test, an access rule and a page of documentation, and the person who knows that is the reviewer who says so afterwards. A blueprint writes that shape down as a custom work-item type — a display name, one of the eight base types, and a list of facets the work has to cover. File an item as that type and the facets land on it as acceptance criteria, ticked automatically wherever a pipeline can prove them. Define the type once on a group and every repository beneath it offers it.

Quick start

Quick start3 steps

Give a kind of issue its own Definition of Done

You’ll need Maintainer on the repository or the group.

Start at Work item types in the side navigation.

  1. Start from one of the templates — or design your own type.

  2. Give each facet a label. Every facet becomes an acceptance criterion on the issues filed as this type.

  3. Choose Create type. It appears in the type picker on New issue.

Before you start

  • Maintainer access on the repository or group you define the type on.A type is shared vocabulary that reaches every item filed against it, so defining one sits at the same level as minting labels and milestones.
  • Reporter access to read the types a container offers.Reading follows the container's visibility: a public repository answers anyone, an internal one answers anyone signed in, and a private one answers only its members. A private container answers 404 rather than 403, so its existence stays private.
  • Developer access on the repository to apply a type to work already filed.Applying writes acceptance criteria onto the item, which is the same bar as authoring one by hand.
  • Reporter access to file work as a custom type.Picking the type on the new-item form is the whole of it, so filing as a custom type costs no more access than filing anything else.
  • A token with the right scopes, if you are scripting: issue_blueprint:read to list and read types, issue_blueprint:manage to define, change, archive or delete one, and issue:update to apply one to an item. A token carries the scopes you chose when you minted it, and they cannot be added to it afterwards.
  • A decision about where the type lives.A type defined on a group is offered by every repository beneath it; a type defined on a repository is offered only there. If two repositories file the same kind of work, define it once on the group above them.
  • Acceptance criteria, if you want facets that prove themselves.A facet is a named group of criteria, and a bound facet is decided by the evidence your pipelines already emit — see Proof of Done for what can be bound and how the evidence gets there.

How it works

A custom type is a base type wearing a name

A blueprint does not invent a new kind of object. It picks one of the eight base types — epic, story, bug, task, spike, chore, incident, sub_task — and adds two things to it: a display name and a facet list. "API Feature" is a story wearing a blueprint. The base type still decides everything structural: which container the item may live in, which children it may hold, and which fields it has. Nothing about hierarchy, boards, listings or rollups changes because an item was filed as a custom type.

That is also why one rule exists that surprises people: an epic lives only in a group, and a group has no pipelines to prove anything, so an epic-based blueprint may declare manual facets only. A bound facet on one is refused at the moment you save the type, not silently dropped when somebody files an item.

Facets become acceptance criteria

Applying a blueprint seeds one acceptance criterion per facet. There is no second checklist and no generated markdown — the facets are the item's Definition of Done, written as the same typed criteria you would author by hand.

Blueprint "API Feature" defined on the group acme
base type: story
facets:
endpoint category: interface required manual
tests category: quality required bound to a test result
observability category: operability required manual
docs category: docs optional manual
| file an item as "API Feature", or apply it to one already filed
v
Work item acme/billing #412 "Add the refund endpoint"
type: story — the blueprint's base type
Definition of Done
Endpoint you tick it
Tests the pipeline decides it — you cannot tick it by hand
Observability you tick it
Docs optional, so it does not hold the item open

Each facet carries a category — interface, operability, accessibility, runbook, whatever your work decomposes into. The vocabulary is yours: a category the platform has never seen is a first-class category, stored and grouped exactly like the ones it has. A facet is green when every one of its required criteria is satisfied, and an item is verified when every required criterion on it is green — which is exactly what the merge gate reads in a repository that has turned it on.

A facet's hint becomes the criterion's text. Leave it out and the criterion is titled by the facet's label, which is fine for Tests and useless for Observability — a hint like "the endpoint emits a request metric and an error metric" is the difference between a checklist and a checklist somebody can act on.

The key is the identity, the label is just the label

Every facet has a key — a slug of 1 to 40 characters, lowercase letters, digits and hyphens — and that key is what the platform matches on. Rename the "Observability" facet to "Operability" and keep its key, and an item that already carries it keeps the criterion it has, with its tick and its history. Change the key instead and you have made a different facet: the old one stays on every item that has it, and the new one arrives beside it the next time the type is applied.

Types are inherited down the group chain

The types a container offers are its own plus every ancestor group's. Ask a repository for its available types and you get its own first, then the inherited ones nearest group first. Where two definitions share an id, the nearest one wins — a repository Maintainer can define api-feature locally and shadow the organisation's version, without affecting any other repository and without needing anything from the group.

Inheritance runs upward only. A type defined on a repository is invisible to its group, and a type defined on a sibling group is invisible here.

Applying is additive, and never a clobber

Applying a blueprint to an item compares facet keys and seeds only the ones the item does not already have. It never rewrites, reorders, unticks or removes an acceptance criterion: an existing facet keeps its tick, its evidence and any criteria a person added underneath it. So re-applying a type after it grew a fifth facet adds one criterion and disturbs nothing.

The consequences of that rule are worth stating plainly, because they are what make the feature safe to use on live work:

  • Editing a blueprint does not reach back into items already filed. Adding a facet does not appear on last month's stories until somebody re-applies the type to them.
  • Removing a facet from a blueprint does not retract it from anything. Items that were filed with it keep it.
  • Deleting a blueprint leaves every seeded criterion intact. A tracker that silently deleted acceptance criteria out from under a team would be a tracker nobody could trust with a Definition of Done.

What an item takes from a blueprint is its Definition of Done, and only that. The item's own type stays the base type the blueprint named — an item shaped by "API Feature" is a story, and reads as a story everywhere a type is shown.

A type is addressed by the container that defines it

A blueprint is named by the container it was defined on, never by the container a work item happens to sit in. That is what makes an inherited type usable — a repository's item can be filed as a type defined three groups above it — and it is why, whenever you name a type outside the picker, you give the group or repository that owns the definition. Every listing entry carries that container, so it is the place to read the id from rather than guessing.

Filing with a type and applying a type are not quite the same

Filing a new item as a custom type sets the item's underlying type from the blueprint's base type and seeds the facets in the same write. It also checks the base type against the container: an epic-based type files in a group, an incident-based one in either, every other base type files in a repository, and the wrong pairing is refused with 400 BAD_REQUEST before anything is written. Applying a type to an item that already exists seeds the facets it is missing and leaves the underlying type where it was — so a task that gets a story-based type is still a task, with a task's fields and a task's legal children. When you want the structure too, file the item as the type; when you want the checklist on work already in flight, apply it.

How to

Define a custom type on a repository

Give it an id, a display name, a base type and its facets. The id is the identity — 1 to 40 characters, lowercase letters, digits and hyphens, not starting with a hyphen — and it has to be free within that container. The display name is what people pick from the type selector.

Open the repository, choose Issues in the side navigation, then Types, then New type. The header of the builder takes the name, the base type and a description that shows as help text under the type in the picker. Below it, each row is a facet: a label, a category of your own naming (any lowercase slug, not only the ones the platform has seen before), a Required toggle that starts on, an optional hint, and a binding picker that starts at Manual. Drag rows to reorder them; the order is the order the criteria appear on the item. Save type returns you to the list.

Terminalbash
gitmoap issue-blueprints create-repo-blueprint \
--repo-id <id> \
--base-type epic|story|bug|task|spike|chore|incident|sub_task \
--blueprint-id <blueprint id> \
--name <name>
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/issue-blueprints \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "base_type": "<one of bug | chore | epic | incident | spike | story | sub_task | task>", "blueprint_id": "<string>", "facets[].category": "<string>", "facets[].key": "<string>", "facets[].label": "<string>", "name": "<string>" }'

Every facet is a manual checkbox until you bind it, and that is the right default — most facets on most teams are not worth binding, and a type that demanded machine-checkable everything would go unused. Bind the ones that pay: a test result, a metric threshold, a coverage floor over a path, or a pipeline node going green. A bound facet's box is filled in from evidence and cannot be ticked by a person, which is the whole point of binding it.

Define a custom type for every repository in a group

Same builder, one level up. This is where a type belongs if more than one repository files that kind of work.

Open the group, choose Issues then Types, then New type. The base-type selector here offers the group-legal types as well — Epic and Incident can be filed in a group — and a type based on any of the repository types is still defined here and offered to every repository beneath.

Terminalbash
gitmoap issue-blueprints create-group-blueprint \
--group-id <id> \
--base-type epic|story|bug|task|spike|chore|incident|sub_task \
--blueprint-id <blueprint id> \
--name <name>
curlbash
curl -X POST https://api.gitmoap.example/groups/{group_id}/issue-blueprints \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "base_type": "<one of bug | chore | epic | incident | spike | story | sub_task | task>", "blueprint_id": "<string>", "facets[].category": "<string>", "facets[].key": "<string>", "facets[].label": "<string>", "name": "<string>" }'

A type whose base type is epic may declare manual facets only, because an epic lives in a group and no group has pipelines that could decide a bound one. That is a property of the base type rather than of where you define it, so it holds for an epic-based type defined on a repository too. Trying to save a bound facet on one is refused with 400 BAD_REQUEST naming the facet. incident is not caught by this — an incident can live in a repository, so an incident-based type may bind.

See which types a repository offers

By default a repository reports only the types defined on it. Ask for inherited ones as well and you get the full picker: its own first, then each ancestor group's, nearest first, with any shadowed duplicate already resolved in favour of the nearer one. Each entry says whether it was inherited, and which container defines it.

Archived types come back in that listing too, each carrying its archived flag. The listing is the raw available set; leaving the archived ones out of the picker is the picker's job, so a script that reads this should filter them itself.

Open the repository, choose Issues then Types. The list shows the repository's own types and the ones it inherits, each tagged with the container it comes from, and archived ones greyed. A filter on the list narrows it to the types defined here.

Terminalbash
gitmoap issue-blueprints list-repo-blueprints --repo-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/issue-blueprints \
-H "Authorization: Bearer $GITMOAP_TOKEN"

See the types a group defines

Open the group, choose Issues then Types. This is the list every repository beneath the group inherits from, so it is the one to edit when you want a change to reach the whole organisation.

Terminalbash
gitmoap issue-blueprints list-group-blueprints --group-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/issue-blueprints \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Read one type's definition

Reading one type asks the container that defines it. A repository will not answer for a type it merely inherits — that read belongs to the group that owns it, and asking the repository gives 404 NOT_FOUND.

Open Issues then Types and choose a row defined on this repository. The detail view shows the base type, the description and every facet with its category, its required flag and its binding.

Terminalbash
gitmoap issue-blueprints get-repo-blueprint \
--repo-id <id> \
--blueprint-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/issue-blueprints/{blueprint_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Open the group's Issues then Types and choose a row. A repository that inherits this type links here rather than showing a local copy, so there is one place it is edited.

Terminalbash
gitmoap issue-blueprints get-group-blueprint \
--group-id <id> \
--blueprint-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/issue-blueprints/{blueprint_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

File work as a custom type

There is no separate "assign a blueprint" step, because picking the type is the assignment. The type selector on the new-item form lists the base types that container takes plus every custom type it offers, and choosing one files the item on the blueprint's base type with its facets already seeded, in a single write — if any part of it is refused, nothing is created. That path is covered in Work items.

Apply a type to work already filed

Applying seeds the facets the item is missing as acceptance criteria. Name the blueprint by the container that defines it — the group, if the type is inherited — and the item by its number in the repository.

Open the work item and use Apply type in the Definition of Done panel. The picker lists the same types the new-item form offers. The panel then shows the facets it added; anything already on the item is left exactly as it was, and the panel says so rather than pretending it did more.

Terminalbash
gitmoap issue-blueprints apply-blueprint \
--repo-id <id> \
--issue-id <id> \
--blueprint '<json>'
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/issues/{issue_id}/apply-blueprint \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "blueprint.blueprint_id": "<string>", "blueprint.scope": "<object>" }'

Applying the same type twice is safe and adds nothing the second time. Applying it after the type grew a facet adds that one facet. If the item has no room left — fifteen criteria is the ceiling on any plan — the whole apply is refused with 429 TOO_MANY_REQUESTS and nothing is written, so you never end up with half a checklist.

A work item filed in a group takes its facets at the moment it is filed. Applying a type after the fact is a repository operation.

Change a type

An update is a full replace under the version you read: send the whole definition, including every facet you want to keep, along with the version that came back from your last read. If somebody else saved the type in between, the write is refused with 409 CONFLICT rather than quietly discarding their edit.

Open Issues then Types, choose the type, and edit it in the same builder that created it. Rows can be added, reordered, relabelled and deleted. Keep a facet's key when you are only changing its wording — the key is what ties the facet to the criteria already sitting on live work. Save reloads the type if somebody changed it while you were editing, and tells you so.

Terminalbash
gitmoap issue-blueprints update-repo-blueprint \
--repo-id <id> \
--blueprint-id <id> \
--base-type epic|story|bug|task|spike|chore|incident|sub_task \
--name <name> \
--version <n>
curlbash
curl -X PUT https://api.gitmoap.example/repositories/{repo_id}/issue-blueprints/{blueprint_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "base_type": "<one of bug | chore | epic | incident | spike | story | sub_task | task>", "facets[].category": "<string>", "facets[].key": "<string>", "facets[].label": "<string>", "name": "<string>", "version": "<int64>" }'

Open the group's Issues then Types and edit the row. Every repository beneath the group sees the new definition on its next read of the picker; items already filed are untouched until somebody applies the type again.

Terminalbash
gitmoap issue-blueprints update-group-blueprint \
--group-id <id> \
--blueprint-id <id> \
--base-type epic|story|bug|task|spike|chore|incident|sub_task \
--name <name> \
--version <n>
curlbash
curl -X PUT https://api.gitmoap.example/groups/{group_id}/issue-blueprints/{blueprint_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "base_type": "<one of bug | chore | epic | incident | spike | story | sub_task | task>", "facets[].category": "<string>", "facets[].key": "<string>", "facets[].label": "<string>", "name": "<string>", "version": "<int64>" }'

Removing a facet from a definition stops it being seeded onto anything new. It does not remove it from work that already carries it — delete the criterion on those items if that is what you meant.

Retire a type without breaking anything

Archiving is the safe retirement: the type stays exactly where it is, keeps its facets and its bindings, and is marked archived so the type picker stops offering it. Archived is a field on the definition, so you archive a type by saving it — the same full-replace save described under Change a type — with that field set to true. Because the save is a full replace, a later save that leaves the field out brings the type back out of the archive; send it every time.

Archiving is a signal to the pickers, not a lock. The listing still returns an archived type, and a request that names its id can still file or apply with it, so archiving stops people choosing the type by accident rather than stopping the type working. Delete it when the id has to stop working altogether.

Note

Archive before you delete. Archiving is reversible and keeps the definition — with every facet, hint and binding you tuned — recoverable. It does cost a slot against your per-container limit, which is almost always the better trade.

Delete a type

Warning

Deleting a type cannot be undone, and it is not the same as archiving. Every work item filed against it keeps its acceptance criteria, with their ticks and their evidence — nothing about a Definition of Done is destroyed. What is destroyed is the definition itself: its facet list, its categories, its hints and its bindings are gone, and nothing can be filed with the type or have it applied from then on. The id falls free the moment the type goes, so the only repair for an accidental delete is to define it again with the same id, the same facet keys and the same bindings — and a facet key you do not reproduce exactly will seed a second criterion beside the old one the next time somebody applies the type.

Open Issues then Types, find the type, and choose Delete from the row menu. The confirmation names the type and warns that the definition, not the criteria it seeded, is what goes.

Terminalbash
gitmoap issue-blueprints delete-repo-blueprint \
--repo-id <id> \
--blueprint-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/issue-blueprints/{blueprint_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Open the group's Issues then Types and choose Delete from the row menu. Deleting a group type removes it from every repository's picker at once; a repository that defined its own type with the same id is unaffected and simply stops being a shadow.

Terminalbash
gitmoap issue-blueprints delete-group-blueprint \
--group-id <id> \
--blueprint-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/groups/{group_id}/issue-blueprints/{blueprint_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Reference

11 endpoints

GET/repositories/{repo_id}/issue-blueprintsissue_blueprint:read

List a repository's available custom types

list_repo_blueprintsGET /repositories/{repo_id}/issue-blueprintsOpen in the API reference

Token scope

issue_blueprint:read

Send

Path
  • repo_iduuidRepository ID
Query
  • includestringoptionalSet to inherited to include ancestor-group blueprints

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Repository not found

Receive

No body.

POST/repositories/{repo_id}/issue-blueprintsissue_blueprint:manage

Define a custom work-item type on a repository

create_repo_blueprintPOST /repositories/{repo_id}/issue-blueprintsOpen in the API reference

Token scope

issue_blueprint:manage

Counts against

blueprints_per_scope

Send

Path
  • repo_iduuidRepository ID
Body
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • blueprint_idstring
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestring

Refuses

  • 400Invalid facets, or a bound facet on a group-only base type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found — or found and private, which is masked as absent
  • 409A blueprint with this id already exists in the scope
  • 429Per-scope blueprint limit reached

Receive

  • archivedbooleanoptionalHidden from the type picker; items already filed keep their type_profile.
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • blueprint_idstring
  • created_atstring
  • created_byobject4 fields
    • avatar_urlobjectoptional
    • emailstring
    • idstring
    • usernamestring
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestringThe custom type's display name, such as "API Feature".
  • scope_idstring
  • scope_typegrouprepository
  • updated_atstring
  • versionint64optionalThe blueprint's revision, incremented on every change. An update must send the version it read, and is refused with a 409 if the blueprint changed since.
GET/repositories/{repo_id}/issue-blueprints/{blueprint_id}issue_blueprint:read

Get one of a repository's custom types

get_repo_blueprintGET /repositories/{repo_id}/issue-blueprints/{blueprint_id}Open in the API reference

Token scope

issue_blueprint:read

Send

Path
  • repo_iduuidRepository ID
  • blueprint_idstringBlueprint id (slug)

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Blueprint not found

Receive

The same shape create_repo_blueprint returns — 12 fields. Go to it

Show it here
  • archivedbooleanoptionalHidden from the type picker; items already filed keep their type_profile.
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • blueprint_idstring
  • created_atstring
  • created_byobject4 fields
    • avatar_urlobjectoptional
    • emailstring
    • idstring
    • usernamestring
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestringThe custom type's display name, such as "API Feature".
  • scope_idstring
  • scope_typegrouprepository
  • updated_atstring
  • versionint64optionalThe blueprint's revision, incremented on every change. An update must send the version it read, and is refused with a 409 if the blueprint changed since.
PUT/repositories/{repo_id}/issue-blueprints/{blueprint_id}issue_blueprint:manage

Replace a repository's custom type

update_repo_blueprintPUT /repositories/{repo_id}/issue-blueprints/{blueprint_id}Open in the API reference

Token scope

issue_blueprint:manage

Send

Path
  • repo_iduuidRepository ID
  • blueprint_idstringBlueprint id (slug)
Body
  • archivedbooleanoptional
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestring
  • versionint64The version you last read. The update is refused with a 409 if the blueprint changed since.

Refuses

  • 400Invalid facets
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Blueprint not found
  • 409The blueprint was modified concurrently

Receive

The same shape create_repo_blueprint returns — 12 fields. Go to it

Show it here
  • archivedbooleanoptionalHidden from the type picker; items already filed keep their type_profile.
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • blueprint_idstring
  • created_atstring
  • created_byobject4 fields
    • avatar_urlobjectoptional
    • emailstring
    • idstring
    • usernamestring
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestringThe custom type's display name, such as "API Feature".
  • scope_idstring
  • scope_typegrouprepository
  • updated_atstring
  • versionint64optionalThe blueprint's revision, incremented on every change. An update must send the version it read, and is refused with a 409 if the blueprint changed since.
DELETE/repositories/{repo_id}/issue-blueprints/{blueprint_id}issue_blueprint:manage

Delete a repository's custom type

delete_repo_blueprintDELETE /repositories/{repo_id}/issue-blueprints/{blueprint_id}Open in the API reference

Token scope

issue_blueprint:manage

Send

Path
  • repo_iduuidRepository ID
  • blueprint_idstringBlueprint id (slug)

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Blueprint not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

GET/groups/{group_id}/issue-blueprintsissue_blueprint:read

List a group's available custom types

list_group_blueprintsGET /groups/{group_id}/issue-blueprintsOpen in the API reference

Token scope

issue_blueprint:read

Send

Path
  • group_iduuidGroup ID
Query
  • includestringoptionalSet to inherited to include ancestor-group blueprints

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Group not found

Receive

No body.

POST/groups/{group_id}/issue-blueprintsissue_blueprint:manage

Define a custom work-item type on a group

create_group_blueprintPOST /groups/{group_id}/issue-blueprintsOpen in the API reference

Token scope

issue_blueprint:manage

Counts against

blueprints_per_scope

Send

Path
  • group_iduuidGroup ID
Body
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • blueprint_idstring
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestring

Refuses

  • 400Invalid facets, or a bound facet on a group-only base type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not found — or found and private, which is masked as absent
  • 409A blueprint with this id already exists in the scope
  • 429Per-scope blueprint limit reached

Receive

The same shape create_repo_blueprint returns — 12 fields. Go to it

Show it here
  • archivedbooleanoptionalHidden from the type picker; items already filed keep their type_profile.
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • blueprint_idstring
  • created_atstring
  • created_byobject4 fields
    • avatar_urlobjectoptional
    • emailstring
    • idstring
    • usernamestring
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestringThe custom type's display name, such as "API Feature".
  • scope_idstring
  • scope_typegrouprepository
  • updated_atstring
  • versionint64optionalThe blueprint's revision, incremented on every change. An update must send the version it read, and is refused with a 409 if the blueprint changed since.
GET/groups/{group_id}/issue-blueprints/{blueprint_id}issue_blueprint:read

Get one of a group's custom types

get_group_blueprintGET /groups/{group_id}/issue-blueprints/{blueprint_id}Open in the API reference

Token scope

issue_blueprint:read

Send

Path
  • group_iduuidGroup ID
  • blueprint_idstringBlueprint id (slug)

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Blueprint not found

Receive

The same shape create_repo_blueprint returns — 12 fields. Go to it

Show it here
  • archivedbooleanoptionalHidden from the type picker; items already filed keep their type_profile.
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • blueprint_idstring
  • created_atstring
  • created_byobject4 fields
    • avatar_urlobjectoptional
    • emailstring
    • idstring
    • usernamestring
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestringThe custom type's display name, such as "API Feature".
  • scope_idstring
  • scope_typegrouprepository
  • updated_atstring
  • versionint64optionalThe blueprint's revision, incremented on every change. An update must send the version it read, and is refused with a 409 if the blueprint changed since.
PUT/groups/{group_id}/issue-blueprints/{blueprint_id}issue_blueprint:manage

Replace a group's custom type

update_group_blueprintPUT /groups/{group_id}/issue-blueprints/{blueprint_id}Open in the API reference

Token scope

issue_blueprint:manage

Send

Path
  • group_iduuidGroup ID
  • blueprint_idstringBlueprint id (slug)
Body
  • archivedbooleanoptional
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestring
  • versionint64The version you last read. The update is refused with a 409 if the blueprint changed since.

Refuses

  • 400Invalid facets
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Blueprint not found
  • 409The blueprint was modified concurrently

Receive

The same shape create_repo_blueprint returns — 12 fields. Go to it

Show it here
  • archivedbooleanoptionalHidden from the type picker; items already filed keep their type_profile.
  • base_typebugchoreepicincidentspikestorysub_tasktaskThe kind of work item. The type decides where an item may live (a group or a repository), which types it may have as children, and which fields it accepts.
  • blueprint_idstring
  • created_atstring
  • created_byobject4 fields
    • avatar_urlobjectoptional
    • emailstring
    • idstring
    • usernamestring
  • descriptionobjectoptional
  • facets[]object6 fields
    • bindingobjectoptionalHow the criterion a facet adds is bound: manual (the default), or bound to evidence the platform checks. A blueprint whose base type lives only in a group, such as epic, may use only manual.
    • categorystring
    • hintobjectoptionalThe starter criterion's text. When absent the criterion is titled by the facet label.
    • keystring
    • labelstringThe facet's display name, such as "Observability".
    • requiredbooleanoptionalWhether the facet's criterion must be satisfied before the work item can be closed or verified. Defaults to true.
  • namestringThe custom type's display name, such as "API Feature".
  • scope_idstring
  • scope_typegrouprepository
  • updated_atstring
  • versionint64optionalThe blueprint's revision, incremented on every change. An update must send the version it read, and is refused with a 409 if the blueprint changed since.
DELETE/groups/{group_id}/issue-blueprints/{blueprint_id}issue_blueprint:manage

Delete a group's custom type

delete_group_blueprintDELETE /groups/{group_id}/issue-blueprints/{blueprint_id}Open in the API reference

Token scope

issue_blueprint:manage

Send

Path
  • group_iduuidGroup ID
  • blueprint_idstringBlueprint id (slug)

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Blueprint not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

POST/repositories/{repo_id}/issues/{issue_id}/apply-blueprintissue:update

Apply a custom type's facets to an existing work item

apply_blueprintPOST /repositories/{repo_id}/issues/{issue_id}/apply-blueprintOpen in the API reference

Token scope

issue:update

Counts against

criteria_per_issue

Send

Path
  • repo_iduuidRepository ID
  • issue_idint64Work-item iid
Body
  • blueprintobject2 fields
    • blueprint_idstring
    • scopeobjectA group or a repository, whichever a setting belongs to.

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Work item not found, or no such blueprint on this repository or the groups above it
  • 409The request conflicts with the resource's current state; `reason` says how
  • 429Per-issue or per-repository criteria limit reached

Receive

No body.

Limits

Two of the limits here scale with your plan. Both are checked as part of the write, so a refused request creates nothing at all — no half-saved type and no partial checklist.

What is countedFreeProTeamEnterprise
Custom types defined on one repository or group2550200500
Acceptance criteria carrying a binding, across one repository2,0005,00020,00050,000

The type count is of the types a container defines itself. Inherited ones are free, which is the direct incentive the design intends: define api-feature once on the organisation's root group rather than twenty-eight times, and you spend one slot instead of twenty-eight. Reaching the ceiling answers 429 TOO_MANY_REQUESTS; delete a type nobody files with, move the definition up to a group, or have an Owner request a limit increase from the group's usage page. Archiving does not help here — an archived type leaves the picker but keeps its slot.

The bound-criteria count is per repository and counts only criteria that carry a binding — manual checkboxes are not counted at all. It exists because every bound criterion is re-checked against every pipeline run in that repository.

The rest are the same on every plan, because they are product shape rather than capacity:

What is countedLimit
Facets in one type25
Acceptance criteria on one work item15
Characters in a facet label or hint200
Characters in a type id, facet key or category40

Going over the facet, label or id limits is a 400 BAD_REQUEST when you save the type. The per-item criteria limit is the one that binds when the type reaches an item rather than when you save it: a type with twenty facets is perfectly legal to define and is refused with 429 TOO_MANY_REQUESTS the moment you file or apply it, because fifteen is all an item holds. If a type keeps hitting that, it is describing more than one piece of work — split it, and let the facets that are really their own job become their own items.

Troubleshooting

400 BAD_REQUESTon any call, mentioning an id
Cause
A path value is not well formed — usually a path like my-group/my-repo pasted where an id belongs, or a work-item number that is not a number
What to do
Use the container's id and the item's numeric reference. #412 is written 412 in a URL
400 BAD_REQUESTnaming the type id, a facet key or a category
Cause
A slug broke its rules: it is empty, longer than 40 characters, starts with a hyphen, or contains something other than lowercase letters, digits and hyphens
What to do
Rewrite it as a slug — api-feature, not API Feature. The display name is the field that takes capitals and spaces
400 BAD_REQUESTsaying a type must have a name, or a facet must have a label
Cause
The field is empty or is empty once the spaces are trimmed
What to do
Give it a name. The name is what people pick from the type selector, so it is not optional
400 BAD_REQUESTabout a duplicate facet key
Cause
Two facets in the same type share a key, and the key is the identity a re-apply matches on
What to do
Rename one of the keys. Two facets may share a category and a label; they may never share a key
400 BAD_REQUESTabout the number of facets
Cause
The type declares more than 25 facets
What to do
Cut it down, or split the type in two. A type with more facets than an item can hold criteria is refused the moment it is applied anyway
400 BAD_REQUESTabout a label or hint being too long
Cause
A facet label or hint is over 200 characters
What to do
Shorten it. Detail that does not fit belongs in the item's description, not in a checklist line
400 BAD_REQUESTsaying a bound facet cannot sit on a group-scoped type
Cause
The type is based on epic, which lives only in a group, and a group has no pipelines whose evidence could decide the facet
What to do
Make the facet manual, or base the type on story, bug or task so it lives in a repository
400 BAD_REQUESTnaming a binding argument
Cause
A binding is incomplete or out of range — a test binding with no test, a metric with no measurement, a coverage binding with no path or a percentage outside 0 to 100
What to do
Fill in the binding, or set the facet back to manual and bind it later
400 BAD_REQUESTabout an unrecognised field
Cause
A top-level field of the request body is one the API does not define — often a typo, or a field from an older script
What to do
Send only the documented top-level fields. A misspelt one is refused rather than ignored, so the call fails immediately instead of silently doing half of what you meant
401 UNAUTHORIZED
Cause
No credential was sent, or the one sent is invalid or expired
What to do
Sign in again, or mint a fresh token. Only a public container answers a read with no credential — an internal one refuses until you sign in, and no container answers a write
403 FORBIDDENon defining, changing or deleting a type, where reading works
Cause
Managing types needs Maintainer, because a type reaches every item filed against it
What to do
Ask an Owner or Maintainer to define it once. After that, anybody with Developer access can apply it
403 FORBIDDENon applying a type
Cause
Applying writes acceptance criteria, which needs Developer on the repository
What to do
Ask for Developer access, or ask somebody who has it to apply the type for you
403 INSUFFICIENT_SCOPE
Cause
The token is valid but was minted without the scope this call needs
What to do
Mint a new token carrying the scope named in the response — issue_blueprint:read, issue_blueprint:manage or issue:update. Scopes are fixed when the token is created
404 NOT_FOUNDon a repository or group you are sure exists
Cause
Either it is private and you are not a member, or the id is wrong
What to do
A private container answers 404 rather than 403 so its existence stays private. Check your membership, then check the id
404 NOT_FOUNDon a type that is right there in the picker
Cause
You asked the repository for a type it inherits. Reading, editing and deleting a type all address the container that defines it
What to do
Read it from the group that owns it — the listing tags every entry with where it comes from
404 NOT_FOUNDon apply, naming the type
Cause
The type reference names the wrong container — it has to name the container the type is defined on, not the repository the item lives in — or it names a type this repository does not offer. Only a type defined on the repository itself or on a group above it can be applied, and the same holds for filing a new item as a type
What to do
Take the container from the repository's own listing of available types, then apply again. A type defined on a sibling repository or another team's group is not offered here: define it on a group both repositories sit under
404 NOT_FOUNDon apply, naming the work item
Cause
The item number is wrong, or the item has been moved to another repository or deleted. A move gives it a new number
What to do
Search for the title, then apply against the repository it is in now
409 CONFLICTon defining a type
Cause
A type with that id already exists in this container
What to do
Pick another id, or edit the existing one. Nothing about the existing type is changed by the refused call
409 CONFLICTon saving an existing type, saying somebody else modified it
Cause
Another person saved the type after you read it, and a full replace under a stale version would discard their edit
What to do
Re-read the type, re-apply your change on top of what is there now, and save again with the version that read returned
429 TOO_MANY_REQUESTSon defining a type
Cause
The container is at its limit for types it defines itself
What to do
Delete one nobody files with — archiving hides a type but keeps its slot — or move the definition to a group so every repository shares one slot. An Owner can request a limit increase from the group's usage page
429 TOO_MANY_REQUESTSon apply, mentioning the work item
Cause
The item cannot hold the criteria the type would add — fifteen per item, on every plan
What to do
Remove criteria the item does not need, or split the work: facets that are really separate jobs are better as child items
429 TOO_MANY_REQUESTSon apply, mentioning the repository
Cause
The type carries bound facets, and the repository is at its ceiling for criteria that carry a binding
What to do
Delete bound criteria on closed work in that repository, or set the facet back to manual in the type and bind it on the individual items that need proving. Manual criteria are not counted at all
A type is missing from a repository's picker
Cause
It is archived, it is defined on a sibling group rather than an ancestor, or the listing was read without asking for inherited types
What to do
Ask for the inherited types, check that the group defining it really is above this repository, and check the type is not archived
Two containers define the same id and only one appears
Cause
Nearer definitions shadow further ones, so a repository's own api-feature hides the group's
What to do
Rename one of them if you meant to offer both. If you meant to override locally, that is exactly what is happening
A type was edited and nothing changed on existing items
Cause
Editing a definition never reaches back into work already filed — that is what stops a wording change from rewriting live checklists
What to do
Apply the type again to the items that should follow it. Only the missing facets are added
Applying a type added nothing
Cause
Every facet key it declares is already on the item. Renaming a facet's label keeps its key, so a relabelled facet counts as present
What to do
Read the item's Definition of Done — the criterion is there under its old wording. Change the facet's key if you genuinely want a second one
A facet's checkbox cannot be ticked
Cause
The facet is bound, so its state is a function of the evidence a pipeline reports, not of anybody's opinion
What to do
Run the pipeline that produces the evidence, or change the criterion's binding on the item to make it manual
A type was applied and the item still reads as its base type
Cause
The facets are what a blueprint gives an item. Applying seeds them as acceptance criteria and changes nothing else — not the item's type, not its fields, not the children it may hold
What to do
Read the Definition of Done, which is where the change landed. Change the item's type separately if you need the structure to move
A facet did not stop an item from closing
Cause
Either the facet is not required — optional facets show and roll up but do not gate — or no gate is switched on. Both gates are off by default
What to do
Save the type with that facet required and apply it to the items that should be gated; existing criteria keep the setting they were seeded with. Then require verified criteria before a close in the container's work-item settings, and require green criteria before a merge in its merge request settings
A saved type came back out of the archive
Cause
A save is a full replace, and the request left the archived field out, so it was cleared
What to do
Read the type, set the archived field on the body you send back, and save again
A bound facet stays unchecked after the work is done
Cause
No pipeline run has reported evidence that matches the binding — most often the binding names a test id, a measurement or a node that the run reports under a different name
What to do
Run the pipeline on the branch, then compare the name in the binding against the name in the run's report and fix whichever is wrong

Examples

A type for an endpoint-shaped story, with three required facets and one optional one, and the test facet bound so a pipeline decides it. This is the body of a create call against a repository or a group:

json
{
"blueprint_id": "api-feature",
"name": "API Feature",
"base_type": "story",
"description": "A new endpoint, and everything that ships alongside it.",
"facets": [
{ "key": "endpoint", "label": "Endpoint", "category": "interface", "hint": "The handler, its request and response shapes, and the access rule it maps to." },
{ "key": "tests", "label": "Tests", "category": "quality", "binding": { "mode": "bound", "binding": { "kind": "test", "suite": "api", "test_id": "refund_endpoint_rejects_double_refund" } } },
{ "key": "observability", "label": "Observability", "category": "operability", "hint": "The endpoint emits a request metric and an error metric." },
{ "key": "docs", "label": "Documentation", "category": "docs", "required": false }
]
}

Applying it to work already filed. Replace <group-id> with the id of the group the type is defined on — the listing tags every entry with the container it came from, and that is the container to name here even when you are applying to a repository's item:

json
{
"blueprint": {
"scope": { "group": "<group-id>" },
"blueprint_id": "api-feature"
}
}

A type defined on a repository is named the same way, with repo in place of group and the repository's id. The item itself is addressed by the path, so the repository in the path and the container in the body are different things and are meant to be.

Later, the team decides documentation is not optional and adds a fifth facet. The update is a full replace, so send every facet you are keeping, with the same keys, plus the version you last read — then apply the type again to the items that should follow it.

FAQ

Do inherited types count against my limit?

No. The limit counts the types a container defines itself, so a type defined once on a root group and offered to fifty repositories costs one slot, not fifty.

Can I change a type's base type?

Yes, on any save. It changes what new items filed with that type become; items already filed keep the base type they were created with, because retyping live work is a separate decision with its own rules. Moving the base type between a group type and a repository type also moves where the type can be filed, so check that the containers using it can still take it.

Can a facet become its own work item?

Not in one action. When a facet is really a chunk of work with its own owner and its own discussion, file a sub-task for it and let the parent's criteria track the rest.

Does a custom type change how items are listed, filtered or rolled up?

No. Every listing, board, filter and rollup keys on the base type, so an item filed as "API Feature" appears everywhere a story appears. The shared vocabulary and the seeded facets are what you gain; nothing about the item's structure is traded for them.

Can I stop people filing with a type without losing it?

Archive it. It leaves the picker, keeps every facet and binding you tuned, and comes back the moment you save it unarchived. It is a signal rather than a lock, though — a request that names the id still works — so delete the type when the id has to stop working. An archived type also holds its slot against the per-container limit while it sits there.

  • Proof of Doneacceptance criteria, what a binding can prove, and the merge gate that reads them
  • Work itemsfiling an item, the eight base types, and what each one may contain
  • Linking work to codeconnecting an item to the commits, merge requests and lines that satisfy it
  • Merge request settingsthe gate that holds a merge until every required criterion on a linked item is green
  • Issuesthe rest of the work tracker, and where blueprints sit in it
  • Billing and planswhat each plan includes, and how to ask for a limit increase