Skip to content
GitmoapdocsBeta
Guide· How do I…?

Service catalog

Overview

The catalog answers the four questions nobody can answer from a repository list: what do we run, who owns it, where is it deployed, and is it any good? A service is a named unit of software mapped onto a set of repository paths, so one repository can hold five services and one service can span three repositories. Ownership is a real group — the one a failing check files work into, and the one that stands in as code owner wherever a repository requires code-owner review and no CODEOWNERS rule matches the path — so setting it does something rather than describing something.

You do not start from an empty page. Every repository already appears as a service, created with the repository itself, so your first job is splitting and correcting rather than filling in a form.

Quick start

Quick start2 steps

Declare a service

You’ll need Developer on the repository, and a group that owns the service.
  1. Add a .gitmoap/catalog.yaml to the repository, naming the service and the group that owns it:

    services:
      - slug: checkout
        owner: acme/platform
  2. Merge it to the default branch. The service appears under Catalog, and its deployments are tracked from then on.

Quick start3 steps

Declare an environment

You’ll need Maintainer on the group or the repository.
  1. Choose Catalog, then Environments, then New environment.

  2. Give it a slug and a tier. At the production tier it arrives with approval required and deploys limited to main.

  3. Choose Create environment. A pipeline step targets it by its slug.

Quick start3 steps

Grade your services with a scorecard

You’ll need Maintainer on the group.
  1. Choose Catalog, then Scorecards, then New scorecard.

  2. Pick which services it grades, and add the checks they must pass.

  3. Choose Create scorecard. Every matching service in this group and beneath it is scored automatically.

Before you start

  • A group, and a repository or two.The catalog fills itself from repositories you already have. Creating a repository creates one service covering all of it.
  • Reporter access to read.A service inherits the visibility of the group that declares it: a public group's services answer anyone, even signed out, an internal group's answer anyone signed in, a private group's answer only its members — with 404 rather than 403, so nothing leaks by existing. Scorecards and dependency edges follow the same rule. Environments, their variables and every deployment read take no such shortcut and are the exception worth remembering: they need Reporter on the owning scope whatever the group's visibility, so somebody who can browse a public group's services still gets 403 on its environments.
  • Maintainer access to declare or edita service, an environment or a scorecard, on the group or repository that owns it — and to attest a check.
  • Owner access to delete an environment, to create or change a production-tier environment, to change protection or tier at any tier, to write or delete an environment variable, and to arm a scorecard campaign. The privilege worth guarding is not deploying to production — it is removing the requirement to be approved, and then deploying entirely within your own rights. Deleting a service or a scorecard stays at Maintainer, the same level that declared it.
  • Maintainer on the group you name as owner.Naming a team as a service's owner authorizes two things: the service, and the team. Without that second check, anyone who can edit a service could make another team responsible for its outages and fill their backlog while doing it.
  • A token with the right scope, if you are calling the API or the CLI: catalog:read to read anything on this page, catalog:manage to write anything. Scopes are chosen when you mint a token and cannot be added afterwards.
  • A pipeline, for the deployment half.Deployments are recorded from pipeline runs whose nodes name a service and an environment. No endpoint creates a deployment by hand.

How it works

A service is an overlay, not a repository

A service binds to a set of (repository, path glob) pairs. That single decision is what makes a monorepo native instead of an apology.

