What this example shows
A directed graph of HTTP / SDK dependencies between services, with edge labels showing the actual endpoint or SDK call rather than abstract “depends on” arrows. The “Web App” and “Mobile App” hit the gateway, the gateway fans out to auth / users / orders / catalog APIs, internal services call each other (orders → billing, orders → users, catalog → search), and external SDK integrations (Stripe, SendGrid) sit at the edge of the graph.
When to use it
When your platform has more than ~10 services and “who calls whom” is no longer something a single engineer holds in their head. The diagram is invaluable for: scoping deprecation work (who’d be affected if we sunset Catalog?), auditing third-party data exposure (which services hand data to which vendors?), incident scoping (when Billing is down, what else fails?), and onboarding (here’s the call graph; here’s the entry points).
What the agent does
The agent scans the OpenAPI specs in your repo (and / or grep for SDK imports - import Stripe from "stripe"), builds the directed call graph, then applies typed operations to keep the persisted scene current. New endpoints become new edge labels; new services become nodes; deprecated endpoints disappear from the graph. The diagram is rebuilt on every push that touches an OpenAPI spec or an SDK import.
Edge labels show the actual endpoint where space allows (“POST /v1/login”, “GET /users/:id”) rather than abstract direction. For high-cardinality calls (e.g. dozens of REST endpoints between gateway and a single service), the agent collapses them to a representative (“REST”) to keep the diagram readable.
What the output includes
- A directed call graph with stable node positioning across runs (same service has the same on-screen location every time).
- Edge labels showing endpoint paths or SDK names - readers learn the integration shape from the diagram alone.
- External systems (Stripe, SendGrid, etc.) visually distinct via the
externalSystemnode type. - Auto-layout via
layoutStrategy: { algorithm: "hierarchical", direction: "LR" }- the agent never hand-positions; the engine figures it out. - A revision history showing how the API surface evolved (which endpoints existed when).
Rendered diagram
Built by Zindex from the canonical scene below. Open in Playground to swap themes
(clean / dark / blueprint / sketch), or POST the scene to /v1/scenes/render
with format: "png" for a rasterised version.
Scene JSON
Raw
The canonical DSP scene used to render the diagram above. Drop into the
Playground or POST to /v1/scenes/render to
reproduce.
{
"schemaVersion": "0.1",
"diagramFamily": "architecture",
"scene": {
"id": "api-dependency-map",
"title": "API Dependency Map",
"units": "px",
"canvas": {
"width": 1200,
"height": 600
}
},
"layoutStrategy": {
"algorithm": "hierarchical",
"direction": "LR",
"nodeSpacing": 60,
"rankSpacing": 130
},
"elements": [
{
"id": "web",
"kind": "node",
"nodeType": "service",
"shape": "roundedRect",
"label": "Web App",
"icon": "lucide:monitor"
},
{
"id": "mobile",
"kind": "node",
"nodeType": "service",
"shape": "roundedRect",
"label": "Mobile App",
"icon": "lucide:smartphone"
},
{
"id": "gateway",
"kind": "node",
"nodeType": "service",
"shape": "roundedRect",
"label": "API Gateway",
"icon": "lucide:network"
},
{
"id": "auth_api",
"kind": "node",
"nodeType": "service",
"shape": "roundedRect",
"label": "Auth API",
"icon": "lucide:lock"
},
{
"id": "users_api",
"kind": "node",
"nodeType": "service",
"shape": "roundedRect",
"label": "Users API",
"icon": "lucide:users"
},
{
"id": "orders_api",
"kind": "node",
"nodeType": "service",
"shape": "roundedRect",
"label": "Orders API",
"icon": "lucide:package"
},
{
"id": "catalog_api",
"kind": "node",
"nodeType": "service",
"shape": "roundedRect",
"label": "Catalog API",
"icon": "lucide:boxes"
},
{
"id": "billing_api",
"kind": "node",
"nodeType": "service",
"shape": "roundedRect",
"label": "Billing API",
"icon": "lucide:credit-card"
},
{
"id": "search_api",
"kind": "node",
"nodeType": "service",
"shape": "roundedRect",
"label": "Search API",
"icon": "lucide:search"
},
{
"id": "stripe",
"kind": "node",
"nodeType": "externalSystem",
"shape": "roundedRect",
"label": "Stripe",
"icon": "lucide:external-link"
},
{
"id": "sendgrid",
"kind": "node",
"nodeType": "externalSystem",
"shape": "roundedRect",
"label": "SendGrid",
"icon": "lucide:mail"
},
{
"id": "e_web_gw",
"kind": "edge",
"from": {
"elementId": "web"
},
"to": {
"elementId": "gateway"
},
"label": "REST",
"router": "orthogonal"
},
{
"id": "e_mob_gw",
"kind": "edge",
"from": {
"elementId": "mobile"
},
"to": {
"elementId": "gateway"
},
"label": "REST",
"router": "orthogonal"
},
{
"id": "e_gw_auth",
"kind": "edge",
"from": {
"elementId": "gateway"
},
"to": {
"elementId": "auth_api"
},
"label": "POST /v1/login",
"router": "orthogonal"
},
{
"id": "e_gw_users",
"kind": "edge",
"from": {
"elementId": "gateway"
},
"to": {
"elementId": "users_api"
},
"label": "GET /v1/users",
"router": "orthogonal"
},
{
"id": "e_gw_orders",
"kind": "edge",
"from": {
"elementId": "gateway"
},
"to": {
"elementId": "orders_api"
},
"label": "REST",
"router": "orthogonal"
},
{
"id": "e_gw_catalog",
"kind": "edge",
"from": {
"elementId": "gateway"
},
"to": {
"elementId": "catalog_api"
},
"label": "REST",
"router": "orthogonal"
},
{
"id": "e_orders_billing",
"kind": "edge",
"from": {
"elementId": "orders_api"
},
"to": {
"elementId": "billing_api"
},
"label": "POST /charge",
"router": "orthogonal"
},
{
"id": "e_orders_users",
"kind": "edge",
"from": {
"elementId": "orders_api"
},
"to": {
"elementId": "users_api"
},
"label": "GET /users/:id",
"router": "orthogonal"
},
{
"id": "e_catalog_search",
"kind": "edge",
"from": {
"elementId": "catalog_api"
},
"to": {
"elementId": "search_api"
},
"label": "POST /index",
"router": "orthogonal"
},
{
"id": "e_billing_stripe",
"kind": "edge",
"from": {
"elementId": "billing_api"
},
"to": {
"elementId": "stripe"
},
"label": "Stripe SDK",
"router": "orthogonal"
},
{
"id": "e_orders_sendgrid",
"kind": "edge",
"from": {
"elementId": "orders_api"
},
"to": {
"elementId": "sendgrid"
},
"label": "SendGrid SDK",
"router": "orthogonal"
}
]
} Agent workflow
Maintain a current diagram of which services expose, consume, and depend on which APIs by parsing OpenAPI specs and SDK imports across the repository, then patching the persisted Zindex scene each time a service ships.
Inputs
- Glob of OpenAPI spec files in the monorepo (e.g. services/*/openapi.yaml)
- Glob of source files that import generated SDKs (used to derive consumer→provider edges)
- Existing Zindex scene id (stored as a repo secret)
- Zindex API key with scene-write scope
Outputs
- Updated persisted scene with one node per service and one edge per cross-service call
- Rendered SVG showing the current API topology
- Revision diff highlighting newly added consumers / providers since the last run
- Optional: PR comment when the topology changes meaningfully (new service added, edge removed)
- 01
Create or fetch the persisted scene
On first run, create a scene with hierarchical LR layoutStrategy. On subsequent runs, fetch the existing scene by id to read the current revision and elements.
- 02
Parse OpenAPI specs
Walk every services/*/openapi.yaml in the monorepo. Each spec becomes a service node; emit operationIds + paths so they can later become edge labels (e.g. 'POST /v1/login'). Do not parse free-text README links - the spec is the source of truth.
- 03
Derive consumer→provider edges
Scan source files for imports of generated SDKs (e.g. `import { usersClient } from '@acme/sdk-users'`). Each import is evidence that the importing service consumes the imported API. Map the SDK package name back to the spec → an edge from consumer to provider, labeled with the most-frequently-called operationId.
- 04
Diff parsed topology against the persisted scene
Compare the parsed (services, edges) tuple to what's currently in the scene. New services become createNode ops; removed services become deleteElement ops; changed labels become updateEdge ops. Always reuse stable element ids (the service name) so a renamed label stays on the same edge.
- 05
Apply the operation batch
Send the diff as one applyOps batch with errorPolicy=allOrNothing. Reuse stable element ids - `users_api`, `orders_api`, `e_orders_users` - so renames update the existing element rather than create a duplicate.
- 06
Validate the new revision
Confirm the topology is still valid: every edge endpoint resolves to a node, no duplicate edges between the same pair without distinct labels.
- 07
Render the updated diagram
Render to SVG. The hierarchical LR layout naturally separates clients (left), gateway (centre), and provider APIs (right); cloud-icon services render icon-only and read cleanly inside the layout.
- 08
Publish the rendered SVG to the docs site
Upload the rendered SVG to the docs site (or commit to the repo's docs/ directory). Optionally, when the topology diff contains added or removed services, post a Slack message to the platform channel so the team is aware of the new dependency.
MCP recipe
For agents using Model Context Protocol. The tool sequence below matches the workflow steps; copy the prompt as a system message.
Tool sequence
- 01
dsp_create_sceneCreate or fetch the persisted scene - 02
dsp_get_sceneDiff parsed topology against the persisted scene - 03
dsp_apply_opsApply the operation batch - 04
dsp_validate_sceneValidate the new revision - 05
dsp_render_sceneRender the updated diagram
Unique tools used: dsp_create_scene, dsp_get_scene, dsp_apply_ops, dsp_validate_scene, dsp_render_scene.
Copyable agent prompt
Drop this verbatim into a system prompt for an MCP-connected agent.
The Zindex MCP server (@zindex-ai/mcp, configured with a
ZINDEX_API_KEY environment variable - setup guide)
exposes the tools the prompt references.
You are an automated platform-documentation agent. Your job is to keep an API dependency-map diagram of an organisation's microservices in sync with the actual code, by parsing OpenAPI specs and SDK imports across the monorepo on a schedule (and on every PR that touches a service spec).
The persisted Zindex scene id is `${SCENE_ID}`; it already exists. Treat it as the canonical, mutable, immutable-revisioned source of truth for the API topology. Each run computes the smallest valid set of typed operations that move the scene from its current revision to one that matches the current code.
Workflow on every run:
1. Walk every `services/*/openapi.yaml` in the monorepo. Each spec corresponds to one service node - use the spec's `info.title` (or the directory name) as the stable element id. Capture the operationIds and paths so they can become edge labels.
2. Walk every source file in the monorepo and look for imports of generated SDK packages (`@acme/sdk-users`, `@acme/sdk-orders`, …). Each import is evidence that the importing service consumes the imported API. Build a `(consumer_service_id, provider_service_id, operationId)` tuple for the most-frequently-called operation between each pair.
3. Call `dsp_get_scene({ sceneId: "${SCENE_ID}" })` to read the current revision and elements. Diff what you parsed against what is persisted: new services → `createNode`, removed services → `deleteElement`, new edges → `createEdge`, removed edges → `deleteElement`, changed edge labels → `updateEdge`.
4. Call `dsp_apply_ops` with one batch. Set `errorPolicy: "allOrNothing"`. Reuse stable element ids - `users_api`, `orders_api`, `e_orders_billing` - so a renamed service or relabelled edge stays on the same element rather than producing a delete+create. Pass a meaningful `revisionMessage` like "add billing-api spec; new e_orders_billing edge from POST /charge".
5. Call `dsp_validate_scene` and resolve any `EDGE_LABEL_SUPPRESSED_REDUNDANT` and `LABEL_DUPLICATION_DETECTED` warnings. (`CANVAS_AUTO_EXTENDED` is informational - let the layout engine size the canvas as the topology grows.) `EDGE_COLUMN_NOT_FOUND` should not appear here; if it does, an edge is incorrectly using ER-family `column` anchors and needs `from/to.elementId` only.
6. Call `dsp_render_scene({ format: "svg", theme: "clean" })` and publish the rendered SVG to the docs site (or commit it to `docs/architecture/api-topology.svg`). The watermark stamps scene-id + revision + date, so the published artifact is traceable back to the persisted scene.
7. Call `dsp_diff_scene({ from: PREV_REVISION, to: NEW_REVISION })`. If the diff contains added or removed services (new providers or consumers), post a short Slack message to the platform channel summarising the change so the team is aware of the new cross-service dependency.
Hard rules: never hand-edit the rendered SVG; always edit the scene with `dsp_apply_ops` and re-render. Never regenerate the scene from scratch; always patch with stable ids - the topology is a long-lived, evolving graph, and stable ids are what make revision history meaningful. Treat the OpenAPI specs as the source of truth; if a spec contradicts a free-text comment elsewhere, follow the spec.
HTTP API recipe
For agents/devs not using MCP. Set $ZINDEX_API_KEY in the
Authorization header on authenticated calls. Stateless endpoints
(/v1/scenes/render, /v1/scenes/validate,
/v1/scenes/normalize) need no key. Full reference: API endpoints, OpenAPI spec.
- 01
POST/v1/scenesOn first run, create a scene with hierarchical LR layoutStrategy. On subsequent runs, fetch the existing scene by id to read the current revision and elements.
Example response
{ "sceneId": "sc_api_topo", "revision": 1 } - 02
GET/v1/scenes/${SCENE_ID}Compare the parsed (services, edges) tuple to what's currently in the scene. New services become createNode ops; removed services become deleteElement ops; changed labels become updateEdge ops. Always reuse stable element ids (the service name) so a renamed label stays on the same edge.
Example response
{ "sceneId": "sc_api_topo", "revision": 47, "elements": ["..."] } - 03
POST/v1/scenes/${SCENE_ID}/opsSend the diff as one applyOps batch with errorPolicy=allOrNothing. Reuse stable element ids - `users_api`, `orders_api`, `e_orders_users` - so renames update the existing element rather than create a duplicate.
Example response
{ "sceneId": "sc_api_topo", "revision": 48, "applied": 6 } - 04
POST/v1/scenes/validateConfirm the topology is still valid: every edge endpoint resolves to a node, no duplicate edges between the same pair without distinct labels.
- 05
POST/v1/scenes/${SCENE_ID}/renderRender to SVG. The hierarchical LR layout naturally separates clients (left), gateway (centre), and provider APIs (right); cloud-icon services render icon-only and read cleanly inside the layout.
Example request body
{ "format": "svg", "theme": "clean" }
Operations
Raw
The typed-operation envelope that builds this scene from empty.
POST to /v1/scenes/:id/ops after creating a scene, or
pass to dsp_apply_ops. Each op carries a stable
id so subsequent runs can update the same elements
instead of regenerating.
{
"schemaVersion": "0.1",
"errorPolicy": "allOrNothing",
"revisionMessage": "Initial API dependency map: clients, gateway, internal APIs, external systems",
"ops": [
{
"op": "createNode",
"id": "web",
"nodeType": "service",
"shape": "roundedRect",
"label": "Web App",
"icon": "lucide:monitor"
},
{
"op": "createNode",
"id": "mobile",
"nodeType": "service",
"shape": "roundedRect",
"label": "Mobile App",
"icon": "lucide:smartphone"
},
{
"op": "createNode",
"id": "gateway",
"nodeType": "service",
"shape": "roundedRect",
"label": "API Gateway",
"icon": "lucide:network"
},
{
"op": "createNode",
"id": "auth_api",
"nodeType": "service",
"shape": "roundedRect",
"label": "Auth API",
"icon": "lucide:lock"
},
{
"op": "createNode",
"id": "users_api",
"nodeType": "service",
"shape": "roundedRect",
"label": "Users API",
"icon": "lucide:users"
},
{
"op": "createNode",
"id": "orders_api",
"nodeType": "service",
"shape": "roundedRect",
"label": "Orders API",
"icon": "lucide:package"
},
{
"op": "createNode",
"id": "catalog_api",
"nodeType": "service",
"shape": "roundedRect",
"label": "Catalog API",
"icon": "lucide:boxes"
},
{
"op": "createNode",
"id": "billing_api",
"nodeType": "service",
"shape": "roundedRect",
"label": "Billing API",
"icon": "lucide:credit-card"
},
{
"op": "createNode",
"id": "search_api",
"nodeType": "service",
"shape": "roundedRect",
"label": "Search API",
"icon": "lucide:search"
},
{
"op": "createNode",
"id": "stripe",
"nodeType": "externalSystem",
"shape": "roundedRect",
"label": "Stripe",
"icon": "lucide:external-link"
},
{
"op": "createNode",
"id": "sendgrid",
"nodeType": "externalSystem",
"shape": "roundedRect",
"label": "SendGrid",
"icon": "lucide:mail"
},
{
"op": "createEdge",
"id": "e_web_gw",
"from": {
"elementId": "web"
},
"to": {
"elementId": "gateway"
},
"label": "REST",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_mob_gw",
"from": {
"elementId": "mobile"
},
"to": {
"elementId": "gateway"
},
"label": "REST",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_gw_auth",
"from": {
"elementId": "gateway"
},
"to": {
"elementId": "auth_api"
},
"label": "POST /v1/login",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_gw_users",
"from": {
"elementId": "gateway"
},
"to": {
"elementId": "users_api"
},
"label": "GET /v1/users",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_gw_orders",
"from": {
"elementId": "gateway"
},
"to": {
"elementId": "orders_api"
},
"label": "REST",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_gw_catalog",
"from": {
"elementId": "gateway"
},
"to": {
"elementId": "catalog_api"
},
"label": "REST",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_orders_billing",
"from": {
"elementId": "orders_api"
},
"to": {
"elementId": "billing_api"
},
"label": "POST /charge",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_orders_users",
"from": {
"elementId": "orders_api"
},
"to": {
"elementId": "users_api"
},
"label": "GET /users/:id",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_catalog_search",
"from": {
"elementId": "catalog_api"
},
"to": {
"elementId": "search_api"
},
"label": "POST /index",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_billing_stripe",
"from": {
"elementId": "billing_api"
},
"to": {
"elementId": "stripe"
},
"label": "Stripe SDK",
"router": "orthogonal"
},
{
"op": "createEdge",
"id": "e_orders_sendgrid",
"from": {
"elementId": "orders_api"
},
"to": {
"elementId": "sendgrid"
},
"label": "SendGrid SDK",
"router": "orthogonal"
}
]
} Validation
Valid
Captured response from POST /v1/scenes/validate. The
platform runs 40+ semantic checks; see the full list in the validation rules reference.
- 0 diagnostics
Scene validates with no diagnostics. Agents that produce scenes like this can ship the rendered SVG without a recovery loop.
Codes the platform can emit include TEXT_OVERFLOW,
CANVAS_AUTO_EXTENDED, EDGE_LABEL_SUPPRESSED_REDUNDANT,
EDGE_LABEL_SUPPRESSED_FANIN, EDGE_COLUMN_NOT_FOUND, LABEL_DUPLICATION_DETECTED, LAYOUT_ABSOLUTE_AT_ORIGIN, and MISSING_DIAGRAM_FAMILY. Each diagnostic carries a
structured data field with element ids and context an
agent can act on programmatically.
Revision diff
Raw
The structural diff between two revisions of the persisted scene —
the response shape dsp_diff_scene returns. Stateful
diagram evolution is Zindex's strongest differentiator: the same
scene id evolves through immutable revisions, each diffable.
Evolution scenario
A new Search API service ships with its own OpenAPI spec; the gateway now routes search queries directly to it. The legacy catalog→search edge label changed to reflect the new operationId. Reviewing the diff is faster than re-reading three OpenAPI files.
- Revision 47 → 48
- +2 added
- -0 removed
- ~1 modified
+ Added
search_apie_gw_search
~ Modified
e_catalog_search
Raw dsp_diff_scene response
{
"schemaVersion": "1.0",
"sceneId": "api-dependency-map",
"fromRevision": 47,
"toRevision": 48,
"summary": {
"added": 2,
"removed": 0,
"modified": 1
},
"added": [
"search_api",
"e_gw_search"
],
"removed": [],
"modified": [
"e_catalog_search"
],
"scenario": "A new Search API service ships with its own OpenAPI spec; the gateway now routes search queries directly to it. The legacy catalog→search edge label changed to reflect the new operationId. Reviewing the diff is faster than re-reading three OpenAPI files."
}
Want to see this on your own scene? Run the CI recipe below — it
calls dsp_diff_scene on every revision change and
surfaces a real before / after on every PR.
CI/CD recipe
A complete, runnable GitHub Actions workflow for this example.
Drop the YAML into .github/workflows/zindex-api-dependency-map.yml,
add the listed secrets, and the agent runs unattended on every
qualifying trigger. Re-comments idempotently using a hidden marker
so the PR conversation stays clean across pushes.
Trigger
Runs on every PR that touches a service spec or a published SDK package, plus a weekly scheduled run on Monday 09:00 UTC. The schedule catches drift from imports that didn't move a spec - e.g. a new SDK consumer added on the consumer side without a spec change.
Required secrets
-
ZINDEX_API_KEYrequired Zindex API key with scene-write scope. -
ZINDEX_SCENE_IDrequired Long-lived persisted scene id for the API topology. Create once via dsp_create_scene; reuse across all runs.
Inputs
- scripts/parse-topology.mjs (you author this) - walks services/*/openapi.* + package.json import graph, emits { services, edges }
- scripts/topology-to-ops.mjs (you author this) - converts topology JSON to a Zindex applyOps batch with stable service-name ids
Outputs
- out/diagram.svg - rendered topology diagram, uploaded as a 30-day workflow artifact
- out/diff.json - structural diff vs previous revision
- PR comment with topology changes and artifact link (PR runs only)
- A new persisted-scene revision per run; the weekly run keeps the diagram fresh on main even if no PR landed that week
GitHub Actions workflow
# Zindex - API dependency map. Maintains a service-to-service dependency graph
# from OpenAPI specs and SDK imports. Runs on every PR touching a service spec
# AND on a weekly schedule (so newly-imported SDKs picked up by static analysis
# don't have to wait for a spec change to surface in the diagram).
#
# Drop into .github/workflows/zindex-api-topology.yml.
name: Zindex - API dependency map
on:
pull_request:
paths:
- "services/*/openapi.yaml"
- "services/*/openapi.json"
- "packages/*/package.json"
schedule:
- cron: "0 9 * * 1" # Monday 09:00 UTC weekly catch-up
workflow_dispatch:
permissions:
contents: read
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
ZINDEX_API_BASE: https://api.zindex.ai
jobs:
sync-topology:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 1. Walk every services/*/openapi.* and emit a normalised
# { services: [...], operations: [...] } JSON. Then walk
# package.json files and import statements to derive consumer→provider
# edges. This step is project-specific - implement parse-topology.mjs
# against your monorepo's conventions.
- name: Derive topology from specs + imports
run: |
mkdir -p out
node scripts/parse-topology.mjs > out/topology.json
echo "::notice::Detected $(jq '.services | length' out/topology.json) services, $(jq '.edges | length' out/topology.json) cross-service edges"
# 2. Capture current revision (for the diff later).
- name: Capture current revision
id: prev_rev
env:
ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
run: |
echo "rev=$(curl -fsSL -H "Authorization: Bearer $ZINDEX_API_KEY" \
"$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID" | jq -r '.revision')" >> "$GITHUB_OUTPUT"
# 3. Compute typed-operation batch. Reuse stable element ids derived
# from service names so a renamed service updates the existing node
# rather than producing a delete + create pair.
- name: Compute applyOps batch
run: node scripts/topology-to-ops.mjs out/topology.json > out/ops.json
# 4. Apply atomically.
- name: Apply ops to persisted scene
id: apply
env:
ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
run: |
RESP=$(curl -fsSL -X POST \
-H "Authorization: Bearer $ZINDEX_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @out/ops.json \
"$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/ops")
echo "rev=$(echo "$RESP" | jq -r '.revision')" >> "$GITHUB_OUTPUT"
echo "applied=$(echo "$RESP" | jq -r '.applied')" >> "$GITHUB_OUTPUT"
# 5. Render.
- name: Render scene
env:
ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
run: |
curl -fsSL -X POST \
-H "Authorization: Bearer $ZINDEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"format":"svg","theme":"clean"}' \
"$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/render" \
| jq -r '.output.content' > out/diagram.svg
# 6. Diff against previous revision.
- name: Diff revisions
id: diff
if: steps.apply.outputs.rev != steps.prev_rev.outputs.rev
env:
ZINDEX_API_KEY: ${{ secrets.ZINDEX_API_KEY }}
ZINDEX_SCENE_ID: ${{ secrets.ZINDEX_SCENE_ID }}
PREV: ${{ steps.prev_rev.outputs.rev }}
NEW: ${{ steps.apply.outputs.rev }}
run: |
curl -fsSL \
-H "Authorization: Bearer $ZINDEX_API_KEY" \
"$ZINDEX_API_BASE/v1/scenes/$ZINDEX_SCENE_ID/diff?from=$PREV&to=$NEW" \
> out/diff.json
echo "added=$(jq -r '.summary.added' out/diff.json)" >> "$GITHUB_OUTPUT"
echo "removed=$(jq -r '.summary.removed' out/diff.json)" >> "$GITHUB_OUTPUT"
echo "modified=$(jq -r '.summary.modified' out/diff.json)" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v4
id: upload
with:
name: api-dependency-map-svg
path: out/diagram.svg
retention-days: 30
# 7. PR comment - only on pull_request runs (the weekly schedule run
# has no PR to comment on; it just refreshes the artifact).
- uses: peter-evans/find-comment@v3
if: github.event_name == 'pull_request'
id: find_comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- zindex-bot:api-dependency-map -->"
- uses: peter-evans/create-or-update-comment@v4
if: github.event_name == 'pull_request'
with:
comment-id: ${{ steps.find_comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
edit-mode: replace
body: |
<!-- zindex-bot:api-dependency-map -->
### API dependency map updated · revision ${{ steps.apply.outputs.rev }}
| | |
|---|---|
| Topology changes | +${{ steps.diff.outputs.added || '0' }} / -${{ steps.diff.outputs.removed || '0' }} / ~${{ steps.diff.outputs.modified || '0' }} |
| Revision | ${{ steps.prev_rev.outputs.rev }} → ${{ steps.apply.outputs.rev }} |
<details><summary>Download rendered SVG</summary>
${{ steps.upload.outputs.artifact-url }}
</details>
<sub>Rendered by Zindex · scene `${{ secrets.ZINDEX_SCENE_ID }}`</sub>
Agent resources
Machine-readable versions of this example. Agents should fetch these rather than scrape the rendered HTML.
-
api-dependency-map.scene.jsonCanonical DSP scene Open -
api-dependency-map.ops.jsonTyped-operation envelope that builds the scene Open -
api-dependency-map.workflow.jsonStructured agent workflow (goal, inputs, outputs, steps) Open -
api-dependency-map.diff.jsonSampledsp_diff_sceneresponse (revision evolution) Open -
api-dependency-map.github-actions.ymlRunnable GitHub Actions workflow for the CI/CD recipe Open -
api-dependency-map.svgBuild-time rendered diagram Open -
api-dependency-map.mdAgent-readable markdown summary Open -
/examples/index.jsonManifest of all examples (cross-linked) Open
PR comment template
The bot posts this comment on every triggering PR. The hidden marker
<!-- zindex-bot:api-dependency-map -->letspeter-evans/create-or-update-commentfind and overwrite the previous comment instead of appending a new one.