Why publish API contracts in the Backstage API catalog
Publish API contracts in the Backstage API catalog when teams cannot find the current definition of an API or cannot tell who depends on it. Each API becomes an API entity in the software catalog, and that entity holds the contract itself along with its owner and lifecycle. Consumers then read the contract from the same place where they look up the owning team, instead of asking in chat or reading an outdated wiki page. The entity has two required fields, spec.type and spec.definition. The type tells Backstage which kind of contract it is (openapi, asyncapi, graphql or grpc), and the definition holds the document. Alongside these, the entity carries spec.owner and spec.lifecycle, which show who is accountable and whether the API is experimental, production or deprecated.
A platform team should model APIs before it builds the rest of the portal, because the relations drive most of the value. Components declare spec.providesApis and spec.consumesApis, and Backstage generates providesApi and consumesApi relations from them. Once those links exist, a team can see which services would be affected by a change to a contract, and an incident responder can find the provider of a failing dependency. Decide early which team owns each API, which lifecycle values your organization will use, and whether the definition will be stored in the service repository or fetched from a central location. Confirm the exact fields and accepted spec types against the descriptor format for your Backstage release before you write a catalog template around them.
Which API spec type and definition source fits each service
This table compares the four well-known API entity types and the three ways to supply a definition: inline text, a file in the same repository, or a remote URL loaded with $text. Read each row against the service's contract format and the team that will maintain the file. The main takeaway is that the spec type is set by the protocol, while the definition source is set by who owns the file and how often it changes.
| Option | Fits when | What the owning team needs |
|---|---|---|
| openapi (OpenAPI 2 or 3) | A REST or HTTP service with a documented contract | A maintained OpenAPI 2 or 3 document that matches the deployed service version |
| asyncapi (AsyncAPI 2 or 3) | An event-driven service publishing or consuming messages on topics or queues | An AsyncAPI 2 or 3 document listing channels, message payloads and broker details |
| graphql | A GraphQL schema exposed to clients through one endpoint | The schema definition kept in step with resolver changes and deprecations |
| grpc (Protocol Buffers) | A service defined by .proto files for internal or cross-team RPC | The .proto contract, plus agreement on where the shared files live and how they are versioned |
| Inline text in the descriptor | A short, stable definition kept next to the component entity | Edits to the catalog file whenever the contract changes, with review in the same pull request |
| File in the same repository | The service owns its contract and the definition moves with the code | A relative file reference and a pipeline step that keeps the file current with each release |
| Remote URL loaded with $text | The definition lives in a shared spec repository, a contract registry or a published artifact | The host added to backend.reading.allow, reachable from the Backstage backend, and an agreed owner for the file |
| Owning team effort | Inline and same-repo sources need little setup; remote sources add access and ownership work | Confirm with your team how often contracts change and who reviews spec updates before choosing a source |
How $text embeds a remote definition and where backend.reading.allow applies
The $text reference in an API entity's spec.definition tells the catalog to load a file from a URL and embed its contents as a string inside the entity. The definition is therefore stored in the catalog as a copy, not as a live link. Your OpenAPI, AsyncAPI, GraphQL or Protobuf file stays in the repository or host where its owners maintain it, and the catalog holds the version it last read. Teams should decide up front which repository is the source of truth for each spec and which path the $text reference points to, so that a reviewer knows where to change a contract.
The read allow list is the control that decides where the backend may fetch from. Hosts that the configured integrations already cover, such as your source control provider, are readable through those integrations. Any other host has to be listed under backend.reading.allow in the app configuration, or the read fails. Confirm the exact matching rules, including how hosts, paths and wildcards are interpreted, in the documentation for your Backstage version before you write the list, because a rule that is too broad widens what the backend can fetch and a rule that is too narrow produces read errors that look like broken specs.
Operationally, a spec that changes at its source does not appear in the portal immediately. The catalog picks up the new content on the next refresh of the entity, so the lag depends on your refresh schedule and the provider configuration, which should be confirmed in your environment. A missing allow-list entry fails the read on the same path, and the entity may show an error or keep stale content depending on how the processor reports it. Plan a check that compares the catalog copy with the source after each contract change, and include the allow-list entry in the same review as the first spec from a new host.
Setting up API entities from definition to linked catalog
This sequence turns API definition files into API entities in the Backstage catalog, then connects each service to the APIs it provides and consumes. The result is a catalog where every API page renders its spec and the dependency graph shows who builds and who calls each API.
Choose the spec type for each service
Platform engineers and service owners agree which definition format each API uses: openapi for OpenAPI 2 or 3 specs, asyncapi for AsyncAPI 2 or 3 event contracts, graphql for GraphQL schemas, and grpc for Protocol Buffers definitions. Record the spec version each team actually maintains, since the entity must match the file it points to. Done when every API in scope has one agreed type and a named owner, and the list is reviewed against the services teams plan to onboard first.
Write the API entity descriptors
For each API, a catalog owner writes an API entity descriptor with apiVersion, kind set to API, metadata, and spec fields. Set spec.type to the chosen type and spec.lifecycle, spec.owner, and spec.definition to the contract. Where the definition lives in a separate file or URL, reference it with $text so Backstage embeds the file contents as a string. Check: run the descriptor through your catalog validation in CI, and confirm the file parses before it goes into a repository.
Allow external definition hosts in the backend
If any definition is loaded from a host that is not already covered by the configured integrations, the backend administrator adds that host to backend.reading.allow in the app configuration. Keep the list to the hosts your teams actually use, and record why each one is there. Check: restart the backend, then confirm the catalog processes the descriptor without a reading error and the embedded definition appears on the API page.
Register the descriptors with the catalog
The catalog administrator adds the API descriptors to the catalog through the location or provider configuration your organization uses, so they are ingested with the rest of the catalog. Confirm the ingestion path matches the repository and branch that owns each descriptor. Check: each API appears in the catalog with its type, owner and lifecycle, and the API page shows the rendered definition for OpenAPI, AsyncAPI, GraphQL or gRPC.
Add provided APIs to each component
Each owning team edits the descriptor of the component that implements an API and lists the API under spec.providesApis. This is typically done in the same repository as the service so the declaration changes with the code. Check: after ingestion, the component page shows the API under its provided APIs, and the catalog graph draws a providesApi relation from the component to the API entity.
Add consumed APIs to calling components
Teams that call an API add it to spec.consumesApis in the descriptor of each calling component. Start with the callers that your architecture review already knows about, then extend the list as teams find dependencies. Check: the consuming component shows the API under consumed APIs, and the graph draws a consumesApi relation pointing to the same API entity that the provider declared, which confirms both sides reference one entity name.
Verify the linked catalog end to end
A catalog owner reviews the finished set of relations with the service owners. Open each API page, confirm the definition renders, then walk the graph from a provider to its consumers and back. Mismatched names usually show up here as a missing relation or a duplicate API entity, so fix the descriptor that is out of line and reingest. Done when every in-scope API has a rendered definition, at least one provider, and the consumers your teams have confirmed.
Base URLs, versions and what to keep out of the catalog
API entities outlive the services behind them, so the choices about base URLs, versions, lifecycle and secrets decide whether the catalog stays trustworthy. Each card below is a decision your team should make before the first definition is published. Settle them once, write them into your descriptor conventions, and check them during review.
Keep base URLs out of the definition
A base URL belongs in environment configuration, not in the spec file, because the same OpenAPI or AsyncAPI document often describes the API in development, staging and production. Hardcoding a production host inside the definition makes every consumer's generated client point at the wrong place after a promotion. Decide which values the spec may carry, which ones your deployment pipeline injects, and who reviews changes to either. Confirm how your chosen tooling and Backstage plugins read server URLs from each spec before you standardize on one approach.
One API entity per major version
Consumers pin to a major version, so a v1 and v2 that share one entity hide breaking changes behind a single page. Publishing each major version as its own API entity lets teams see which consumers still depend on the old contract and retire it deliberately. Name entities consistently, for example by including the major version, and link the versions to each other in the description. Confirm your naming convention with the teams that will search for these APIs, because the names become the identifiers other entities reference.
Use lifecycle values for deprecated APIs
An API entity carries a lifecycle value in its metadata, and marking a retiring API as deprecated keeps it discoverable while signaling that new consumers should not start on it. Removing the entity instead erases the record that existing consumers still rely on, and the providesApi relations that point at it. Agree on the lifecycle values your organization uses, the criteria for moving an API between them, and the date by which a deprecated entity is removed. Confirm the exact lifecycle keywords against the descriptor format reference for your Backstage version.
Keep secrets and internal hosts out
Definitions are readable by anyone who can view the catalog, so they must not contain API keys, tokens, internal-only hostnames, or example credentials copied from a working request. Embedding a definition with $text also copies whatever the remote file contains into the catalog, so a file that changes later can change what the portal displays. Add a review step or automated scan for definitions before they merge, and confirm which hosts appear in the backend.reading.allow list because each one is a trusted source for catalog content.
Assign an owner for shared APIs
A shared API used by several teams needs one owning team recorded in the entity, with the spec.owner field set to a group or user from the catalog. Without a clear owner, change requests stall, deprecation notices reach no one, and the entity drifts from the running service. Decide whether consumers may propose changes, how the owner approves them, and how ownership transfers when a team reorganizes. Confirm that the owner references resolve to real catalog entries, since an unresolved owner shows up as an orphan in review.
Questions teams ask before publishing API definitions
How much effort does publishing API definitions take for one team?
For one team with a handful of services, the work is mostly locating existing specs, adding an API entity next to each service, and agreeing where the definition file lives. The effort grows with how many specs are missing, outdated or stored in places the catalog cannot read. Most of the time goes into deciding the owner, lifecycle and base URL for each API rather than writing the YAML itself. Ask for an inventory of which services already have OpenAPI, AsyncAPI, GraphQL or gRPC files before estimating anything.
How does the effort change for a catalog of hundreds of services?
At that size the cost comes from consistency rather than from any single entry. Each service needs a reviewed owner, a valid spec type, a correct definition source and clear provider and consumer links, and those rules must hold across every team. Teams usually need a shared descriptor convention, a validation step in CI, and an ingestion approach that does not depend on hand edits. Ask any partner how they would enforce the descriptor format across all teams and how they would handle specs that fail validation.
What does the work need from service owners?
Service owners must confirm which spec version their service actually serves, which environments and base URLs consumers should call, and who is accountable when the contract changes. They also need to say which other services call their API so that consumer relations can be recorded. Without these answers, the catalog shows definitions that do not match production. Plan a short review with each owner before the definition is published, and assign one named owner per API entity.
Can our own engineers publish API definitions without outside help?
Yes, a team that already runs Backstage and knows its own services can usually publish the first set of API entities alone. The harder parts are the external hosts, the reading allow list and the consumer mapping, which need someone who can review network paths and access controls. Outside help is most useful for setting conventions, reviewing the first few entries and writing the validation checks. Decide early which of these tasks your team will own after the pilot.
How long does a first pilot usually take?
A pilot covering a small set of services with existing specs is typically a matter of weeks, but the exact duration depends on how many owners must be consulted and how many definitions need fixing first. Pilots slow down when specs live in private repositories that need new integrations, or when the team has not agreed on naming and ownership. Ask any provider for a written timeline tied to a named list of services, and check whether it includes time for owner review.
What drives the cost of this work up or down?
The main drivers are the number of APIs, how many spec types are involved, the quality of existing definitions, and how many hosts sit outside the configured integrations. Mixed estates with gRPC and GraphQL alongside REST usually need more review than a single spec type. Costs fall when specs are already validated, live in source control the catalog can read, and have clear owners. When requesting a quote, ask what is one-time setup, what recurs as specs change, and who maintains the validation rules afterward.
Which parts of the setup can create security or access risk?
Adding external hosts to the reading allow list widens what the backend can fetch, so each host should be reviewed for ownership, transport security and whether it should be reachable at all. Definitions should come from sources the organization controls, and base URLs should point to the intended environment rather than to internal or test endpoints. Confirm these checks with your security team before the first external host is added. Backstage's own documentation on the descriptor format is the reference for the fields involved.
How DeployOpen scopes an API catalog rollout
Scoping starts with a written inventory of existing API definitions, not with the portal. We list every OpenAPI, AsyncAPI, GraphQL and Protocol Buffers file your teams already maintain, note where each lives (a repository, a gateway, a schema registry or a wiki), and record who owns it. That inventory sets the spec types the first release must support, and it shows which definitions are generated from code and which are hand-written. Generated files change with each build, so the scope has to decide whether the catalog reads them from the build output or from a fixed URL. Engagements are fixed-scope: the inventory, chosen spec types, sources, pilot services and handover deliverables are agreed in writing before work starts, and a quote follows from that written scope.
The scope then fixes the allow-list review with your security team. Any definition loaded with $text from a host outside the configured integrations must be added under backend.reading.allow, so each host needs an owner, a reason and a decision on whether it is internal only. We confirm with your security team which hosts are approved, whether those hosts require credentials, and how changes to the list go through review. Pilot services are then chosen to cover each spec type in use, one producer and its consumers per type, so the team can check providesApi and consumesApi relations and base URLs before the rest of the estate follows. Your team should confirm which pilots carry real traffic and which teams will sign off on the results.
Handover is the final scoped deliverable: a written descriptor standard covering required spec.type and spec.definition values, naming, lifecycle and ownership fields, and the rule for when a component declares spec.providesApis or spec.consumesApis. The owning teams keep that standard after the engagement. Ongoing support, upgrades of the catalog and continuous testing are available as a retainer, which we would scope separately from the fixed-scope build.