repository: acme/platform services
├── services/checkout/** ─────────────────▶ checkout (owner: acme/payments)
├── services/inventory/** ─────────────────▶ inventory (owner: acme/supply)
├── libs/money/** ─────────────────▶ money (owner: acme/payments, kind: library)
└── ** ─────────────────▶ (nothing — the wide binding was split away)
repository: acme/checkout-worker
└── ** ─────────────────▶ checkout ← the same service, second repository

Four things fall out of the binding and each would otherwise be its own feature: per-service ownership, per-service technical debt, pipeline nodes that run only when their own folder changed, and a deploy that knows which of five services it deployed.

When two globs match one path, the winner is decided in a fixed order — longest literal prefix first, then the glob with fewer wildcards, then a stable tie-break. An exclude glob removes its own matches before any of that, so paths: ["services/checkout/**"] with exclude: ["**/vendor/**"] does what it reads like. The order is fixed rather than resolved by row order because a service's owner can gate a merge, and a gate that flips depending on which row was read first is not a gate.

Identity, address and where an entry came from

A service's id never changes. The slug is the address, it changes, and it is unique across your whole organisation rather than per group — a depends_on: entry in another repository's catalog file, a pipeline node's service: and the by-slug lookup all need payments-api to resolve without a path in front of it. The cost is real: at a couple of hundred services you will occasionally have to call something payments-api-v2.

Every entry records how it came to exist, and that is visible on purpose:

SourceCreated byWhat it means
inferredthe repository's own creationnobody has vouched for this entry yet — it covers the whole repository
declareda .gitmoap/catalog.yaml in the repositorya file in git is the source of truth for it
apisomebody called the API, the CLI or the web appa person entered it directly

A human has vouched for this is simply source not being inferred, which is a scorecard check you can turn on rather than a banner nobody dismisses.

A declaration is a refinement, and it never destroys history

.gitmoap/catalog.yaml — or the same path with a .yml extension, either works — on the default branch splits and corrects the catalog. An entry needs only a slug. kind defaults to service, lifecycle to production, display_name to the slug, and paths to ** when the file declares exactly one service — declare two and every entry must name its own paths. owner may be left out too, and the entry is then unowned, which is exactly what the has an owner check exists to surface, so name a team while you are there:

yaml
services:
- slug: checkout
kind: service
owner: acme/payments
paths: ["services/checkout/**"]
exclude: ["**/vendor/**"]

Exactly one of the two extensions may exist in a repository; both at once is a validation error naming them. The file is validated on every branch and applied only on the default one, and the result is posted as a gitmoap/catalog commit status — so a broken declaration is a red check inside the merge request, seen by the person who caused it, before it lands. A file with any error applies none of its services.

A file of forty near-identical entries repeats itself, and a defaults: block above services: is the remedy — kind, lifecycle, owner, exclude and annotations set there apply to every entry that does not state its own. It is the reuse mechanism on purpose: YAML anchors would not survive a change of format, and this one round-trips.

The first catalog file in a repository does not throw away what the catalog already knew. If exactly one declared service covers the whole repository — paths: ["**"], or no paths at all — it adopts the entry created with the repository: same id, same deployments, same check history, now marked as declared. Declare two or more, or narrow the paths, and you are splitting: the declared services are new entries and the original is orphaned rather than deleted, so its history stays readable while you decide what to do with it.

When a declaration disappears the service is orphaned, never deleted: nothing attached to it is touched. Most disappearances are a bad merge or a refactor in flight, and reaping a deploy history over one would destroy the record rather than tidy it. Bring the declaration back and the same service is revived, with its id, its deployments and its check history intact. Deleting is an explicit act by somebody who can see what they are destroying.

Environments are one row, resolved by a walk up the tree

An environment is a named place software runs, carrying the policy that governs deploying to it. It is exactly one row, owned by exactly one scope. Declaring production on your root group does not copy it into 400 repositories — it makes one row that every descendant may reference.

resolving environment: production from acme/platform/checkout
1. an environment named `production` on the repository itself → use it
2. else the nearest ancestor group that declares one → use it
3. else the run fails to resolve, before any node starts

A nearer slug shadows a farther one, so a repository can have its own staging without disturbing the organisation's. slug is whatever you call it — prod, live, eu-west-1-blue — while tier is one of development, testing, staging or production, and the tier is what lets a check say deployed to production within 30 days without knowing your naming.

A production-tier environment is gated by default. Create one with no protection block and you get approval required, the people given access on the owning group named as its approvers, and deploys restricted to one branch — the repository's own default branch when you declare it on a repository, and main when you declare it on a group, since a group has no default branch to inherit. Turning any of that off is a deliberate edit, because a feature whose safe configuration requires reading the documentation is unsafe in the median case.

allowed_scopes is the other half, and it is an access rule rather than an approval rule: leave it unset and the declaring scope's subtree may use the environment; set it and only the groups and repositories you name may. Protection gates approval; this gates reach.

Deployments are recorded, never entered

Every deployment is an edge from a pipeline run to a (service, environment) at a commit, written by the platform when a node that named both starts and finishes.

Requested ──gate──▶ AwaitingApproval ──approve──▶ Running ──▶ Succeeded ──▶ Superseded
│ │ ├──▶ Failed
│ └──reject──▶ Rejected └──▶ Cancelled
└──concurrency policy = skip──▶ Skipped

Two consequences worth knowing. A rollback is a new deployment, not a status change on the old one — redeploying v2.13.0 over v2.14.0 writes a fresh row and marks the previous one superseded, so the history and the current pointer never disagree about what was true at 14:00. And a deployment outlives its run: reaping pipeline history does not erase the record of what went to production.

Checks come in three kinds

KindWhere the answer comes fromHow freshExample
Deriveda query over the platform's own recordsexact, alwayshas_owner, branch_protection, released_within, deployed_within
Evidencea report a pipeline node produced at a named commitas of the last run that produced ita coverage threshold, a smoke test, a latency metric
Attesteda person, with an expiryuntil it expiresa disaster-recovery runbook exists and was rehearsed

Each result is one of six states, and three of them exist to stop a scorecard lying. not_applicable leaves the denominator entirely — a library has no deployments, so marking it red teaches people to ignore red. unknown means never evaluated; stale means the thing that used to prove this ran again and no longer proves it; expired is an attestation whose renewal window has passed.

Scorecards union down the tree — a service is graded by every scorecard in its ancestor chain whose selector matches it, and a subgroup cannot switch off an organisation-wide baseline.

Dependency edges are observed, not asserted

Because the platform runs the npm, Cargo and PyPI registries, an edge is discovered rather than declared: when one service publishes acme-auth and another's lockfile resolves it, that edge names the real lockfile, the real commit and the real resolved version. A service declares what it provides; the lockfile says who consumes it. Two lockfile formats are read — Cargo.lock and package-lock.json — anywhere in the tree, so a monorepo's services/checkout/Cargo.lock counts exactly as a root one does. Consumption in any other ecosystem has to be declared, in depends_on:.

A declared edge and an observed edge for the same pair coexist, and their disagreement is the finding — you declare a dependency no lockfile mentions, or your lockfile depends on something your declaration does not admit. Both are checks you can turn on.

Anything the platform does not build — a vendor API, a managed database, a team on another forge — is an external entity: named, owned, and pointed at by edges, with nothing else asserted about it. External entities never enter a score silently; every aggregate reports them as a separate unscored count.

How to

See what a repository already contains

Every repository starts life with one service, minted alongside it and covering all of it. Start here before declaring anything, because the entry you want to correct probably already exists.

Open the repository and choose Catalog in the side navigation. Each row is a service bound to this repository, with its kind, lifecycle, owner and a promotion track showing how far the latest build has got — one stop per tier, from development through to production. Filter by kind, lifecycle or source, or type in the search box to match a name, a description or part of a slug. Entries whose declaration has disappeared are hidden until you tick Include orphaned.

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

Each row carries two facts per tier and they answer different questions: deployed is how far has this got, read from what is serving now, and status is is anything wrong, read from the latest attempt. A tier showing deployed with a failed status means the previous version is still serving and the attempt to replace it failed — which no single indicator can say.

A tier is not an environment. If production holds prod-eu, prod-us and prod-ap, the row still draws one stop: status is the worst of the three and deployed is true if any of them is serving. Open the service to see them apart.

Browse a group's services

Open the group and choose Catalog. The list is alphabetical by slug and carries the same filters and promotion track as the repository view, across every service the group declares. Selecting one opens its overview.

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

If you already know the slug, fetch it directly rather than paging — slugs are unique across the organisation, so any group in it resolves the same name.

Type the slug into the catalog search box; an exact slug match is pinned to the top of the results.

Terminalbash
gitmoap catalog get-service-by-slug --group-id <id> --slug <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/catalog/services/by-slug/{slug} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Open a service

The overview is one call that returns everything a service page shows: its repositories and path globs, its owner, its latest release and its latest pipeline run, how many merge requests are open against it, its technical-debt rollup by path, what is deployed where, its scorecard verdicts, and how many dependency edges point each way.

Open the service from either catalog listing. The header carries the owner, kind and lifecycle; the panels below show repositories and paths, deployments by environment, scorecards with their level, dependencies in both directions, and the open bugs and markers found in the service's own paths.

Terminalbash
gitmoap catalog get-service-overview --service-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/services/{service_id}/overview \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Everything on the overview is authorized per far end rather than per page. A service in a public group may bind paths in a private repository, and a reader who cannot see that repository gets a service with an empty repository list — not a 403, and not a hint that the repository exists.

Use the plain read when you want the entry itself without the derived panels.

The overview page's Details tab shows the stored entry — slug, kind, lifecycle, owner, links, annotations, published packages and API specs.

Terminalbash
gitmoap catalog get-service --service-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/services/{service_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Check which service owns a path

This is the question everything else rests on, and it takes up to 200 paths in one call so a client resolving a changed file set makes one request rather than one per file.

Open the repository's Catalog tab and use Resolve path. Paste one or more repository-relative paths; each answer shows the winning service and, where more than one glob matched, the others in resolution order so you can see the ambiguity rather than guess at it.

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

An answer with entries under overlapping means two declarations claim the same path. The winner is stable, but the overlap is worth fixing — narrow one of the globs, or add an exclude.

Declare a service

Most of the time you will declare services in .gitmoap/catalog.yaml, so the split lives in git beside the code it describes and arrives through a merge request. Declare one through the API when you are scripting a migration, or when the entry is an external entity with no code behind it.

Open the group, choose Catalog, then New service. Give it a slug, pick a kind and lifecycle, choose an owning team, and add one or more path bindings — a repository plus a glob, with optional exclusions. External entity hides the bindings and is for something the platform does not build.

Terminalbash
gitmoap catalog create-service --group-id <id> --slug <slug>
curlbash
curl -X POST https://api.gitmoap.example/groups/{group_id}/catalog/services \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "api_specs[].path": "<string>", "api_specs[].repository_id": "<uuid>", "api_specs[].spec_kind": "<one of asyncapi | grpc | openapi>", "links[].link_type": "<one of chat | dashboard | docs | other | runbook>", "links[].url": "<string>", "provides[].format": "<one of cargo | npm | pypi>", "provides[].name": "<string>", "repo_paths[].path_glob": "<string>", "repo_paths[].repository_id": "<uuid>", "slug": "<string>" }'

Three rules are enforced at write time and are worth knowing before you hit them. A service that is not external must bind at least one path that includes something — exclusions alone are not a binding — and an external one must not bind any: inferring externality from a missing binding was rejected because a typo in a glob would then quietly turn every check not applicable, which reads as passing. Naming an owner group requires Maintainer standing on that group as well as on the declaring one, and naming an owner team requires that you maintain it or manage your organisation's teams. And binding a repository requires Maintainer on that repository too, so nobody can attach another team's code to a service they own; a repository you cannot write to answers 404 rather than 403, whether or not you can read it.

An owner may be a team — in the catalog file, owner: team:backend, a team of your organisation by its slug — and then the service is owned by whoever is on the team, and a code-owner approval from any of them counts. An owner may also be a person — owner: user:sam — and it satisfies the has an owner check. Prefer a group or a team anyway: a person is not a backlog, so a campaign filing work against a person-owned service falls back to the group the service is declared in, and every ownership rollup is a group-by over teams.

Correct a service

Renaming a slug, changing the owner, moving a lifecycle to deprecated, adding a runbook link — all of it is one partial update. Supply the version you read to make the write refuse rather than silently overwrite somebody else's edit.

Open the service and choose Edit. Everything is editable except three things the platform owns: the service's id, the group it is declared in, and how the entry came to exist. Saving with a stale copy open shows a conflict rather than overwriting.

Terminalbash
gitmoap catalog update-service --service-id <id>
curlbash
curl -X PATCH https://api.gitmoap.example/catalog/services/{service_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "api_specs[].path": "<string>", "api_specs[].repository_id": "<uuid>", "api_specs[].spec_kind": "<one of asyncapi | grpc | openapi>", "links[].link_type": "<one of chat | dashboard | docs | other | runbook>", "links[].url": "<string>", "provides[].format": "<one of cargo | npm | pypi>", "provides[].name": "<string>", "repo_paths[].path_glob": "<string>", "repo_paths[].repository_id": "<uuid>" }'

Renaming a slug through this endpoint is deliberate and safe: everything that points at the service points at its id, so deployments, check results and dependency edges survive. Renaming a slug inside a catalog file is not the same act — it reads as a delete plus a create, because a slug is a cross-repository identifier other services resolve against.

Delete a service

Warning

Deleting a service destroys its deployment history, every scorecard result and transition recorded against it, and every dependency edge that points at or away from it. None of that is recoverable, and rebuilding it means re-running the pipelines that produced it. Work items a campaign filed against it are the one thing that survives — they are somebody's backlog, so they are left behind rather than reaped, and they will name a service that no longer exists. If what you want is to stop maintaining an entry, set its lifecycle to retired, or remove its declaration from the catalog file — which orphans the entry and keeps everything attached to it.

Open the service, choose Settings, then Delete service. The confirmation names what goes with it — the deployment count, the scorecard results and the edges. Deleting requires Owner on the declaring group.

Terminalbash
gitmoap catalog delete-service --service-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/catalog/services/{service_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Deleting a repository does something narrower and deliberately different: an auto-created service bound only to that repository is deleted with it, while a declared service is orphaned so its history survives.

Declare an environment

Declare it on the group when several repositories deploy to the same place — which is almost always right for production — and on a repository when the place belongs to that repository alone.

Open the group, choose Catalog then Environments, and New environment. Give it a slug, pick a tier, and optionally a display name and a URL. The protection panel opens preconfigured for the tier you chose; at production tier it arrives with approval required, the owning group's people as approvers, and deploys restricted to main.

Terminalbash
gitmoap catalog create-group-environment --group-id <id> --slug <slug>
curlbash
curl -X POST https://api.gitmoap.example/groups/{group_id}/catalog/environments \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "slug": "<string>" }'

The same form is on the repository's Catalog → Environments tab. A repository-owned environment shadows an ancestor group's environment of the same name for that repository only.

Terminalbash
gitmoap catalog create-repo-environment --repo-id <id> --slug <slug>
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/catalog/environments \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "slug": "<string>" }'

Two refusals to expect. Requiring approval with no approvers is refused at create time — a gate nobody can open is a deploy that can never happen, and 02:00 during an incident is the worst possible time to learn that. And a Maintainer cannot create a production-tier environment at all; that one is Owner.

List the environments a repository can deploy to

Open the repository's Catalog → Environments tab. By default it lists the environments this repository owns. Tick Include inherited to see everything it may reference, with each row showing the scope that owns it — which is the difference between declared here and declared four levels up. Where a repository and an ancestor both use a slug, only the nearer one is shown, exactly as a pipeline would resolve it.

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

The group's Catalog → Environments tab is the same list one level up, with the same filters: tier, a search over the slug and display name, Include inherited for the ancestor groups' rows, and Include archived for the ones retired from resolution.

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

Selecting any environment row opens it — slug, tier, owning scope, URL, protection, concurrency policy, variable count and the version you need to edit it safely.

Terminalbash
gitmoap catalog get-environment --environment-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/environments/{environment_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Change an environment's protection

Open the environment and choose Edit. Protection sets who may approve, whether the person who triggered the run may approve their own deploy, how long to wait after approval before starting, which refs may deploy, and which scopes may reference the environment at all. Concurrency sets whether deploys serialise, and what happens to a run that arrives while another holds the lock — queue, skip, or cancel the one in flight.

Terminalbash
gitmoap catalog update-environment --environment-id <id>
curlbash
curl -X PATCH https://api.gitmoap.example/catalog/environments/{environment_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Owner is required to change protection, to change the tier, or to change anything at all on a production-tier environment. The last of those exists so an Owner-only environment cannot be reshaped one Maintainer-level field at a time.

Who may approve is a list of approvers, each a person, a team, or a role at a group or repository — everyone holding at least that role there. With direct_only, a role counts only people given access on that group or repository itself, not those who inherit it from a group above, so an organisation Owner does not walk through every team's gate. required is how many different approvers must approve before the deploy starts, and accept_from says which approvals count: people_signed_in by default — a person approving in the web app, or in the CLI after gitmoap login — or people, which also counts a personal access token, or people_and_service_accounts. <team-id> and <group-id> below are ids from your organisation:

json
{
"protection": {
"requires_approval": true,
"approvers": [
{ "team": "<team-id>" },
{ "role": { "at": { "group": "<group-id>" }, "min": "maintainer", "direct_only": true } }
],
"required": 2,
"accept_from": "people_signed_in",
"prevent_self_approval": true,
"allowed_refs": ["main"]
}
}

A deployment's approvers are always named: anyone and code owners, which an approval rule on a merge request may use, are refused here. A pipeline step deploying to the environment may narrow these approvers, never add to them.

The wait timer starts when the gate clears, not when the run reaches it. Approved, deploying in ten minutes, cancel if you spot something is what the pause is for, and a timer that had been running while an approver deliberated would already have expired by the time they pressed the button.

Give an environment its variables and secrets

Variables reach a pipeline step as environment variables. A non-secret's value is stored where the API can read it back; a secret's is stored where no read can return it, and is redacted from the step's logs.

Open the environment and choose Variables, then Add variable. Names are shell-shaped — letters, digits and underscores, not starting with a digit — because they become environment variables inside the step. Tick Secret to store the value out of reach; leaving it unticked gives you a variable whose value is visible, which you will notice, rather than one you believe is secret and is not.

Terminalbash
gitmoap catalog put-environment-variable \
--environment-id <id> \
--name <id> \
--value <value>
curlbash
curl -X PUT https://api.gitmoap.example/catalog/environments/{environment_id}/variables/{name} \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "value": "<string>" }'

The Variables tab lists every variable in name order, with who set it and when. A secret shows no value — not masked, absent, because nothing holds one to show. Reading the list is Reporter: the names are not the secret, the values are, and only writes and deletes go into the audit log.

Terminalbash
gitmoap catalog list-environment-variables --environment-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/environments/{environment_id}/variables \
-H "Authorization: Bearer $GITMOAP_TOKEN"

A handful of names are refused, because a variable able to set them would point a platform-signed token at a role of your choosing: anything beginning GITMOAP_, anything beginning AWS_CONTAINER_CREDENTIALS_, and AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_WEB_IDENTITY_TOKEN_FILE and AWS_REGION. Set a region or a role on the provider connection instead, where it is authorized.

Where a name is set both on a provider connection and on the environment, the environment wins — the more specific place is the more deliberate one. What is refused rather than resolved is a collision that changes a name's secrecy: a connection secret shadowed by a plain environment value would move a credential out of the redaction list, so the run fails at resolution naming both sources. The same holds for a group or repository pipeline variable that shares a name with an environment variable and differs from it in secrecy.

A step's configuration can read a non-secret variable of the environment it deploys to as well, by writing {"$var": "WEB_S3_BUCKET"} where the value belongs, so a bucket or an endpoint declared here once serves every pipeline that deploys here. A secret cannot be read that way, because configuration is not redacted from logs. See Read a variable in a step's configuration.

Warning

Deleting a variable is immediate and the value is gone — a secret's especially, since nothing else holds a copy. Any pipeline node that expected it runs without it on the next attempt, which usually surfaces as a failure inside your own build rather than a message from the platform.

On the Variables tab, choose the bin icon at the end of a row. The confirmation names the variable and whether it is a secret. Writing and deleting variables both require Owner.

Terminalbash
gitmoap catalog delete-environment-variable \
--environment-id <id> \
--name <id>
curlbash
curl -X DELETE https://api.gitmoap.example/catalog/environments/{environment_id}/variables/{name} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Build with an environment's settings

A web app's bundle usually carries its settings inside it: the API address, the sign-in client, each VITE_* or NEXT_PUBLIC_* value is baked in when the app is built. So the step that runs the build needs the environment's values, and it must not be the step that deploys there. Set variables_from on the build step instead of environment:

yaml
- name: build-web
capability: node:build
variables_from: production
- name: deploy-web
capability: aws:s3-sync
environment: production

The build receives the environment's non-secret variables as environment variables, and its configuration can read them with {"$var": …}. It gets nothing else from the environment: no approval wait, no lock, no secrets and no OIDC identity, and finishing it records no deployment. A secret is given only to a step that deploys there, so the build's log says how many of the environment's secrets it did not receive. A build-time secret, such as a token for uploading source maps, belongs on the step's connection or in a pipeline variable.

Two things are refused when you save the pipeline, and again when a run starts:

  • One step naming both. A step that deploys already reads its own environment's variables.
  • Shipping one environment's build to another. A deploy whose input was built with a different environment's variables — staging's settings bound for production — is refused, naming both steps. Build once per environment, each with that environment's variables. A build that reads no environment produces something you can deploy anywhere.

allowed_scopes applies to variables_from as it does to a deploy, because reading an environment's settings is using it. allowed_refs does not: a step that only reads variables reaches nothing, so a feature branch can build with production's settings to preview them.

Retire an environment

Warning

Deleting an environment is refused once anything has been deployed to it, and that refusal is the feature: deployment history is the record of what was in production, and a delete must not be a way to erase it. Deleting an environment that has no deployments also destroys its variables — the plain values and the secrets both, permanently.

Archiving is the reversible operation. An archived environment leaves resolution — no new run can target it — while every deployment recorded against it stays readable. Archive with the ordinary edit; delete only an environment created by mistake.

Open the environment, choose Settings, then Delete environment. If anything has ever deployed here the button offers Archive instead, and says why. Deleting requires Owner.

Terminalbash
gitmoap catalog delete-environment --environment-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/catalog/environments/{environment_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

See what is deployed

The service overview's Deployments panel shows one entry per environment — the commit, when it landed and the run that put it there. This is a direct lookup rather than a scan of history, so it is the read to hang an incident page on.

Terminalbash
gitmoap catalog get-service-current-deployments --service-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/services/{service_id}/deployments/current \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Choose History on the same panel for every attempt, newest first, filtered by environment or by status. Each entry carries who triggered it, who approved it, the commit and ref, the attempt number and, on a failure, the summary.

Terminalbash
gitmoap catalog list-service-deployments --service-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/services/{service_id}/deployments \
-H "Authorization: Bearer $GITMOAP_TOKEN"

From an environment, choose Deployments to invert the question: what is in this place, across every service. One row per service, most recently deployed first. It has no cursor — it is bounded by limit alone, which defaults to 20 and goes up to 100, so raise it when an environment holds more services than a page shows.

Terminalbash
gitmoap catalog list-environment-deployments --environment-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/environments/{environment_id}/deployments \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Read the deployment board

The board is services down the side, environments across the top, the current deployment in each cell — the page people actually leave open.

Open the group's Catalog → Deployments tab. Choose which environments form the columns, or narrow them to one tier. Search matches a service's name, description or slug. An empty cell means nothing has deployed there, which is a different fact from a failure, and it is rendered differently.

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

The rows are the services this group declares, not its whole subtree. Open a subgroup's board to see that subgroup's services; the group you opened is the one that decides the rows.

Columns are read with inheritance, so a column can be an ancestor group's environment — which is the usual case, since most repositories deploy to a production declared far above them. A nearer slug shadows a farther one, archived environments are left out, and an ancestor's environment you have no access to is dropped rather than shown empty.

Every environment you can see is a column, on every page, in slug order. Paging moves through the services, never the columns. A board that would draw more than 500 columns is refused with 400 rather than drawn short: choose the environments you want or narrow to a tier.

The board deliberately does not carry drift. Working out how far behind each cell is costs a git walk per service, environment and repository, and a forty-by-four board would be 160 of them on a page nobody closes. Ask for drift one service at a time.

Find out how far behind a deployment is

Open the service and choose Drift on the deployments panel. Each row is one (environment, repository) pair with a current deployment, naming the environment, the repository and its default branch, the commit that is deployed, and how many commits behind that branch it is.

Terminalbash
gitmoap catalog get-service-deployment-drift --service-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/services/{service_id}/deployments/drift \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Drift is reported per repository, never averaged. A service spanning three repositories has three answers and they are rendered as three rows, because a single number combining them is confidently wrong and a wrong number gets acted on. A row shows no count when the deployed commit can no longer be read — after a force-push, or a repository move — rather than failing the whole read.

Trace what a service depends on

Open the service and choose Dependencies. Each edge names the far service, what kind of coupling it is, and where the claim came from — a lockfile at a named commit, or somebody's declaration. An observed edge also carries the version the manifest asked for and the version the lockfile actually pinned.

Terminalbash
gitmoap catalog list-service-dependencies --service-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/services/{service_id}/dependencies \
-H "Authorization: Bearer $GITMOAP_TOKEN"

The Dependents tab is the reverse, and it is the question people actually ask during an upgrade: who is on the old version of this?

Terminalbash
gitmoap catalog list-service-dependents --service-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/services/{service_id}/dependents \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Graph walks outward from the service, up to three hops, and lays the result out in rings by distance. Depth defaults to two.

Terminalbash
gitmoap catalog get-service-graph --service-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/services/{service_id}/graph \
-H "Authorization: Bearer $GITMOAP_TOKEN"

All three reads drop edges whose far end you cannot see and report how many they dropped, so a partial answer says so rather than looking complete. They are bounded rather than paged; each says whether its bound was reached.

See what checks exist before writing a scorecard

The scorecard editor's check picker is built from this registry. Each entry gives the check's name, what it reads, what makes it re-evaluate, whether it also re-runs on a schedule, and a form for its own parameters — a window in days for released recently, a tier and a window for deployed recently.

Terminalbash
gitmoap catalog list-check-kinds
curlbash
curl -X GET https://api.gitmoap.example/catalog/check-kinds \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Thirteen derived checks ship: has_owner, human_declared, has_repository, has_pipeline, branch_protection, requires_review, released_within, deployed_within, has_runbook, has_api_spec, declared_dep_unobserved, observed_dep_undeclared and dependency_freshness. released_within takes a window in days, defaulting to 90; deployed_within takes a window defaulting to 30 days and a tier defaulting to production.

Applicability is decided by the facts a service has. Each check names what it reads — a binding, a repository, a deployment, a release, an interface — and a service with none of them resolves not applicable rather than failing, which is why a library the platform has never recorded a deployment for is never marked red for having none. The one check that reads kind rather than a fact is the API-spec check: an api or a service is expected to publish an interface description and a datastore is not. Everywhere else, kind decides which scorecards select a service; it does not excuse a check from one that already selected it.

Create a scorecard

Open the group, choose Catalog → Scorecards, then New scorecard. Give it a slug and a name, then a selector deciding which services it grades — by kind, by lifecycle, by annotation, or everything. Add checks from the registry, or bind one to evidence a pipeline produces, or make one attested with a renewal window. Levels are optional: name a rung and list the checks it requires.

Terminalbash
gitmoap catalog create-scorecard --group-id <id> --slug <slug>
curlbash
curl -X POST https://api.gitmoap.example/groups/{group_id}/catalog/scorecards \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "checks[].key": "<string>", "checks[].rule": "<object>", "levels[].name": "<string>", "slug": "<string>" }'

Levels are a ladder, not a set of badges, so list them in the order you want them climbed. Grading walks them from the first and stops at the first rung whose required checks are not satisfied — a service cannot skip one, so nothing is ever graded Gold that never reached Bronze. A check counts as satisfied when it is passing or not applicable, which is what lets a library reach the top rung of a scorecard that includes a deployment check; a required check that has never been evaluated counts as unknown and does not satisfy.

A scorecard with no levels is a checklist, which is what most of them are.

Read a scorecard's results

Open the scorecard and read the results table. It opens worst-first, because the passing rows are not what you opened it for. Filter to one state, one check or one service. Beside the rows is the partition: how many services were scored and how they distribute across the levels, and how many were unscored with the reason — external entities live here, counted apart rather than folded into a percentage.

Terminalbash
gitmoap catalog list-scorecard-results --scorecard-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/scorecards/{scorecard_id}/results \
-H "Authorization: Bearer $GITMOAP_TOKEN"

The partition stays scorecard-wide whatever you filter by. Narrow to one service and the rows are that service's, but the counts beside them are still every service the scorecard grades — so read a per-service tally off the rows, not off the summary.

The group's Scorecards tab lists every scorecard that applies here, including those inherited from ancestor groups. Untick Include inherited to see only this group's own, or tick Enabled only to hide the ones switched off.

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

Selecting a scorecard opens its definition — selector, checks, levels, campaign switches and the version you need to amend it safely.

Terminalbash
gitmoap catalog get-scorecard --scorecard-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/scorecards/{scorecard_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

From the other direction, a service's Scorecards panel shows one row per scorecard that applies to it, with the level it reaches and its counts by state.

Terminalbash
gitmoap catalog get-service-scorecards --service-id <id>
curlbash
curl -X GET https://api.gitmoap.example/catalog/services/{service_id}/scorecards \
-H "Authorization: Bearer $GITMOAP_TOKEN"

A result marked flapping means the check has changed state ten times today and the platform has stopped recording its edges. Read it as this check is not trustworthy — usually a threshold set at the noise floor, or a test that is not deterministic — and fix the check rather than the service.

Amend a scorecard, and run a campaign

Open the scorecard and choose Edit. Adding, removing and reconfiguring checks, editing levels and changing the selector are all Maintainer. Arming the campaign, and raising how many work items it may file, are Owner.

Terminalbash
gitmoap catalog update-scorecard --scorecard-id <id>
curlbash
curl -X PATCH https://api.gitmoap.example/catalog/scorecards/{scorecard_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "checks[].key": "<string>", "checks[].rule": "<object>", "levels[].name": "<string>" }'

A campaign turns red squares into work: a failing check whose Create work item box is ticked opens an item in the owning group's backlog, carrying a criterion bound to the check — so the item closes itself when the check passes and reopens if it regresses.

It is gated three times, and the gating is the design rather than caution. Work-item creation is off per check. The campaign is off per scorecard, so the whole set is inert until somebody switches it on. And each activation may file at most a set number of items — counted from zero each time the switch moves off to on, so a campaign paused and resumed next quarter does not start already exhausted. Four hundred services times twelve checks is 4,800 work items in one pass, which is the single most likely way this feature gets switched off across an organisation in week one.

Removing a check that a level still requires is refused, because the rung would be unreachable for ever.

Attest a check no machine can see

Some true statements about software are not measurable. We rehearsed the failover is one, and a system that demanded a metric for it would simply get nothing recorded.

Open the service's scorecard panel, find the attested check and choose Attest. Record it as passing or failing and add a note. The attestation expires after the check's renewal window, so we rehearsed it cannot quietly come to mean in 2023.

Terminalbash
gitmoap catalog attest-service-check \
--service-id <id> \
--scorecard-id <id> \
--check-key <id> \
--state passing|failing
curlbash
curl -X POST https://api.gitmoap.example/catalog/services/{service_id}/checks/{scorecard_id}/{check_key}/attest \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "state": "<one of failing | passing>" }'

Only passing and failing can be attested. stale, unknown and not_applicable are the platform's own vocabulary for what it could not determine, and letting a person write them would make nobody has looked indistinguishable from somebody said so. Attesting a derived or evidence check is refused for the same reason: a value the platform computes has exactly one writer.

Delete a scorecard

Warning

Deleting a scorecard destroys every result it produced and every state transition recorded against it — the whole history of what passed, what failed, and when each one changed. Recreating the scorecard starts the history over from unknown. If you want to stop grading without losing the record, switch the scorecard off instead: a disabled scorecard stops evaluating and keeps everything it has already found.

Open the scorecard, choose Settings, then Delete scorecard. The confirmation names the number of results going with it. Deleting requires Owner.

Terminalbash
gitmoap catalog delete-scorecard --scorecard-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/catalog/scorecards/{scorecard_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Reference

Services

9 endpoints

GET/groups/{group_id}/catalog/servicescatalog:read

List a group's services, each with its deployment promotion track

list_servicesGET /groups/{group_id}/catalog/servicesOpen in the API reference

Token scope

catalog:read

Send

Path
  • group_iduuidThe declaring group's id
Query
  • kindstringoptionalFilter by service kind
  • lifecyclestringoptionalFilter by lifecycle
  • sourcestringoptionalFilter by how the entry came to exist
  • include_orphanedbooleanoptionalInclude entries whose declaration has disappeared (excluded by default)
  • searchstringoptionalMatch the name, description or a slug substring
  • limitint32optionalPage size (1-100, default 20)
  • next_tokenstringoptionalPagination cursor

Refuses

  • 400Unknown filter value or malformed cursor
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such group, or it is not visible to the caller

Receive

  • items[]object10 fields
    • serviceobject10 fields
      • display_namestring
      • externalbooleanoptional
      • group_iduuid
      • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
      • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
      • orphaned_atobjectoptional
      • ownerobjectoptional
      • service_iduuid
      • slugstring
      • sourceapideclaredinferredHow the catalog entry came to exist. Set by the platform, never accepted from a request. Any source other than inferred means a person has vouched for the entry.
  • next_tokenobjectoptional
GET/repositories/{repo_id}/catalog/servicescatalog:read

List the services bound to a repository, each with its deployment promotion track

list_repo_servicesGET /repositories/{repo_id}/catalog/servicesOpen in the API reference

Token scope

catalog:read

Send

Path
  • repo_iduuidThe repository the services are bound to
Query
  • kindstringoptionalFilter by service kind
  • lifecyclestringoptionalFilter by lifecycle
  • sourcestringoptionalFilter by how the entry came to exist
  • include_orphanedbooleanoptionalInclude entries whose declaration has disappeared (excluded by default)
  • searchstringoptionalMatch the name, description or a slug substring
  • limitint32optionalPage size (1-100, default 20)
  • next_tokenstringoptionalPagination cursor

Refuses

  • 400Unknown filter value or malformed cursor
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such repository, or it is not visible to the caller

Receive

The same shape list_services returns — 2 fields. Go to it

Show it here
  • items[]object10 fields
    • serviceobject10 fields
      • display_namestring
      • externalbooleanoptional
      • group_iduuid
      • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
      • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
      • orphaned_atobjectoptional
      • ownerobjectoptional
      • service_iduuid
      • slugstring
      • sourceapideclaredinferredHow the catalog entry came to exist. Set by the platform, never accepted from a request. Any source other than inferred means a person has vouched for the entry.
  • next_tokenobjectoptional
GET/catalog/services/{service_id}catalog:read

Get a service by id

get_serviceGET /catalog/services/{service_id}Open in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidThe service's id

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
  • 404No such service, or it is not visible to the caller

Receive

  • annotationsobjectoptional
  • api_specs[]object3 fields
    • pathstring
    • repository_iduuid
    • spec_kindasyncapigrpcopenapiWhich interface-description format a spec is written in.
  • created_atstring
  • created_byobjectoptional
  • descriptionobjectoptional
  • display_namestring
  • externalbooleanoptionalWhether the service is built outside gitmoap — a vendor SaaS, a service on another forge, a database provisioned elsewhere. The catalog records its name, owner and the dependencies on it, and nothing else. An external service has no repository paths.
  • group_iduuid
  • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
  • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
  • links[]object2 fields
    • link_typechatdashboarddocsotherrunbookWhat a service link points at. other covers anything the listed kinds do not.
    • urlstring
  • orphaned_atobjectoptionalWhen the catalog file declaring this service stopped declaring it. The service is kept, with its history, rather than deleted.
  • ownerobjectoptional
  • provides[]object2 fields
    • formatcargonpmpypiThe package ecosystem a registry speaks. A group has at most one registry per format.
    • namestring
  • repo_paths[]object4 fields
    • is_excludebooleanoptionalWhether this glob removes its matches from the service's paths instead of adding them. It affects only its own service.
    • path_globstring
    • path_prefixstringThe longest literal prefix of path_glob, worked out by the platform. Read-only.
    • repository_iduuid
  • service_iduuid
  • slugstring
GET/groups/{group_id}/catalog/services/by-slug/{slug}catalog:read

Get a service by its slug within a namespace

get_service_by_slugGET /groups/{group_id}/catalog/services/by-slug/{slug}Open in the API reference

Token scope

catalog:read

Send

Path
  • group_iduuidA group in the namespace to resolve within
  • slugstringThe service's slug, unique per namespace

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
  • 404No such service, or it is not visible to the caller

Receive

The same shape get_service returns — 17 fields. Go to it

Show it here
  • annotationsobjectoptional
  • api_specs[]object3 fields
    • pathstring
    • repository_iduuid
    • spec_kindasyncapigrpcopenapiWhich interface-description format a spec is written in.
  • created_atstring
  • created_byobjectoptional
  • descriptionobjectoptional
  • display_namestring
  • externalbooleanoptionalWhether the service is built outside gitmoap — a vendor SaaS, a service on another forge, a database provisioned elsewhere. The catalog records its name, owner and the dependencies on it, and nothing else. An external service has no repository paths.
  • group_iduuid
  • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
  • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
  • links[]object2 fields
    • link_typechatdashboarddocsotherrunbookWhat a service link points at. other covers anything the listed kinds do not.
    • urlstring
  • orphaned_atobjectoptionalWhen the catalog file declaring this service stopped declaring it. The service is kept, with its history, rather than deleted.
  • ownerobjectoptional
  • provides[]object2 fields
    • formatcargonpmpypiThe package ecosystem a registry speaks. A group has at most one registry per format.
    • namestring
  • repo_paths[]object4 fields
    • is_excludebooleanoptionalWhether this glob removes its matches from the service's paths instead of adding them. It affects only its own service.
    • path_globstring
    • path_prefixstringThe longest literal prefix of path_glob, worked out by the platform. Read-only.
    • repository_iduuid
  • service_iduuid
  • slugstring
GET/catalog/services/{service_id}/overviewcatalog:read

Everything a service page shows, in one call

get_service_overviewGET /catalog/services/{service_id}/overviewOpen in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidThe service's id

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
  • 404No such service, or it is not visible to the caller

Receive

  • current_deployments[]object8 fields
    • commit_shastring
    • deployed_atstring
    • deployment_iduuid
    • environment_iduuid
    • pipeline_run_idobjectoptional
    • repository_iduuid
    • service_iduuid
    • statusawaiting_approvalcancelledfailedrejectedrequestedrunningskippedsucceededsupersededWhere a deployment is in its life. It starts requested; waits as awaiting_approval when the environment needs approval, ending rejected if refused; then running, ending succeeded, failed or cancelled. skipped means another deploy held the environment and its policy is to skip. A successful deployment becomes superseded when a later one replaces it.
  • debtobject8 fields
    • driftedint32
    • open_bugsint32
    • rows[]object6 fields
      • driftedint32
      • markersint32
      • open_bugsint32
      • open_storiesint32
      • pathstring
      • totalint32
  • dependency_countsobject3 fields
    • hiddenint32Dependencies left out, in either direction, because you cannot see the service at the far end.
    • inboundint32
    • outboundint32
  • latest_pipeline_runobjectoptional
  • latest_releaseobjectoptional
  • open_merge_requestsint32
  • ownerobjectoptional
  • repositories[]object1 field
    • default_branchstring
GET/repositories/{repo_id}/catalog/resolvecatalog:read

Resolve repository paths to the services that own them

resolve_repo_path_serviceGET /repositories/{repo_id}/catalog/resolveOpen in the API reference

Token scope

catalog:read

Send

Path
  • repo_iduuidThe repository the paths belong to
Query
  • patharrayA repository-relative path. Repeatable, up to 200 per call — one request per changed file set, not one per file

Refuses

  • 400No paths given, or more than 200
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such repository, or it is not visible to the caller

Receive

  • resolved[]object2 fields
    • pathstring
    • serviceobjectoptional
POST/groups/{group_id}/catalog/servicescatalog:manage

Declare a service in a group

create_servicePOST /groups/{group_id}/catalog/servicesOpen in the API reference

Token scope

catalog:manage

Counts against

services_per_scope

Send

Path
  • group_iduuidThe declaring group's id
Body
  • annotationsobjectoptional
  • api_specs[]object3 fields
    • pathstring
    • repository_iduuid
    • spec_kindasyncapigrpcopenapiWhich interface-description format a spec is written in.
  • descriptionobjectoptional
  • display_nameobjectoptionalDefaults to the slug.
  • externalbooleanoptional
  • kindapidatastorejoblibraryml_modelotherservicewebsiteoptionalWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
  • lifecycledeprecatedexperimentalproductionretiredoptionalWhere a service is in its life.
  • links[]object2 fields
    • link_typechatdashboarddocsotherrunbookWhat a service link points at. other covers anything the listed kinds do not.
    • urlstring
  • owner_group_idobjectoptional
  • owner_principal_idobjectoptional
  • owner_team_idobjectoptional
  • provides[]object2 fields
    • formatcargonpmpypiThe package ecosystem a registry speaks. A group has at most one registry per format.
    • namestring
  • repo_paths[]object3 fields
    • is_excludebooleanoptional
    • path_globstring
    • repository_iduuid
  • slugstring

Refuses

  • 400Validation error
  • 401Unauthenticated — no credential, or an invalid one
  • 403Insufficient standing on the group, or on the group named as owner
  • 404No such group, or a named repository is not visible
  • 409The slug is already taken in this namespace
  • 429The namespace is at its service limit

Receive

The same shape get_service returns — 17 fields. Go to it

Show it here
  • annotationsobjectoptional
  • api_specs[]object3 fields
    • pathstring
    • repository_iduuid
    • spec_kindasyncapigrpcopenapiWhich interface-description format a spec is written in.
  • created_atstring
  • created_byobjectoptional
  • descriptionobjectoptional
  • display_namestring
  • externalbooleanoptionalWhether the service is built outside gitmoap — a vendor SaaS, a service on another forge, a database provisioned elsewhere. The catalog records its name, owner and the dependencies on it, and nothing else. An external service has no repository paths.
  • group_iduuid
  • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
  • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
  • links[]object2 fields
    • link_typechatdashboarddocsotherrunbookWhat a service link points at. other covers anything the listed kinds do not.
    • urlstring
  • orphaned_atobjectoptionalWhen the catalog file declaring this service stopped declaring it. The service is kept, with its history, rather than deleted.
  • ownerobjectoptional
  • provides[]object2 fields
    • formatcargonpmpypiThe package ecosystem a registry speaks. A group has at most one registry per format.
    • namestring
  • repo_paths[]object4 fields
    • is_excludebooleanoptionalWhether this glob removes its matches from the service's paths instead of adding them. It affects only its own service.
    • path_globstring
    • path_prefixstringThe longest literal prefix of path_glob, worked out by the platform. Read-only.
    • repository_iduuid
  • service_iduuid
  • slugstring
PATCH/catalog/services/{service_id}catalog:manage

Update a service

update_servicePATCH /catalog/services/{service_id}Open in the API reference

Token scope

catalog:manage

Send

Path
  • service_iduuidThe service's id
Body
  • annotationsobjectoptional
  • api_specs[]object3 fields
    • pathstring
    • repository_iduuid
    • spec_kindasyncapigrpcopenapiWhich interface-description format a spec is written in.
  • descriptionobjectoptional
  • display_nameobjectoptional
  • externalobjectoptional
  • kindobjectoptional
  • lifecycleobjectoptional
  • links[]object2 fields
    • link_typechatdashboarddocsotherrunbookWhat a service link points at. other covers anything the listed kinds do not.
    • urlstring
  • owner_group_idobjectoptional
  • owner_principal_idobjectoptional
  • owner_team_idobjectoptional
  • provides[]object2 fields
    • formatcargonpmpypiThe package ecosystem a registry speaks. A group has at most one registry per format.
    • namestring
  • repo_paths[]object3 fields
    • is_excludebooleanoptional
    • path_globstring
    • repository_iduuid
  • slugobjectoptional
  • versionint64optionalThe version you last read. When given and the service has changed since, the update is refused with a 409.

Refuses

  • 400Validation error
  • 401Unauthenticated — no credential, or an invalid one
  • 403Insufficient standing on the group, or on the group named as the new owner
  • 404No such service, or it is not visible to the caller
  • 409The slug is taken, or the service was modified concurrently

Receive

The same shape get_service returns — 17 fields. Go to it

Show it here
  • annotationsobjectoptional
  • api_specs[]object3 fields
    • pathstring
    • repository_iduuid
    • spec_kindasyncapigrpcopenapiWhich interface-description format a spec is written in.
  • created_atstring
  • created_byobjectoptional
  • descriptionobjectoptional
  • display_namestring
  • externalbooleanoptionalWhether the service is built outside gitmoap — a vendor SaaS, a service on another forge, a database provisioned elsewhere. The catalog records its name, owner and the dependencies on it, and nothing else. An external service has no repository paths.
  • group_iduuid
  • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
  • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
  • links[]object2 fields
    • link_typechatdashboarddocsotherrunbookWhat a service link points at. other covers anything the listed kinds do not.
    • urlstring
  • orphaned_atobjectoptionalWhen the catalog file declaring this service stopped declaring it. The service is kept, with its history, rather than deleted.
  • ownerobjectoptional
  • provides[]object2 fields
    • formatcargonpmpypiThe package ecosystem a registry speaks. A group has at most one registry per format.
    • namestring
  • repo_paths[]object4 fields
    • is_excludebooleanoptionalWhether this glob removes its matches from the service's paths instead of adding them. It affects only its own service.
    • path_globstring
    • path_prefixstringThe longest literal prefix of path_glob, worked out by the platform. Read-only.
    • repository_iduuid
  • service_iduuid
  • slugstring
DELETE/catalog/services/{service_id}catalog:manage

Delete a service

delete_serviceDELETE /catalog/services/{service_id}Open in the API reference

Token scope

catalog:manage

Send

Path
  • service_iduuidThe service's id

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Deleting a service requires Owner on its declaring group
  • 404No such service, or it is not visible to the caller
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

Environments

7 endpoints

GET/groups/{group_id}/catalog/environmentscatalog:read

List a group's environments

list_group_environmentsGET /groups/{group_id}/catalog/environmentsOpen in the API reference

Token scope

catalog:read

Send

Path
  • group_iduuidThe owning group's id
Query
  • tierstringoptionalFilter by tier
  • include_archivedbooleanoptionalInclude archived environments (excluded by default)
  • inheritedbooleanoptionalAlso return the ancestor-group environments this scope may reference, with a nearer slug shadowing a farther one
  • searchstringoptionalMatch the slug or display name
  • limitint32optionalPage size (1-100, default 20)
  • next_tokenstringoptionalPagination cursor

Refuses

  • 400Unknown filter value or malformed cursor
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such group, or it is not visible to the caller

Receive

  • items[]object7 fields
    • archivedboolean
    • display_nameobjectoptional
    • environment_iduuid
    • scopeobjectA group or a repository, whichever a setting belongs to.
    • slugstring
    • tierdevelopmentproductionstagingtestingWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
    • urlobjectoptional
  • next_tokenobjectoptional
GET/repositories/{repo_id}/catalog/environmentscatalog:read

List a repository's environments

list_repo_environmentsGET /repositories/{repo_id}/catalog/environmentsOpen in the API reference

Token scope

catalog:read

Send

Path
  • repo_iduuidThe owning repository's id
Query
  • tierstringoptionalFilter by tier
  • include_archivedbooleanoptionalInclude archived environments (excluded by default)
  • inheritedbooleanoptionalAlso return the ancestor-group environments this repository may reference — what a pipeline author needs, since a node's environment: resolves up the chain
  • searchstringoptionalMatch the slug or display name
  • limitint32optionalPage size (1-100, default 20)
  • next_tokenstringoptionalPagination cursor

Refuses

  • 400Unknown filter value or malformed cursor
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such repository, or it is not visible to the caller

Receive

The same shape list_group_environments returns — 2 fields. Go to it

Show it here
  • items[]object7 fields
    • archivedboolean
    • display_nameobjectoptional
    • environment_iduuid
    • scopeobjectA group or a repository, whichever a setting belongs to.
    • slugstring
    • tierdevelopmentproductionstagingtestingWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
    • urlobjectoptional
  • next_tokenobjectoptional
GET/catalog/environments/{environment_id}catalog:read

Get an environment by id

get_environmentGET /catalog/environments/{environment_id}Open in the API reference

Token scope

catalog:read

Send

Path
  • environment_iduuidThe environment's id

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
  • 404No such environment, or it is not visible to the caller

Receive

  • approval_enforcedbooleanWhether the platform enforces protection.requires_approval on deploys, so a client can tell a policy that is applied from one that is only recorded.
  • archivedbooleanWhether the environment is archived. An archived environment can no longer be deployed to but keeps its deployment history, and archiving can be undone. An environment with deployments cannot be deleted, only archived.
  • concurrencyobject3 fields
    • exclusivebooleanoptionalWhether deploys to this environment run one at a time. Off by default.
    • max_lock_wait_secondsint32optionalHow long a queued deploy waits for its turn before failing, in seconds: from 60 (shorter values are rounded up) to 21600. 0, the default, means it waits for as long as the pipeline's timeout allows.
    • modeobjectoptional
  • created_atstring
  • created_byobjectoptional
  • display_nameobjectoptional
  • environment_iduuid
  • owner_full_pathstringThe full path of the group or repository that owns the environment (e.g. acme/platform), which shows when an environment in a repository's listing comes from a group above it. With the slug it forms the environment's OIDC claim, {owner_full_path}:{slug}.
  • protectionobject5 fields
    • accept_fromobjectoptional
    • prevent_self_approvalbooleanoptionalWhether the person who started the run is barred from approving its deployment. What counts is who started the run, not who wrote the commit: deploying somebody else's commit is still your deploy.
    • requiredint32optionalDistinct approvals needed before the deployment starts, 1–10.
    • requires_approvalbooleanoptionalWhen set, a deployment to this environment waits for approval before it starts. Nothing runs and no credentials are issued while it waits.
    • wait_timer_secondsint32optionalHow long a deployment waits before it may start, in seconds, up to one hour. 0 means no wait.
  • scopeobjectA group or a repository, whichever a setting belongs to.
  • slugstring
  • tierdevelopmentproductionstagingtestingWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
  • updated_atstring
  • urlobjectoptional
  • variable_countint32How many variables the environment holds.
  • versionint64Increases with every change. Send it with an update to have the update refused with 409 if someone else changed the environment first.
POST/groups/{group_id}/catalog/environmentscatalog:manage

Declare an environment on a group

create_group_environmentPOST /groups/{group_id}/catalog/environmentsOpen in the API reference

Token scope

catalog:manage

Counts against

environments_per_scope

Send

Path
  • group_iduuidThe owning group's id
Body
  • concurrencyobjectoptional
  • display_nameobjectoptional
  • protectionobjectoptional
  • slugstring
  • tierdevelopmentproductionstagingtestingoptionalWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
  • urlobjectoptional

Refuses

  • 400Validation error, or a gate with no approver groups
  • 401Unauthenticated — no credential, or an invalid one
  • 403Insufficient standing, or Maintainer attempting a production-tier environment
  • 404No such group, or it is not visible to the caller
  • 409The slug is already taken on this scope
  • 429The scope is at its environment limit

Receive

The same shape get_environment returns — 16 fields. Go to it

Show it here
  • approval_enforcedbooleanWhether the platform enforces protection.requires_approval on deploys, so a client can tell a policy that is applied from one that is only recorded.
  • archivedbooleanWhether the environment is archived. An archived environment can no longer be deployed to but keeps its deployment history, and archiving can be undone. An environment with deployments cannot be deleted, only archived.
  • concurrencyobject3 fields
    • exclusivebooleanoptionalWhether deploys to this environment run one at a time. Off by default.
    • max_lock_wait_secondsint32optionalHow long a queued deploy waits for its turn before failing, in seconds: from 60 (shorter values are rounded up) to 21600. 0, the default, means it waits for as long as the pipeline's timeout allows.
    • modeobjectoptional
  • created_atstring
  • created_byobjectoptional
  • display_nameobjectoptional
  • environment_iduuid
  • owner_full_pathstringThe full path of the group or repository that owns the environment (e.g. acme/platform), which shows when an environment in a repository's listing comes from a group above it. With the slug it forms the environment's OIDC claim, {owner_full_path}:{slug}.
  • protectionobject5 fields
    • accept_fromobjectoptional
    • prevent_self_approvalbooleanoptionalWhether the person who started the run is barred from approving its deployment. What counts is who started the run, not who wrote the commit: deploying somebody else's commit is still your deploy.
    • requiredint32optionalDistinct approvals needed before the deployment starts, 1–10.
    • requires_approvalbooleanoptionalWhen set, a deployment to this environment waits for approval before it starts. Nothing runs and no credentials are issued while it waits.
    • wait_timer_secondsint32optionalHow long a deployment waits before it may start, in seconds, up to one hour. 0 means no wait.
  • scopeobjectA group or a repository, whichever a setting belongs to.
  • slugstring
  • tierdevelopmentproductionstagingtestingWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
  • updated_atstring
  • urlobjectoptional
  • variable_countint32How many variables the environment holds.
  • versionint64Increases with every change. Send it with an update to have the update refused with 409 if someone else changed the environment first.
POST/repositories/{repo_id}/catalog/environmentscatalog:manage

Declare an environment on a repository

create_repo_environmentPOST /repositories/{repo_id}/catalog/environmentsOpen in the API reference

Token scope

catalog:manage

Counts against

environments_per_scope

Send

Path
  • repo_iduuidThe owning repository's id
Body
  • concurrencyobjectoptional
  • display_nameobjectoptional
  • protectionobjectoptional
  • slugstring
  • tierdevelopmentproductionstagingtestingoptionalWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
  • urlobjectoptional

Refuses

  • 400Validation error, or a gate with no approver groups
  • 401Unauthenticated — no credential, or an invalid one
  • 403Insufficient standing, or Maintainer attempting a production-tier environment
  • 404No such repository, or it is not visible to the caller
  • 409The slug is already taken on this scope
  • 429The scope is at its environment limit

Receive

The same shape get_environment returns — 16 fields. Go to it

Show it here
  • approval_enforcedbooleanWhether the platform enforces protection.requires_approval on deploys, so a client can tell a policy that is applied from one that is only recorded.
  • archivedbooleanWhether the environment is archived. An archived environment can no longer be deployed to but keeps its deployment history, and archiving can be undone. An environment with deployments cannot be deleted, only archived.
  • concurrencyobject3 fields
    • exclusivebooleanoptionalWhether deploys to this environment run one at a time. Off by default.
    • max_lock_wait_secondsint32optionalHow long a queued deploy waits for its turn before failing, in seconds: from 60 (shorter values are rounded up) to 21600. 0, the default, means it waits for as long as the pipeline's timeout allows.
    • modeobjectoptional
  • created_atstring
  • created_byobjectoptional
  • display_nameobjectoptional
  • environment_iduuid
  • owner_full_pathstringThe full path of the group or repository that owns the environment (e.g. acme/platform), which shows when an environment in a repository's listing comes from a group above it. With the slug it forms the environment's OIDC claim, {owner_full_path}:{slug}.
  • protectionobject5 fields
    • accept_fromobjectoptional
    • prevent_self_approvalbooleanoptionalWhether the person who started the run is barred from approving its deployment. What counts is who started the run, not who wrote the commit: deploying somebody else's commit is still your deploy.
    • requiredint32optionalDistinct approvals needed before the deployment starts, 1–10.
    • requires_approvalbooleanoptionalWhen set, a deployment to this environment waits for approval before it starts. Nothing runs and no credentials are issued while it waits.
    • wait_timer_secondsint32optionalHow long a deployment waits before it may start, in seconds, up to one hour. 0 means no wait.
  • scopeobjectA group or a repository, whichever a setting belongs to.
  • slugstring
  • tierdevelopmentproductionstagingtestingWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
  • updated_atstring
  • urlobjectoptional
  • variable_countint32How many variables the environment holds.
  • versionint64Increases with every change. Send it with an update to have the update refused with 409 if someone else changed the environment first.
PATCH/catalog/environments/{environment_id}catalog:manage

Patch an environment

update_environmentPATCH /catalog/environments/{environment_id}Open in the API reference

Token scope

catalog:manage

Send

Path
  • environment_iduuidThe environment's id
Body
  • archivedobjectoptional
  • concurrencyobjectoptional
  • display_nameobjectoptional
  • protectionobjectoptional
  • slugobjectoptional
  • tierobjectoptional
  • urlobjectoptional
  • versionint64optionalOptimistic-concurrency guard. When supplied and stale, the write is a 409.

Refuses

  • 400Validation error, or a gate with no approver groups
  • 401Unauthenticated — no credential, or an invalid one
  • 403Owner is required to edit protection, tier, or a production-tier environment
  • 404No such environment, or it is not visible to the caller
  • 409The slug is taken, or the supplied version is stale

Receive

The same shape get_environment returns — 16 fields. Go to it

Show it here
  • approval_enforcedbooleanWhether the platform enforces protection.requires_approval on deploys, so a client can tell a policy that is applied from one that is only recorded.
  • archivedbooleanWhether the environment is archived. An archived environment can no longer be deployed to but keeps its deployment history, and archiving can be undone. An environment with deployments cannot be deleted, only archived.
  • concurrencyobject3 fields
    • exclusivebooleanoptionalWhether deploys to this environment run one at a time. Off by default.
    • max_lock_wait_secondsint32optionalHow long a queued deploy waits for its turn before failing, in seconds: from 60 (shorter values are rounded up) to 21600. 0, the default, means it waits for as long as the pipeline's timeout allows.
    • modeobjectoptional
  • created_atstring
  • created_byobjectoptional
  • display_nameobjectoptional
  • environment_iduuid
  • owner_full_pathstringThe full path of the group or repository that owns the environment (e.g. acme/platform), which shows when an environment in a repository's listing comes from a group above it. With the slug it forms the environment's OIDC claim, {owner_full_path}:{slug}.
  • protectionobject5 fields
    • accept_fromobjectoptional
    • prevent_self_approvalbooleanoptionalWhether the person who started the run is barred from approving its deployment. What counts is who started the run, not who wrote the commit: deploying somebody else's commit is still your deploy.
    • requiredint32optionalDistinct approvals needed before the deployment starts, 1–10.
    • requires_approvalbooleanoptionalWhen set, a deployment to this environment waits for approval before it starts. Nothing runs and no credentials are issued while it waits.
    • wait_timer_secondsint32optionalHow long a deployment waits before it may start, in seconds, up to one hour. 0 means no wait.
  • scopeobjectA group or a repository, whichever a setting belongs to.
  • slugstring
  • tierdevelopmentproductionstagingtestingWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
  • updated_atstring
  • urlobjectoptional
  • variable_countint32How many variables the environment holds.
  • versionint64Increases with every change. Send it with an update to have the update refused with 409 if someone else changed the environment first.
DELETE/catalog/environments/{environment_id}catalog:manage

Delete an environment

delete_environmentDELETE /catalog/environments/{environment_id}Open in the API reference

Token scope

catalog:manage

Send

Path
  • environment_iduuidThe environment's id

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Owner is required to delete an environment
  • 404No such environment, or it is not visible to the caller
  • 409The environment has deployments; archive it instead
  • 501Environment variables are not configured on this deployment

Receive

No body.

Environment variables

3 endpoints

GET/catalog/environments/{environment_id}/variablescatalog:read

List an environment's variables

list_environment_variablesGET /catalog/environments/{environment_id}/variablesOpen in the API reference

Token scope

catalog:read

Send

Path
  • environment_iduuidThe environment's id

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
  • 404No such environment, or it is not visible to the caller

Receive

  • items[]object6 fields
    • created_atstring
    • created_byobjectoptional
    • namestring
    • secretboolean
    • updated_atstring
    • valueobjectoptionalThe variable's value. Absent for a secret, whose value can be written but never read back.
  • next_tokenobjectoptional
PUT/catalog/environments/{environment_id}/variables/{name}catalog:manage

Create or replace an environment variable

put_environment_variablePUT /catalog/environments/{environment_id}/variables/{name}Open in the API reference

Token scope

catalog:manage

Counts against

variables_per_environment

Send

Path
  • environment_iduuidThe environment's id
  • namestringThe variable's name (POSIX env-var shape)
Body
  • secretbooleanoptionalWhether the value is a secret. A secret's value can never be read back and is redacted from step logs; either way the step receives it as an environment variable. Defaults to false.
  • valuestring

Refuses

  • 400Invalid name or value, or a platform-reserved name
  • 401Unauthenticated — no credential, or an invalid one
  • 403Owner is required to write an environment variable
  • 404No such environment, or it is not visible to the caller
  • 409The request conflicts with the resource's current state; `reason` says how
  • 429The environment's variable quota is exhausted
  • 501Environment variables are not configured on this deployment

Receive

  • created_atstring
  • created_byobjectoptional
  • namestring
  • secretboolean
  • updated_atstring
  • valueobjectoptionalThe variable's value. Absent for a secret, whose value can be written but never read back.
DELETE/catalog/environments/{environment_id}/variables/{name}catalog:manage

Delete an environment variable

delete_environment_variableDELETE /catalog/environments/{environment_id}/variables/{name}Open in the API reference

Token scope

catalog:manage

Send

Path
  • environment_iduuidThe environment's id
  • namestringThe variable's name

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Owner is required to delete an environment variable
  • 404No such environment or no such variable
  • 409The request conflicts with the resource's current state; `reason` says how
  • 501Environment variables are not configured on this deployment

Receive

No body.

Deployments

5 endpoints

GET/catalog/services/{service_id}/deployments/currentcatalog:read

What a service has deployed right now

get_service_current_deploymentsGET /catalog/services/{service_id}/deployments/currentOpen in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidThe service's id

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
  • 404No such service, or it is not visible to the caller

Receive

  • items[]object8 fields
    • commit_shastring
    • deployed_atstring
    • deployment_iduuid
    • environment_iduuid
    • pipeline_run_idobjectoptional
    • repository_iduuid
    • service_iduuid
    • statusawaiting_approvalcancelledfailedrejectedrequestedrunningskippedsucceededsupersededWhere a deployment is in its life. It starts requested; waits as awaiting_approval when the environment needs approval, ending rejected if refused; then running, ending succeeded, failed or cancelled. skipped means another deploy held the environment and its policy is to skip. A successful deployment becomes superseded when a later one replaces it.
  • next_tokenobjectoptional
GET/catalog/services/{service_id}/deploymentscatalog:read

A service's deployment history

list_service_deploymentsGET /catalog/services/{service_id}/deploymentsOpen in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidThe service's id
Query
  • environment_idstringoptionalOnly deployments to this environment
  • statusstringoptionalFilter by deployment status
  • limitint32optionalPage size (1-100, default 20)
  • next_tokenstringoptionalPagination cursor

Refuses

  • 400Unknown filter value or malformed cursor
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such service, or it is not visible to the caller

Receive

  • items[]object15 fields
    • approved_byobjectoptional
    • attemptint32
    • commit_shastring
    • deployment_iduuid
    • environment_iduuid
    • error_summaryobjectoptional
    • finished_atobjectoptional
    • node_idobjectoptional
    • pipeline_run_idobjectoptional
    • ref_nameobjectoptional
    • repository_iduuid
    • service_iduuid
    • started_atstring
    • statusawaiting_approvalcancelledfailedrejectedrequestedrunningskippedsucceededsupersededWhere a deployment is in its life. It starts requested; waits as awaiting_approval when the environment needs approval, ending rejected if refused; then running, ending succeeded, failed or cancelled. skipped means another deploy held the environment and its policy is to skip. A successful deployment becomes superseded when a later one replaces it.
    • triggered_byobjectoptional
  • next_tokenobjectoptional
GET/catalog/services/{service_id}/deployments/driftcatalog:read

How far behind its default branch each deployment is

get_service_deployment_driftGET /catalog/services/{service_id}/deployments/driftOpen in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidThe service's id

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
  • 404No such service, or it is not visible to the caller

Receive

  • rows[]object13 fields
    • commits_behindint32optionalHow many commits the default branch has that the deployed commit does not. Absent when the deployed commit can no longer be read, for example after a force-push.
    • default_branchstring
    • deployed_shastring
    • environmentobject7 fields
      • archivedboolean
      • display_nameobjectoptional
      • environment_iduuid
      • scopeobjectA group or a repository, whichever a setting belongs to.
      • slugstring
      • tierdevelopmentproductionstagingtestingWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
      • urlobjectoptional
    • repository_iduuid
    • repository_pathstring
    • truncatedbooleanoptional
  • service_iduuid
GET/catalog/environments/{environment_id}/deploymentscatalog:read

What is deployed into an environment

list_environment_deploymentsGET /catalog/environments/{environment_id}/deploymentsOpen in the API reference

Token scope

catalog:read

Send

Path
  • environment_iduuidThe environment's id
Query
  • limitint32optionalPage size (1-100, default 20)

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
  • 404No such environment, or it is not visible to the caller

Receive

The same shape get_service_current_deployments returns — 2 fields. Go to it

Show it here
  • items[]object8 fields
    • commit_shastring
    • deployed_atstring
    • deployment_iduuid
    • environment_iduuid
    • pipeline_run_idobjectoptional
    • repository_iduuid
    • service_iduuid
    • statusawaiting_approvalcancelledfailedrejectedrequestedrunningskippedsucceededsupersededWhere a deployment is in its life. It starts requested; waits as awaiting_approval when the environment needs approval, ending rejected if refused; then running, ending succeeded, failed or cancelled. skipped means another deploy held the environment and its policy is to skip. A successful deployment becomes superseded when a later one replaces it.
  • next_tokenobjectoptional
GET/groups/{group_id}/catalog/deploymentscatalog:read

The deployment board — services × environments

list_group_deploymentsGET /groups/{group_id}/catalog/deploymentsOpen in the API reference

Token scope

catalog:read

Send

Path
  • group_iduuidThe group whose services the board lists
Query
  • environment_idstringoptionalShow only these environments, as a comma-separated list of ids
  • tierstringoptionalShow only environments of this tier
  • searchstringoptionalMatch a service's name, description or slug substring
  • limitint32optionalServices per page (1-100, default 20)
  • next_tokenstringoptionalPagination cursor

Refuses

  • 400Unknown filter value, malformed cursor, or more than 500 environment columns
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such group, or it is not visible to the caller

Receive

  • environments[]object7 fields
    • archivedboolean
    • display_nameobjectoptional
    • environment_iduuid
    • scopeobjectA group or a repository, whichever a setting belongs to.
    • slugstring
    • tierdevelopmentproductionstagingtestingWhat kind of environment this is, whatever it is called: development, testing, staging or production. A production environment is protected by default.
    • urlobjectoptional
  • next_tokenobjectoptional
  • rows[]object10 fields
    • serviceobject10 fields
      • display_namestring
      • externalbooleanoptional
      • group_iduuid
      • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
      • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
      • orphaned_atobjectoptional
      • ownerobjectoptional
      • service_iduuid
      • slugstring
      • sourceapideclaredinferredHow the catalog entry came to exist. Set by the platform, never accepted from a request. Any source other than inferred means a person has vouched for the entry.

Scorecards

9 endpoints

GET/catalog/check-kindscatalog:read

Describe every derived check the platform implements

list_check_kindsGET /catalog/check-kindsOpen in the API reference

Token scope

catalog:read

Send

Nothing — the address is the whole request.

Refuses

  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient

Receive

  • kinds[]object5 fields
    • config_schemaobjectA JSON Schema over the fields — render a form from this.
    • descriptionstringWhat the check means, and the one thing to do about a failure.
    • keystring
    • labelstring
    • sweepsbooleanWhether this check is re-evaluated periodically as well as on activity — true for checks whose answer can change with time alone.
GET/groups/{group_id}/catalog/scorecardscatalog:read

List the scorecards that apply to a group

list_scorecardsGET /groups/{group_id}/catalog/scorecardsOpen in the API reference

Token scope

catalog:read

Send

Path
  • group_iduuidThe group's id
Query
  • include_inheritedbooleanoptionalInclude ancestor scopes' scorecards. Defaults to true, because union inheritance is what makes a baseline a baseline
  • enabled_onlybooleanoptionalOmit disabled scorecards
  • limitint32optionalPage size (clamped)
  • next_tokenstringoptionalOpaque pagination cursor

Refuses

  • 400Invalid filter or cursor
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such group

Receive

  • items[]object8 fields
    • campaign_activeboolean
    • check_countint32
    • enabledboolean
    • level_countint32
    • namestring
    • scopeobjectA group or a repository, whichever a setting belongs to.
    • scorecard_iduuid
    • slugstring
  • next_tokenobjectoptional
GET/catalog/scorecards/{scorecard_id}catalog:read

Read one scorecard

get_scorecardGET /catalog/scorecards/{scorecard_id}Open in the API reference

Token scope

catalog:read

Send

Path
  • scorecard_iduuidThe scorecard's id

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
  • 404No such scorecard, or it is not visible

Receive

  • applies_toobject1 field
    • annotationsobjectoptionalAnnotations a service must carry, as key–value pairs. Every entry must match; a service with extra annotations still matches.
  • campaign_activebooleanWhether the scorecard's campaign is on. Checks that open work items on failure do so only while it is.
  • campaign_enforcedbooleanWhether the platform acts on campaign_active. Always true.
  • campaign_item_countint32How many work items the campaign has filed since it was last switched on. Read-only.
  • campaign_max_itemsint32How many work items the campaign may file each time it is switched on. The count starts from zero every time campaign_active is turned on.
  • checks[]object4 fields
    • configobjectoptionalThe check's own parameters, such as {"days": 90} for released_within. An unknown parameter is refused with a 400 rather than ignored.
    • creates_work_itembooleanoptionalWhether a failure opens a work item in the owning group's backlog. Off by default, and it takes effect only while the scorecard's campaign is active.
    • keystring
    • ruleobjectHow a check decides its answer.
  • created_atstring
  • created_byobjectoptional
  • descriptionobjectoptional
  • enabledboolean
  • levels[]object1 field
    • namestring
  • namestring
  • scopeobjectA group or a repository, whichever a setting belongs to.
  • scorecard_iduuid
  • slugstring
  • updated_atstring
  • versionint64The scorecard's revision, incremented on every change. Send it with an update to have the update refused with a 409 if someone changed the scorecard since you read it.
POST/groups/{group_id}/catalog/scorecardscatalog:manage

Declare a scorecard on a group

create_scorecardPOST /groups/{group_id}/catalog/scorecardsOpen in the API reference

Token scope

catalog:manage

Counts against

scorecards_per_scope

Send

Path
  • group_iduuidThe owning group's id
Body
  • applies_toobject1 field
    • annotationsobjectoptionalAnnotations a service must carry, as key–value pairs. Every entry must match; a service with extra annotations still matches.
  • campaign_activebooleanoptionalWhether to switch the campaign on from the start. Setting it to true requires the Owner role.
  • campaign_max_itemsint32optionalHow many work items the campaign may file each time it is switched on. Defaults to 200; at most 1,000.
  • checks[]object4 fields
    • configobjectoptionalThe check's own parameters, such as {"days": 90} for released_within. An unknown parameter is refused with a 400 rather than ignored.
    • creates_work_itembooleanoptionalWhether a failure opens a work item in the owning group's backlog. Off by default, and it takes effect only while the scorecard's campaign is active.
    • keystring
    • ruleobjectHow a check decides its answer.
  • descriptionobjectoptional
  • enabledbooleanoptional
  • levels[]object1 field
    • namestring
  • nameobjectoptionalDefaults to the slug.
  • slugstring

Refuses

  • 400Validation error, an unimplemented derived check, or a level requiring a check the scorecard does not carry
  • 401Unauthenticated — no credential, or an invalid one
  • 403Insufficient standing on the group, or Owner required to arm a campaign
  • 404No such group
  • 409The slug is already taken in this scope
  • 429The namespace is at its scorecard limit

Receive

The same shape get_scorecard returns — 17 fields. Go to it

Show it here
  • applies_toobject1 field
    • annotationsobjectoptionalAnnotations a service must carry, as key–value pairs. Every entry must match; a service with extra annotations still matches.
  • campaign_activebooleanWhether the scorecard's campaign is on. Checks that open work items on failure do so only while it is.
  • campaign_enforcedbooleanWhether the platform acts on campaign_active. Always true.
  • campaign_item_countint32How many work items the campaign has filed since it was last switched on. Read-only.
  • campaign_max_itemsint32How many work items the campaign may file each time it is switched on. The count starts from zero every time campaign_active is turned on.
  • checks[]object4 fields
    • configobjectoptionalThe check's own parameters, such as {"days": 90} for released_within. An unknown parameter is refused with a 400 rather than ignored.
    • creates_work_itembooleanoptionalWhether a failure opens a work item in the owning group's backlog. Off by default, and it takes effect only while the scorecard's campaign is active.
    • keystring
    • ruleobjectHow a check decides its answer.
  • created_atstring
  • created_byobjectoptional
  • descriptionobjectoptional
  • enabledboolean
  • levels[]object1 field
    • namestring
  • namestring
  • scopeobjectA group or a repository, whichever a setting belongs to.
  • scorecard_iduuid
  • slugstring
  • updated_atstring
  • versionint64The scorecard's revision, incremented on every change. Send it with an update to have the update refused with a 409 if someone changed the scorecard since you read it.
PATCH/catalog/scorecards/{scorecard_id}catalog:manage

Amend a scorecard

update_scorecardPATCH /catalog/scorecards/{scorecard_id}Open in the API reference

Token scope

catalog:manage

Send

Path
  • scorecard_iduuidThe scorecard's id
Body
  • applies_toobjectoptional
  • campaign_activeobjectoptionalSwitches the campaign on or off. Unlike every other field here, changing it requires the Owner role, because switching it on can file work items across every covered service.
  • campaign_max_itemsint32optional
  • checks[]object4 fields
    • configobjectoptionalThe check's own parameters, such as {"days": 90} for released_within. An unknown parameter is refused with a 400 rather than ignored.
    • creates_work_itembooleanoptionalWhether a failure opens a work item in the owning group's backlog. Off by default, and it takes effect only while the scorecard's campaign is active.
    • keystring
    • ruleobjectHow a check decides its answer.
  • descriptionobjectoptional
  • enabledobjectoptional
  • levels[]object1 field
    • namestring
  • nameobjectoptional
  • slugobjectoptional
  • versionint64optionalThe version you last read. When given and the scorecard has changed since, the update is refused with a 409.

Refuses

  • 400Validation error, or a level requiring a check the amendment removed
  • 401Unauthenticated — no credential, or an invalid one
  • 403Insufficient standing, or Owner required to change the campaign switch
  • 404No such scorecard, or it is not visible
  • 409The scorecard was modified by someone else

Receive

The same shape get_scorecard returns — 17 fields. Go to it

Show it here
  • applies_toobject1 field
    • annotationsobjectoptionalAnnotations a service must carry, as key–value pairs. Every entry must match; a service with extra annotations still matches.
  • campaign_activebooleanWhether the scorecard's campaign is on. Checks that open work items on failure do so only while it is.
  • campaign_enforcedbooleanWhether the platform acts on campaign_active. Always true.
  • campaign_item_countint32How many work items the campaign has filed since it was last switched on. Read-only.
  • campaign_max_itemsint32How many work items the campaign may file each time it is switched on. The count starts from zero every time campaign_active is turned on.
  • checks[]object4 fields
    • configobjectoptionalThe check's own parameters, such as {"days": 90} for released_within. An unknown parameter is refused with a 400 rather than ignored.
    • creates_work_itembooleanoptionalWhether a failure opens a work item in the owning group's backlog. Off by default, and it takes effect only while the scorecard's campaign is active.
    • keystring
    • ruleobjectHow a check decides its answer.
  • created_atstring
  • created_byobjectoptional
  • descriptionobjectoptional
  • enabledboolean
  • levels[]object1 field
    • namestring
  • namestring
  • scopeobjectA group or a repository, whichever a setting belongs to.
  • scorecard_iduuid
  • slugstring
  • updated_atstring
  • versionint64The scorecard's revision, incremented on every change. Send it with an update to have the update refused with a 409 if someone changed the scorecard since you read it.
DELETE/catalog/scorecards/{scorecard_id}catalog:manage

Delete a scorecard

delete_scorecardDELETE /catalog/scorecards/{scorecard_id}Open in the API reference

Token scope

catalog:manage

Send

Path
  • scorecard_iduuidThe scorecard's id

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Owner is required — a delete destroys every result keyed on it
  • 404No such scorecard, or it is not visible
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

No body.

GET/catalog/scorecards/{scorecard_id}/resultscatalog:read

List a scorecard's results, worst state first

list_scorecard_resultsGET /catalog/scorecards/{scorecard_id}/resultsOpen in the API reference

Token scope

catalog:read

Send

Path
  • scorecard_iduuidThe scorecard's id
Query
  • statestringoptionalFilter to one check state
  • check_keystringoptionalFilter to one check
  • service_idstringoptionalFilter to one service. summary is unaffected and stays scorecard-wide, so a per-service count is folded from items — which with this filter is that service's complete result set
  • limitint32optionalPage size (clamped)
  • next_tokenstringoptionalOpaque pagination cursor

Refuses

  • 400Invalid filter or cursor
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such scorecard, or it is not visible

Receive

  • items[]object15 fields
    • attested_atobjectoptional
    • attested_byobjectoptional
    • check_keystring
    • detailobjectoptional
    • evaluated_atstring
    • evidence_atobjectoptional
    • evidence_commit_shaobjectoptional
    • evidence_pipeline_idobjectoptional
    • evidence_run_idobjectoptional
    • expires_atobjectoptional
    • flappingbooleanoptionalWhether the check has changed state ten times today, after which further changes are not recorded until tomorrow. Read it as "this check is not trustworthy": a threshold set at the noise floor, or a test that is not deterministic.
    • observed_valuedoubleoptional
    • scorecard_iduuid
    • service_iduuid
    • stateexpiredfailingnot_applicablepassingstaleunknownWhat a check currently says about a service, listed worst first. failing: the check does not hold. stale: the evidence that used to prove it no longer does. expired: an attestation whose renewal window has passed. unknown: never evaluated. passing: the check holds. not_applicable: the check does not apply to this service, so it does not count against it.
  • next_tokenobjectoptional
  • summaryobject8 fields
    • scoredobject8 fields
      • by_levelobjectoptionalServices at each named level. Absent levels are absent keys, not zeroes.
      • checksobject6 fields
        • expiredint32
        • failingint32
        • not_applicableint32
        • passingint32
        • staleint32
        • unknownint32
      • totalint32
GET/catalog/services/{service_id}/scorecardscatalog:read

Every scorecard's verdict on one service

get_service_scorecardsGET /catalog/services/{service_id}/scorecardsOpen in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidThe service's id

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
  • 404No such service, or it is not visible

Receive

  • scorecards[]object10 fields
    • countsobject6 fields
      • expiredint32
      • failingint32
      • not_applicableint32
      • passingint32
      • staleint32
      • unknownint32
    • levelobjectoptionalThe highest level every required check satisfies, or null when the scorecard has no levels or the service reaches none of them.
    • namestring
    • scorecard_iduuid
    • slugstring
POST/catalog/services/{service_id}/checks/{scorecard_id}/{check_key}/attestcatalog:manage

Attest a check no machine can see

attest_service_checkPOST /catalog/services/{service_id}/checks/{scorecard_id}/{check_key}/attestOpen in the API reference

Token scope

catalog:manage

Send

Path
  • service_iduuidThe service's id
  • scorecard_iduuidThe scorecard's id
  • check_keystringThe check's key on that scorecard
Body
  • noteobjectoptional
  • statefailingpassingThe states an attestation may assert.

Refuses

  • 400The note is too long
  • 401Unauthenticated — no credential, or an invalid one
  • 403Insufficient standing on the declaring group, or the caller is not a person
  • 404No such service, scorecard, or check
  • 409The check is not attestable: a derived or evidence check is computed by the platform alone

Receive

No body.

Dependencies

3 endpoints

GET/catalog/services/{service_id}/dependenciescatalog:read

List the services this service depends on

list_service_dependenciesGET /catalog/services/{service_id}/dependenciesOpen in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidThe service's id

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
  • 404No such service, or it is not visible to the caller

Receive

  • edges[]object22 fields
    • confidencefloatHow certain the dependency is: 1.0 for anything the platform observed or a person stated, lower only for a derived one.
    • detailobject5 fields
      • formatobjectoptional
      • lockfile_pathobjectoptional
      • package_nameobjectoptional
      • resolvedobjectoptionalThe version the lockfile actually pins.
      • version_reqobjectoptionalWhat the manifest asked for, where the lockfile records it.
    • from_service_iduuid
    • kindapidatastoreotherpackageHow a service depends on the far end of a dependency — not what the far end is. The same library service can be consumed as a package by one service and called over HTTP by another.
    • observed_atobjectoptional
    • observed_repoobjectoptional
    • observed_shaobjectoptional
    • sourcedeclaredinferredobserved_lockfileobserved_publishHow a dependency came to be known, and therefore how far it can be trusted. A declared and an observed dependency between the same two services are reported separately, so a declaration no lockfile mentions, or a lockfile entry nobody declared, stands out.
    • toobject10 fields
      • display_namestring
      • externalbooleanoptional
      • group_iduuid
      • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
      • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
      • orphaned_atobjectoptional
      • ownerobjectoptional
      • service_iduuid
      • slugstring
      • sourceapideclaredinferredHow the catalog entry came to exist. Set by the platform, never accepted from a request. Any source other than inferred means a person has vouched for the entry.
  • hiddenint32Dependencies left out because you cannot see the service at the far end.
  • truncatedbooleanWhether the list stopped at its limit of 500 and more exist.
GET/catalog/services/{service_id}/dependentscatalog:read

List the services that depend on this service

list_service_dependentsGET /catalog/services/{service_id}/dependentsOpen in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidThe service's id

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
  • 404No such service, or it is not visible to the caller

Receive

The same shape list_service_dependencies returns — 3 fields. Go to it

Show it here
  • edges[]object22 fields
    • confidencefloatHow certain the dependency is: 1.0 for anything the platform observed or a person stated, lower only for a derived one.
    • detailobject5 fields
      • formatobjectoptional
      • lockfile_pathobjectoptional
      • package_nameobjectoptional
      • resolvedobjectoptionalThe version the lockfile actually pins.
      • version_reqobjectoptionalWhat the manifest asked for, where the lockfile records it.
    • from_service_iduuid
    • kindapidatastoreotherpackageHow a service depends on the far end of a dependency — not what the far end is. The same library service can be consumed as a package by one service and called over HTTP by another.
    • observed_atobjectoptional
    • observed_repoobjectoptional
    • observed_shaobjectoptional
    • sourcedeclaredinferredobserved_lockfileobserved_publishHow a dependency came to be known, and therefore how far it can be trusted. A declared and an observed dependency between the same two services are reported separately, so a declaration no lockfile mentions, or a lockfile entry nobody declared, stands out.
    • toobject10 fields
      • display_namestring
      • externalbooleanoptional
      • group_iduuid
      • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
      • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
      • orphaned_atobjectoptional
      • ownerobjectoptional
      • service_iduuid
      • slugstring
      • sourceapideclaredinferredHow the catalog entry came to exist. Set by the platform, never accepted from a request. Any source other than inferred means a person has vouched for the entry.
  • hiddenint32Dependencies left out because you cannot see the service at the far end.
  • truncatedbooleanWhether the list stopped at its limit of 500 and more exist.
GET/catalog/services/{service_id}/graphcatalog:read

Walk the dependency graph outward from a service

get_service_graphGET /catalog/services/{service_id}/graphOpen in the API reference

Token scope

catalog:read

Send

Path
  • service_iduuidThe service the walk starts from
Query
  • depthint32optionalHow many hops to follow (1-3, default 2)

Refuses

  • 400The depth is outside 1-3
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404No such service, or it is not visible to the caller

Receive

  • depthint32How many hops were followed: the requested depth, at most 3.
  • edges[]object5 fields
    • depthint32How many hops from the root service this dependency was reached at, from 1 to the graph's depth.
    • from_service_iduuid
    • kindapidatastoreotherpackageHow a service depends on the far end of a dependency — not what the far end is. The same library service can be consumed as a package by one service and called over HTTP by another.
    • sourcedeclaredinferredobserved_lockfileobserved_publishHow a dependency came to be known, and therefore how far it can be trusted. A declared and an observed dependency between the same two services are reported separately, so a declaration no lockfile mentions, or a lockfile entry nobody declared, stands out.
    • to_service_iduuid
  • hiddenint32Dependencies left out because you cannot see the service at the far end.
  • nodes[]object10 fields
    • display_namestring
    • externalbooleanoptional
    • group_iduuid
    • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
    • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
    • orphaned_atobjectoptional
    • ownerobjectoptional
    • service_iduuid
    • slugstring
    • sourceapideclaredinferredHow the catalog entry came to exist. Set by the platform, never accepted from a request. Any source other than inferred means a person has vouched for the entry.
  • rootobject7 fields
    • display_namestring
    • externalbooleanoptional
    • group_iduuid
    • kindapidatastorejoblibraryml_modelotherservicewebsiteWhat sort of thing a service is. The kind decides which scorecard checks apply: a library has no deployments, so a deployment check is not applicable to it rather than failing. For your own classifications, use annotations.
    • lifecycledeprecatedexperimentalproductionretiredWhere a service is in its life.
    • orphaned_atobjectoptional
    • ownerobjectoptional

Limits

Three of the catalog's caps move with your plan. Each is counted on the scope that holds the entry — the group for a service or a scorecard, the group or the repository for an environment — not across your whole organisation.

LimitFreeProTeamEnterprise
Services per scope2002,00020,000200,000
Environments per scope102550100
Scorecards per scope5153060

The services cap counts only entries a person declared — through the API, the CLI, the web app or a catalog file. The service created alongside each repository never counts against it, so a service limit can never be the reason a repository creation fails.

Variables are capped at 100 per environment on every plan, and that one is fixed rather than commercial: each secret is copied into the run for every node on every attempt, so a plan selling 500 of them would be selling a failure.

The rest are fixed bounds on the shape of a single entity:

BoundValue
Path globs per service, inclusions and exclusions together50
Links per service20
Published packages declared per service20
API specs declared per service20
Annotations per service, and the length of one value50, 500 characters
Declared dependencies per service50
Checks per scorecard, and levels per scorecard50 checks, 20 levels
Evidence-backed checks one repository may have evaluated by a single pipeline run5,000
Annotation matchers in one scorecard selector20
Services in one catalog file100
Catalog file size256 KiB
Lockfile size, and lockfiles read per push4 MiB each, 20 per push, 32 MiB in total
Paths per resolve request200
Dependency edges per read500
Dependency graph depth, and nodes returned3 hops, 200 nodes
Environment variable name, and value size128 characters, 4 KiB
Approvers, allowed refs, and allowed scopes on one environment50, 20, 100
Approvals an environment can require1 to 10
Wait timer after approvalup to 1 hour
Work items one campaign activation may file200 by default, 1,000 maximum
Recorded state changes per check per day10, after which the check is marked flapping
Attestation renewal window, and note lengthup to 3,650 days, 2,000 characters

Reaching a cap refuses the whole request with 429, and nothing is partially created. A create that would exceed the services limit writes no service and no bindings; a catalog file declaring more services than you have room for applies none of them and reports it on the commit status.

To raise a plan-backed limit, upgrade the plan, or — as an Owner of the group — ask for an increase on the group's own limits page, which records the request for review. The fixed bounds above are not raisable, and reaching one usually means the entity wants splitting: a service with more than fifty path globs is generally two services.

Troubleshooting

400 BAD_REQUESTmentioning a slug
Cause
The slug is not lowercase letters, digits and hyphens, 1–64 characters, without a leading or trailing hyphen
What to do
Rename it. Slugs appear in URLs, in files and in deployment identifiers, so the charset is deliberately narrow
400 INVALID_PATH_GLOB
Cause
A path binding carries an empty glob, or one that is only whitespace
What to do
Give it a pattern, written relative to the repository root and without a leading slash — services/checkout/**. One glob may be up to 500 characters; a longer one is refused as a constraint violation
400 SERVICE_REQUIRES_PATHS
Cause
A service that is not external has no including binding — either none at all, or nothing but exclusions
What to do
Add at least one binding that includes a path, or mark the entry external if the platform does not build it. An exclusion narrows a set; it cannot be the whole set. On an update the rule reads the bindings the service ends up with, so an update that leaves them alone never trips it
400 EXTERNAL_REQUIRES_NO_PATHS
Cause
An external entity was given a path binding
What to do
Remove the bindings, or untick external — an external entity is a name and an owner, with no code behind it
400 BAD_REQUESTon an environment, mentioning approvers
Cause
Protection requires approval but names no approvers, names more than 50, or names anyone or code owners — or required is outside 1 to 10
What to do
Name between one and 50 people, teams or roles, and require between one and 10 approvals. A gate nobody can open is refused at write time rather than discovered during an incident
404 NOT_FOUNDon an environment, naming a team or a group
Cause
An approver names a team, group or repository that is not in your organisation
What to do
Check the id. Approvers can only be people, teams and roles of the organisation the environment belongs to
400 BAD_REQUESTon an environment, mentioning allowed scopes
Cause
The allowlist was sent as an empty list, which means nobody rather than everybody
What to do
Omit the field entirely to let the declaring scope's subtree use the environment, or name at least one group or repository
400 BAD_REQUESTon a variable, naming the variable
Cause
The name is not shell-shaped, the value is over 4 KiB or holds a NUL, or the name is reserved by the platform
What to do
Rename it. Set a cloud region or role on the provider connection instead, where it is authorized
400 UNKNOWN_CHECKor 400 RESERVED_CHECK_KEY
Cause
A derived check names a key the platform does not implement, or an evidence check reuses a key the registry already owns
What to do
Pick a key from the check registry for a derived check, and a name of your own for an evidence one. Check keys are snake_case, up to 64 characters — has-owner and has_owner render identically in a list, so hyphens are refused rather than silently made a second thing
400 BAD_REQUESTon a level
Cause
A level requires a check the scorecard does not carry, usually because the amendment removed it
What to do
Remove the key from the level, or keep the check. A rung requiring a missing check is unreachable for ever
400 BAD_REQUESTon resolve
Cause
No path was given, or more than 200 were
What to do
Send between one and 200 paths per call, and batch a changed file set into one request
400 BAD_REQUESTon the graph
Cause
depth is outside 1–3
What to do
Ask for at most three hops. Beyond that, ask the reverse question about one dependency instead
400 BAD_REQUESTon a listing
Cause
An unrecognised filter value — a tier, kind, lifecycle, source or status that does not exist — or a cursor from a different query
What to do
Check the spelling against the values the card lists. A page size over the maximum is clamped rather than refused, but a bad filter is refused, because a caller who filters and gets everything back has been told something false
401 UNAUTHORIZED
Cause
No credential, an expired one, or a token that has been revoked
What to do
Sign in again, or mint a new token. Services, dependencies and scorecards read without one when the declaring group is public; environments, variables and deployments never do
403 FORBIDDENon a create where reads work
Cause
Declaring a service, environment or scorecard needs Maintainer on the owning group or repository
What to do
Ask an Owner or Maintainer to raise your access level on that scope
403 FORBIDDENreading an environment or a deployment in a group whose services you can browse
Cause
Environments, their variables and deployments have no public-read shortcut. They need Reporter on the owning scope, which services and scorecards do not
What to do
Ask to be added to the group or the repository as a Reporter. The service, its scorecards and its dependency edges keep answering in the meantime
403 FORBIDDENwhen naming an owner
Cause
You have Maintainer on the service's group but not on the group you named as owner
What to do
Ask a Maintainer of that team to raise your level there, or ask them to make the change. This check is what stops ownership being conferred on a team that never agreed to it
403 FORBIDDENon deleting an environment
Cause
Deleting an environment is Owner, one level above creating it. Deleting a service or a scorecard needs only Maintainer
What to do
Ask an Owner of the group to delete the environment, or use the reversible operation instead — retire a service, archive an environment, disable a scorecard
403 FORBIDDENon a production environment
Cause
Creating a production-tier environment, changing a tier, or changing protection at any tier is Owner
What to do
Ask an Owner. Maintainer covers everything else about an environment
403 FORBIDDENon a variable write
Cause
Writing and deleting environment variables is Owner, at every tier
What to do
Ask an Owner. Reading the list is Reporter, so you can still see which names exist
403 FORBIDDENon the campaign switch
Cause
Arming a campaign — moving campaign_active — is Owner even though the rest of the scorecard is Maintainer
What to do
Ask an Owner to arm it. Everything else about the scorecard, including how many items a campaign may file, is yours to edit
403 INSUFFICIENT_SCOPE
Cause
The token is valid but was minted without catalog:read or catalog:manage
What to do
Mint a new token carrying the scope. Scopes are fixed when a token is created and cannot be added to it afterwards
404 NOT_FOUNDon something you are sure exists
Cause
The declaring group is private and you are not a member, or the id is wrong
What to do
A private scope answers 404 rather than 403 so its existence stays private. Check your membership of the declaring group, then check the id
404 NOT_FOUNDnaming a repository you can see
Cause
Binding a repository to a service needs Maintainer on that repository, and the refusal is reported as an absence so it cannot be used to probe for repositories
What to do
Ask a Maintainer or Owner of that repository to raise your level there, or ask them to add the binding
404 NOT_FOUNDon a variable
Cause
The environment exists but holds no variable by that name
What to do
List the variables first. Names are case-sensitive
409 CONFLICTon a service create, or on renaming one
Cause
Another service in the organisation already holds that slug — possibly an orphaned one, which keeps its slug
What to do
Pick another slug, or find the holder and delete or rename it. Slugs are unique across the whole organisation, not per group. The same collision from a catalog file is reported on the commit status instead, and that message names the repository holding it
409 CONFLICTon an environment create
Cause
The scope already has an environment with that slug
What to do
Pick another slug, or edit the existing one. A repository may reuse a slug an ancestor group declares — that shadows it, deliberately
409 CONFLICTon a scorecard create
Cause
The group already has a scorecard with that slug
What to do
Pick another slug, or amend the existing one. A scorecard slug is unique on its own scope only, so a subgroup may reuse a name an ancestor uses — and both then grade the services below it
409 CONFLICTon an update, mentioning the version
Cause
Somebody changed the entry since you read it
What to do
Re-read it, reapply your change on top and save again. Nothing was overwritten
409 CONFLICTon deleting an environment
Cause
Something has been deployed to it, and deployment history is not erasable through a delete
What to do
Archive it instead. An archived environment leaves resolution while its history stays readable
409 CHECK_NOT_ATTESTABLE
Cause
The check is derived or evidence-backed, so the platform computes it
What to do
Attest only checks defined as attested. If the automated answer is wrong, fix the check's configuration or the fact behind it
429 TOO_MANY_REQUESTS
Cause
The scope reached its services, environments or scorecards limit, or the environment reached 100 variables
What to do
Nothing was created. Delete something you no longer need, upgrade the plan, or ask an Owner to request an increase
A pushed catalog file changed nothing
Cause
It was pushed to a branch that is not the default one. Files are validated everywhere and applied only on the default branch
What to do
Merge it. The commit status on your branch already tells you whether it would apply cleanly
A red gitmoap/catalog commit status
Cause
The file failed validation — an unknown kind, an owner path that does not resolve, a slug another repository holds, or a bound exceeded
What to do
Read the status text; it names the line or the holder. Nothing was applied, so fixing the file and pushing again is the whole repair
A service shows as orphaned
Cause
Its declaration disappeared from the catalog file — usually a bad merge, a renamed file, or a slug edited in place
What to do
Restore the declaration and the same service is revived with all its history. A slug edit is a delete plus a create, so rename slugs through the API instead
The catalog only updates when somebody pushes directly
Cause
It should not — a merge request landing on the default branch syncs the same way a push does
What to do
If a merge changed a catalog file and the entry did not move, check that the file is at .gitmoap/catalog.yaml, or the same path with a .yml extension, and that only one of the two exists; both at once is a validation error naming them
The deployment board is empty for a service that deploys
Cause
The pipeline's deploy node names no environment, or no service in a repository holding more than one
What to do
Add environment: and service: to the node. An ambiguous deploy is a resolution error rather than a guess, because a mis-attributed production deployment is the worst thing this can produce
Drift shows no commit count
Cause
The deployed commit can no longer be read — a force-push rewrote it, or the repository moved
What to do
Deploy again to re-anchor the comparison. The rest of the row is still accurate
A dependency edge is missing
Cause
The lockfile is over 4 MiB, the repository holds more than 20 readable lockfiles or more than 32 MiB of them in total, or the format is one of the ones not read — only Cargo.lock and package-lock.json are parsed
What to do
Check the file's size and name. An unread lockfile is named in a warning on the catalog commit status, and it leaves the edges it established untouched rather than deleting them — so nothing was lost, but nothing was learned either. For any other ecosystem, declare the edge in depends_on:
A dependency read reports hidden edges
Cause
Some far ends are in groups you cannot see, so those edges were dropped
What to do
Ask for access to the far group, or read the count as what it is — a total that says how much of itself is missing
A check sits at unknown
Cause
Nothing has evaluated it yet: an evidence check whose pipeline has not run, a derived check awaiting its next recompute, or a repository over the 5,000 evidence-binding ceiling, whose checks a run skips whole rather than in part
What to do
Run the pipeline that produces the evidence. Derived checks recompute on the activity that could change them, and some also sweep on a schedule. If every evidence check in one repository is stuck, narrow the selectors of the scorecards grading its services
A check went stale
Cause
The pipeline that last proved it ran again and produced no report for it this time — a deleted test, a renamed report, a step that was skipped
What to do
Restore the report to that pipeline's output, or point the binding at the pipeline that actually produces it. An unrelated pipeline running never stales a check, so the run to look at is the one that used to prove it
A library is marked failing on a deployment check
Cause
A check is excused only for a service the platform holds no such fact about, and one deployment ever recorded is enough to make the question apply again
What to do
Narrow the scorecard's selector so it grades only the kinds you mean, or drop the check from the level that requires it. Changing kind alone will not fix it — kind decides which scorecards select the service, not which checks apply once one has
A step cannot see a variable you set
Cause
The name is reserved, or a provider connection or a group or repository pipeline variable sets the same name with a different secrecy
What to do
Rename the variable. A reserved name keeps its value but loses its export; a secrecy collision fails the run and names both sources
A run is refused, naming a step, a variable and this environment
Cause
The step's configuration reads that variable with {"$var": …}, and the environment does not declare it — or declares it as a secret, which configuration cannot read
What to do
Add the variable to the environment, without the secret flag if the configuration should read it. A secret stays readable inside the step as $NAME
A deploy waits and never starts
Cause
The environment requires approval and not enough of its approvers have approved, or the wait timer has not elapsed
What to do
Ask the named approvers to decide it — a gate needing two approvals waits for two different people. Being an Owner of the organisation does not confer approval — being named does, and that is the point of the gate
  • Pipelinesdefining a run, and the nodes that name a service and an environment
  • Using providersthe steps a pipeline runs, and the connections that hold their credentials
  • Cloud authentication from a pipelinereaching your own cloud with a short-lived token whose claims name the environment being deployed to
  • Repository rulesprotecting a branch, and the CODEOWNERS file that wins over the catalog wherever it matches
  • Groupsthe namespaces services are declared in, the teams that own them, and where usage and limits live
  • Repositoriescreating one, which is also what mints its first service
  • Work itemsthe backlog a failing check files work into
  • Linking work to codethe anchors behind a service's technical-debt rollup
  • Package registriesthe npm, Cargo and PyPI registries whose publishes and lockfiles become dependency edges
  • Access tokensminting one that carries catalog:read or catalog:manage
  • Billing and plansupgrading, and asking for a limit increase