Documentation for v2.6.1. Read the latest, v2.12.0.

Reference

REST API

api.yaml version 2.6.1

API contract for tooling that talks to Sanshain (build plugins, CLIs). A Producer provides its complete specification under a producer-declared version; a Consumer pins an exact version and requires only the endpoint snippets it uses. Covers providing specifications (OpenAPI, AsyncAPI, gRPC/Proto) and requiring endpoint snippets. See CONTEXT.md for the vocabulary and docs/adr/0003-versions-replace-branches.md for the model.

The version lives in the spec file. For OpenAPI and AsyncAPI it is read from info.version; for proto from a mandatory // sanshain-version: MAJOR.MINOR.PATCH comment. Accepted spellings are MAJOR[.MINOR[.PATCH]] with an optional leading v — omitted parts are zero (v2 → 2.0.0) and the stored/answered form is always the full three-part version. No -SNAPSHOT or other suffixes.

Stability is declared on every Provide: snapshot (overwritable work-in-progress, expires when unused) or ga (immutable; the number is permanently claimed). Resolution prefers GA, falls back only to the same-numbered snapshot, and otherwise fails immediately — no fallback, no long-polling.

2.0 is a clean break of the 1.x branch-based contract: branch, base_version, force, timeout, source_protected_branch and pull_from_branch are no longer accepted anywhere and are rejected as unknown fields.

Only the canonical names producername and consumername are accepted; the 1.x aliases servicename and clientname are rejected as unknown fields like every other branch-era leftover.

Tokens, scopes, sessions and error shapes are explained in Using the API.

Endpoints#

MethodPathSummary
POST/provideProvide an OpenAPI specification under its declared version
POST/provide/asyncapiProvide an AsyncAPI specification under its declared version
POST/provide/grpcProvide a .proto file under its declared version
GET/requireRetrieve an endpoint snippet at a pinned version
GET/require/asyncapiRetrieve an AsyncAPI channel snippet at a pinned version
GET/require/grpcRetrieve a gRPC method snippet at a pinned version
POST/require-bundleRetrieve multiple endpoint snippets as a single merged spec
PUT/catalog/{systemname}Record a system's periphery and context from its Backstage catalog-info.yaml
GET/producers/{producername}/versionsList a Producer's version lines
POST/validateValidate a specification without publishing anything
GET/versionReport the running Sanshain instance version
GET/auth/tokensList the caller's API tokens
POST/auth/tokensCreate an API token
DELETE/auth/tokens/{id}Revoke an API token

/provide#

POST /provide#

Provide an OpenAPI specification under its declared version

Authentication: not stated

Parses the provided OpenAPI YAML, reads the version from info.version, splits the document into individual endpoint snippets, and stores the full document and the snippets as one version-line entry for the Producer.

Releasing is a role: stability: ga — a fresh GA, a promotion, or even an idempotent GA re-provide — requires the release_ga permission (the releaser role; admins and root hold it implicitly) and is otherwise refused 403. Snapshots need only authentication.

Idempotency: re-providing byte-identical content is a no-op — CI re-runs of the same commit never fight. changes reports all zero. The GA gate still applies first: for stability: ga the caller must hold release_ga even when the re-provide would change nothing.

Snapshots are overwritable: a snapshot Provide for an existing snapshot version replaces it wholesale (last writer wins; the previous provider is named in the audit trail).

GA is immutable: a Provide for an existing GA version with different content is rejected 409 with proposed_version — the next free number, bumped by what actually changed (breaking → major, additive → minor, shape-identical → patch). A GA Provide for a version that exists as a snapshot promotes it in place. A snapshot Provide for a version that has gone GA is rejected — a released number can never carry a snapshot again.

Semver honesty: a GA Provide whose changes relative to the highest GA below it are breaking without a major bump is rejected 409 with the correct proposed_version. Snapshots are never compatibility-checked.

Request body

application/json

Schema: ProvidePayload.

Example request body (placeholder values derived from the schema)

{
  "producername": "string",
  "openapi_yaml": "string",
  "stability": "snapshot",
  "retired": false,
  "dry_run": false,
  "trunk": false,
  "tag": "string"
}

Responses

StatusDescriptionBody
202

Specification accepted and stored — or, for retired: true, the family retired. The two answer different objects; a caller knows which it sent.

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

one of ProvideResponse, RetiredProtocolResponse

Example 202 response (placeholder values derived from the schema)

{
  "version": "string",
  "stability": "snapshot",
  "content_hash": "string",
  "changes": {
    "inserts": 0,
    "updates": 0,
    "deletes": 0
  },
  "promoted": false,
  "harvested_subscriptions": [
    {
      "channel": "string",
      "message_name": "string",
      "owner": "string",
      "drift": "string"
    }
  ]
}
400

Invalid request — malformed spec YAML, a missing/non-semver info.version, or two paths that are the same endpoint after normalization (differing only in a path-parameter name, a trailing slash, or doubled separators).

ErrorBody
403

stability: ga without the release_ga permission. Releasing is a role: grant releaser to the publishing user or token (admins and root always may), or publish as a snapshot.

Also retired: true without the release_ga permission and without a maintainer grant on this Producer. Retiring is a role for the same reason releasing is — see ProvidePayload.retired.

Also any request with a read_only API token — see the ReadOnlyToken response.

ErrorBody
409

Rejected by the version rules. The body carries proposed_version, the next free version to publish as instead. Every rejection is self-service — fix the version and republish.

ErrorBody
413

Request body exceeds the configured size limit (MAX_SPEC_BODY_BYTES, default 4 MiB).

—
422

Request body has the wrong shape — missing stability, or unknown (1.x) fields such as branch, base_version or force, which are rejected by name rather than silently ignored.

—

/provide/asyncapi#

POST /provide/asyncapi#

Provide an AsyncAPI specification under its declared version

Authentication: not stated

Same rules as /provide, for AsyncAPI 2.x/3.x documents (version auto-detected). The version is read from info.version. Only PUB (publish/send) operations are stored; SUB channels belong in a Consumer's requires. On GA provides, message-level channel contracts are reconciled: the first Producer to publish a named PUB message owns its payload schema; a different Producer may co-publish only an identical schema.

Request body

application/json

Schema: ProvideAsyncApiPayload.

Example request body (placeholder values derived from the schema)

{
  "producername": "string",
  "asyncapi_yaml": "string",
  "stability": "snapshot",
  "retired": false,
  "dry_run": false,
  "trunk": false,
  "tag": "string"
}

Responses

StatusDescriptionBody
202

Specification accepted and stored — or, for retired: true, the family retired. The two answer different objects; a caller knows which it sent.

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

one of ProvideResponse, RetiredProtocolResponse

Example 202 response (placeholder values derived from the schema)

{
  "version": "string",
  "stability": "snapshot",
  "content_hash": "string",
  "changes": {
    "inserts": 0,
    "updates": 0,
    "deletes": 0
  },
  "promoted": false,
  "harvested_subscriptions": [
    {
      "channel": "string",
      "message_name": "string",
      "owner": "string",
      "drift": "string"
    }
  ]
}
400

Invalid request (e.g. malformed YAML, bad version).

ErrorBody
403

stability: ga without the release_ga permission, or retired: true without it and without a maintainer grant on this Producer (see /provide), or a read_only API token (see the ReadOnlyToken response).

ErrorBody
409

Rejected by the version rules (see /provide), or by a channel message contract owned by another Producer with a different schema.

ErrorBody
413

Request body exceeds the configured size limit (MAX_SPEC_BODY_BYTES, default 4 MiB).

—

/provide/grpc#

POST /provide/grpc#

Provide a .proto file under its declared version

Authentication: not stated

Same rules as /provide, for proto files. Proto has no standard version slot, so the file must carry exactly one // sanshain-version: MAJOR.MINOR.PATCH comment (conventionally in the header); a missing, malformed or conflicting marker rejects the Provide. The marker travels into the split files Consumers download.

Request body

application/json

Schema: ProvideProtoPayload.

Example request body (placeholder values derived from the schema)

{
  "producername": "string",
  "proto_content": "string",
  "stability": "snapshot",
  "retired": false,
  "dry_run": false,
  "trunk": false,
  "tag": "string"
}

Responses

StatusDescriptionBody
202

Specification accepted and stored — or, for retired: true, the family retired. The two answer different objects; a caller knows which it sent.

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

one of ProvideResponse, RetiredProtocolResponse

Example 202 response (placeholder values derived from the schema)

{
  "version": "string",
  "stability": "snapshot",
  "content_hash": "string",
  "changes": {
    "inserts": 0,
    "updates": 0,
    "deletes": 0
  },
  "promoted": false,
  "harvested_subscriptions": [
    {
      "channel": "string",
      "message_name": "string",
      "owner": "string",
      "drift": "string"
    }
  ]
}
400

Invalid request (e.g. missing sanshain-version marker).

ErrorBody
403

stability: ga without the release_ga permission, or retired: true without it and without a maintainer grant on this Producer (see /provide), or a read_only API token (see the ReadOnlyToken response).

ErrorBody
409

Rejected by the version rules (see /provide).

ErrorBody
413

Request body exceeds the configured size limit (MAX_SPEC_BODY_BYTES, default 4 MiB).

—

/require#

GET /require#

Retrieve an endpoint snippet at a pinned version

Authentication: not stated

Resolves one endpoint of the Producer's line at exactly the pinned version: GA preferred, else the same-numbered snapshot, else an immediate 404. There is no fallback to any other version and nothing waits — a missing pinned version is a configuration error.

A successful require records the dependency (this is what the graph shows) and counts as use for snapshot expiry. A failed require records nothing — resolution never creates graph entities.

Responses carry X-Sanshain-Resolution (served), X-Sanshain-Version (the Pin) and X-Sanshain-Stability (ga or snapshot — what actually answered), plus an ETag; If-None-Match yields 304 Not Modified when unchanged.

Parameters

NameInRequiredTypeDescription
consumernamequeryyesstring

Name of the requiring Consumer.

producernamequeryyesstring

Name of the Producer to require from.

versionqueryyesstring

The exact pinned version (MAJOR.MINOR.PATCH). No ranges, no latest. A leading v and omitted MINOR/PATCH are accepted and normalized (v1 → 1.0.0).

pathqueryyesstring

The OpenAPI path (lenient matching normalizes variable names).

methodqueryyesstring

The HTTP verb (case-insensitive).

dry_runquery—boolean

Resolve without recording the dependency.

Default false.

trunkquery—boolean

Marks this build as the trunk stream's: the pin is additionally recorded in the append-only trunk store that feeds the main graph (last write per endpoint wins). Omit when not using trunk/graph features.

Default false.

tagquery—string

The pin updates the named sanshain-branch's graph instead of trunk (release-branch hotfix). Mutually exclusive with trunk: both together, or a tag naming no existing sanshain-branch, still answer normally but record the pin in the dev graph only, with a Sanshain-Warning header. Branches are never auto-created.

Responses

StatusDescriptionBody
200

The endpoint snippet as YAML.

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

string (application/yaml)
304

Not modified (ETag match).

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

—
403

The request carried an API token with scope read_only. Such a token may read — reports, /producers/{producername}/versions, /endpoint-versions, the update streams and the MCP endpoint — but never provide, retire, require or push a catalog. error names the token's scope and the scope the call needs (full).

ErrorBody
404

Unknown — the Producer, or the pinned version, does not exist in either stability. Fails in milliseconds; nothing waits for a version to appear.

ErrorBody
410

Absent — the pinned version exists and deliberately does not include this endpoint. Provided specs are complete, so absence is a definitive no, distinct from an unknown version.

ErrorBody

/require/asyncapi#

GET /require/asyncapi#

Retrieve an AsyncAPI channel snippet at a pinned version

Authentication: not stated

Same resolution rules as /require. path is the channel name (AsyncAPI 2.x channel key / 3.x channel address); method is PUB or SUB from the provider's perspective.

With format=openapi the answer is not the AsyncAPI snippet but an OpenAPI 3.0.3 document holding the payload schemas of the channel's messages (see the format parameter). Its ETag never matches the AsyncAPI answer's.

Parameters

NameInRequiredTypeDescription
consumernamequeryyesstring
producernamequeryyesstring
versionqueryyesstring
pathqueryyesstring
methodqueryyesstring
dry_runquery—boolean

Default false.

trunkquery—boolean

See trunk on /require.

Default false.

tagquery—string

See tag on /require.

formatquery—string

openapi answers an OpenAPI 3.0.3 document instead of the AsyncAPI snippet: openapi: 3.0.3, info.title <producername> message schemas, info.version the pinned version, empty paths, and in components.schemas only the payload schemas reachable from the kept messages (nested $refs included). A payload given as $ref: '#/components/schemas/X' keeps the name X; an inline payload is named <MessageKey>Payload (the message's component or channel message key, else its messageId or name). Every $ref in the answer resolves inside it. AsyncAPI 2.x and 3.x are supported. Without the parameter the answer is unchanged.

One of openapi.

Responses

StatusDescriptionBody
200

The channel snippet as YAML, or with format=openapi the OpenAPI 3.0.3 document of its payload schemas.

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

string (application/yaml)
304

Not modified (ETag match).

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

—
400

An unknown format, or the spec cannot be converted with format=openapi: two different schemas would get the same name, a message payload has a non-AsyncAPI schemaFormat (e.g. Avro), a $ref is external or does not resolve, or an inline payload has no message key to be named after.

ErrorBody
403

The request carried an API token with scope read_only. Such a token may read — reports, /producers/{producername}/versions, /endpoint-versions, the update streams and the MCP endpoint — but never provide, retire, require or push a catalog. error names the token's scope and the scope the call needs (full).

ErrorBody
404

Unknown Producer or version.

ErrorBody
410

The pinned version deliberately lacks this channel.

ErrorBody

/require/grpc#

GET /require/grpc#

Retrieve a gRPC method snippet at a pinned version

Authentication: not stated

Same resolution rules as /require. path is the fully qualified service name; method is the exact (case-sensitive) RPC name.

Parameters

NameInRequiredTypeDescription
consumernamequeryyesstring
producernamequeryyesstring
versionqueryyesstring
pathqueryyesstring
methodqueryyesstring
dry_runquery—boolean

Default false.

trunkquery—boolean

See trunk on /require.

Default false.

tagquery—string

See tag on /require.

Responses

StatusDescriptionBody
200

The .proto snippet.

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

string (text/plain)
304

Not modified (ETag match).

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

—
403

The request carried an API token with scope read_only. Such a token may read — reports, /producers/{producername}/versions, /endpoint-versions, the update streams and the MCP endpoint — but never provide, retire, require or push a catalog. error names the token's scope and the scope the call needs (full).

ErrorBody
404

Unknown Producer or version.

ErrorBody
410

The pinned version deliberately lacks this method.

ErrorBody

/require-bundle#

POST /require-bundle#

Retrieve multiple endpoint snippets as a single merged spec

Authentication: not stated

Resolves several endpoints of one Producer at one pinned version and returns a single merged document (one copy of each shared schema, so generated DTOs are shared across operations). Resolution rules match /require; if any requested endpoint is absent from the pinned version the whole bundle fails 410 naming the missing endpoints, and no dependency is recorded.

With format=openapi (query parameter, api_type: asyncapi only) the answer is one OpenAPI 3.0.3 document holding the payload schemas of all requested channels' messages, built as described on /require/asyncapi.

Parameters

NameInRequiredTypeDescription
formatquery—string

openapi answers the payload schemas of the requested AsyncAPI channels as an OpenAPI 3.0.3 document (see format on /require/asyncapi). Only valid with api_type: asyncapi. Without it the answer is unchanged.

One of openapi.

Request body

application/json

Schema: RequireBundlePayload.

Example request body (placeholder values derived from the schema)

{
  "consumername": "string",
  "producername": "string",
  "version": "string",
  "api_type": "openapi",
  "endpoints": [
    {
      "path": "string",
      "method": "string"
    }
  ],
  "dry_run": false,
  "trunk": false,
  "tag": "string"
}

Responses

StatusDescriptionBody
200

The merged specification as YAML, or with format=openapi the OpenAPI 3.0.3 document of the channels' payload schemas.

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

string (application/yaml)
304

Not modified (ETag match).

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

—
400

format with an api_type other than asyncapi (the default is openapi), an unknown format, or a spec format=openapi cannot convert (see /require/asyncapi).

ErrorBody
403

The request carried an API token with scope read_only. Such a token may read — reports, /producers/{producername}/versions, /endpoint-versions, the update streams and the MCP endpoint — but never provide, retire, require or push a catalog. error names the token's scope and the scope the call needs (full).

ErrorBody
404

Unknown Producer or version.

ErrorBody
410

The pinned version deliberately lacks one or more requested endpoints.

ErrorBody

/catalog/{systemname}#

PUT /catalog/{systemname}#

Record a system's periphery and context from its Backstage catalog-info.yaml

Authentication: not stated

Optional. A Producer's or Consumer's build pushes its Backstage catalog-info.yaml so the network graph can show the Peripheral Resources the system depends on — databases, buckets, queues and other things outside Sanshain — and so the graph's node panel and the system's page can say who owns it, what it is, where its code lives and where its wiki, dashboards and ticket board are.

Sanshain reads only the Component whose metadata.name equals systemname (case-insensitive), and of it only:

  • spec.dependsOn: only resource: refs are kept; component: refs and consumesApis are ignored, because edges between systems are Sanshain's own (/require). A ref without a kind is read as a resource, a ref without a namespace takes the Component's namespace (default when it declares none), and refs are lower-cased.
  • spec.owner, normalised like other refs (a bare team-orders is group:default/team-orders) and kept as kind and name.
  • metadata.description, trimmed, at most 1000 characters (a longer one is cut with an ellipsis).
  • the backstage.io/source-location annotation, without its url: prefix, when it is an http or https URL.
  • metadata.links: url (must be http or https, otherwise the link is dropped), title (defaults to the URL's host), type and icon; at most 20, further ones are ignored.

Everything else in the file is ignored. Each push replaces the system's periphery and its context: a resource no longer listed is removed from the current graph (it stays in the timeline), an empty or missing dependsOn clears it, and a missing owner, description, source location or link list leaves that field empty. trunk: true updates the main graph and the dev graph; otherwise only the dev graph. The context is one present-day fact per system, whatever the stream. Sanshain-branches get the periphery copied when they are cut. A push whose content matches what is recorded changes nothing.

Any authenticated caller may push — the same bar as /require.

Parameters

NameInRequiredTypeDescription
systemnamepathyesstring

The system the file describes — the serviceName from sanshain.yaml, i.e. the Producer or Consumer name.

Request body

application/json

Schema: CatalogPushPayload.

Example request body (placeholder values derived from the schema)

{
  "catalog_info_yaml": "string",
  "trunk": false
}

Responses

StatusDescriptionBody
200

The periphery now recorded for the system (recorded: true).

When the file is not valid YAML, a document in it is not a Backstage entity, it has no (or more than one) Component named systemname, or its dependsOn is not a list of entity refs, the answer is still 200, with resources: [] and recorded: false: nothing is recorded — neither periphery nor context — the system's previous state stays as it was, and a Sanshain-Warning header names the reason.

Header Sanshain-Warning: A warning the caller's build should print — the call itself succeeded. Sent once per warning (the header may appear several times). The value is plain ASCII: any other character, e.g. from a tag name, is escaped as \u{XXXX} (its Unicode code point in hex, at least four digits). The graph is informational and never fails a build: an unknown tag (no sanshain-branch '<name>' - this build was not recorded in a release graph), trunk and tag together (trunk and tag are mutually exclusive - this build was recorded in neither), or a catalog push that recorded nothing (the reason). Clients fail on it only in strict mode.

CatalogPushResponse

Example 200 response (placeholder values derived from the schema)

{
  "system": "string",
  "trunk": false,
  "resources": [
    "string"
  ],
  "recorded": false
}
403

The request carried an API token with scope read_only. Such a token may read — reports, /producers/{producername}/versions, /endpoint-versions, the update streams and the MCP endpoint — but never provide, retire, require or push a catalog. error names the token's scope and the scope the call needs (full).

ErrorBody
413

Request body exceeds the configured size limit (MAX_SPEC_BODY_BYTES, default 4 MiB).

—
422

Request body has the wrong shape (missing catalog_info_yaml, unknown fields).

—

/producers/{producername}/versions#

GET /producers/{producername}/versions#

List a Producer's version lines

Authentication: not stated

Every version-line entry of one Producer — version, stability, content hash, timestamps, last provider, endpoint count, and (for snapshots, when cleanup is enabled) the use-based expiry. What "what can I upgrade to?" tooling reads.

Parameters

NameInRequiredTypeDescription
producernamepathyesstring
api_typequery—string

Narrow to one API type's line.

One of openapi, asyncapi, proto.

Responses

StatusDescriptionBody
200

The version-line entries, API types grouped, newest first.

ProducerVersionInfo[]

Example 200 response (placeholder values derived from the schema)

[
  {
    "api_type": "openapi",
    "version": "string",
    "stability": "snapshot",
    "content_hash": "string",
    "provided_by": "string",
    "created_at": "string",
    "updated_at": "string",
    "last_required_at": "string",
    "endpoint_count": 1,
    "expires_at": "string"
  }
]
404

Unknown Producer.

ErrorBody

/validate#

POST /validate#

Validate a specification without publishing anything

Authentication: not stated

The free validator: runs exactly the provide-side pipeline — version extraction (MAJOR[.MINOR[.PATCH]], optional v prefix) and endpoint splitting — with no persistence and no producer context. Unauthenticated and stateless; bounded by the request body-size limit. Always answers 200 with a verdict, so callers parse one shape. "Valid here" means "a Provide of this document parses there" — the version-line rules (GA immutability, semver honesty) additionally apply on a real Provide.

Request body

application/json

FieldTypeRequiredDescription
api_typestringyes

One of openapi, asyncapi, proto.

contentstringyes

The full specification document.

Example request body (placeholder values derived from the schema)

{
  "api_type": "openapi",
  "content": "string"
}

Responses

StatusDescriptionBody
200

The verdict.

ValidationReport

Example 200 response (placeholder values derived from the schema)

{
  "valid": false,
  "api_type": "openapi",
  "version": "string",
  "endpoint_count": 0,
  "endpoints": [
    "string"
  ],
  "error": "string"
}
413

Request body exceeds the configured size limit.

—

/version#

GET /version#

Report the running Sanshain instance version

Authentication: not stated

The instance version — Sanshain's own build — never to be confused with spec versions.

Responses

StatusDescriptionBody
200

Instance version and id.

object

Example 200 response (placeholder values derived from the schema)

{
  "version": "string",
  "instance_id": "string"
}

/auth/tokens#

GET /auth/tokens#

List the caller's API tokens

Authentication: not stated

Every token the signed-in user owns, newest first, with its scope and when it was last used. The raw token is never answered again after creation. Session-authenticated: this is the account page's call, so an API token cannot list or mint tokens.

Responses

StatusDescriptionBody
200

The caller's tokens.

ApiToken[]

Example 200 response (placeholder values derived from the schema)

[
  {
    "id": "string",
    "user_id": 1,
    "name": "string",
    "scope": "full",
    "created_at": "string",
    "expires_at": "string",
    "last_used_at": "string"
  }
]
401

No valid session.

—

POST /auth/tokens#

Create an API token

Authentication: not stated

Mints a token for the signed-in user and answers it once. The scope is decided here and is immutable afterwards: full (the default) may do everything the user may; read_only is for AI / MCP clients and is refused on every route that changes the catalog (see the ReadOnlyToken response). Session-authenticated.

Request body

application/json

Schema: CreateApiTokenPayload.

Example request body (placeholder values derived from the schema)

{
  "name": "string",
  "expires_in_days": 0,
  "scope": "full"
}

Responses

StatusDescriptionBody
200

The new token. Copy token now; it is not stored and not shown again.

CreateApiTokenResponse

Example 200 response (placeholder values derived from the schema)

{
  "id": "string",
  "name": "string",
  "scope": "full",
  "token": "string"
}
401

No valid session.

—
409

The caller already has a token of that name.

ErrorBody

/auth/tokens/{id}#

DELETE /auth/tokens/{id}#

Revoke an API token

Authentication: not stated

Deletes one of the caller's tokens; it stops validating immediately. Session-authenticated.

Parameters

NameInRequiredTypeDescription
idpathyesstring

The token id answered at creation and by the listing.

Responses

StatusDescriptionBody
200

Revoked.

—
401

No valid session.

—
404

No such token owned by the caller.

ErrorBody

Schemas#

ErrorBody#

The body every error response carries. error holds the human-readable message; proposed_version appears only on 409s from the version rules.

FieldTypeRequiredDescription
errorstringyes

Human-readable description of what went wrong.

proposed_versionstring—

The next free version the Producer should publish as instead. Present only on a 409 refusing a Provide by the version rules.

Example (placeholder values derived from the schema)

{
  "error": "string",
  "proposed_version": "string"
}

ApiTokenScope#

What a token may do. Decided at creation, immutable afterwards. full is everything its owner may do; read_only is for AI / MCP clients and is refused on every catalog-changing route.

Type: string. One of full, read_only.

ApiToken#

FieldTypeRequiredDescription
idstringyes
user_idinteger (int64)—
namestringyes
scopeApiTokenScopeyes

What a token may do. Decided at creation, immutable afterwards. full is everything its owner may do; read_only is for AI / MCP clients and is refused on every catalog-changing route.

One of full, read_only.

Default "full".

created_atstringyes

RFC 3339 instant.

expires_atstringyes

RFC 3339 instant; the token stops validating after it.

last_used_atstring | null—

When the token last authenticated a request (API or MCP), to the minute — the stamp is refreshed at most once a minute. null when never used.

Example (placeholder values derived from the schema)

{
  "id": "string",
  "user_id": 1,
  "name": "string",
  "scope": "full",
  "created_at": "string",
  "expires_at": "string",
  "last_used_at": "string"
}

CreateApiTokenPayload#

FieldTypeRequiredDescription
namestringyes

Unique per user.

expires_in_daysintegeryes
scopeApiTokenScope—

Omitted means full.

One of full, read_only.

Default "full".

Example (placeholder values derived from the schema)

{
  "name": "string",
  "expires_in_days": 0,
  "scope": "full"
}

CreateApiTokenResponse#

FieldTypeRequiredDescription
idstringyes
namestringyes
scopeApiTokenScopeyes

What a token may do. Decided at creation, immutable afterwards. full is everything its owner may do; read_only is for AI / MCP clients and is refused on every catalog-changing route.

One of full, read_only.

Default "full".

tokenstringyes

The raw token (san_ + 64 hex). Shown only here.

Example (placeholder values derived from the schema)

{
  "id": "string",
  "name": "string",
  "scope": "full",
  "token": "string"
}

Stability#

Which of exactly two states a stored version is in. Declared by the caller on every Provide; a state of the stored version, never part of the version string. Snapshots are overwritable and expire when unused; GA is immutable and permanently claims its number.

Type: string. One of snapshot, ga.

ProvidePayload#

FieldTypeRequiredDescription
producernamestringyes

Name of the Producer providing the specification.

openapi_yamlstring—

The full OpenAPI specification as a YAML string. Its info.version is the authoritative version of this Provide. Required, except on a retire (retired: true), which publishes nothing.

stabilityStability—

Required, except on a retire, which publishes nothing and so has no stability to declare.

One of snapshot, ga.

retiredboolean—

The Producer no longer provides this API family — retire the capability instead of publishing a version. The endpoint already names the family, so nothing else identifies it.

Retiring clears the messaging/grpc auto-tag, closes the family's open trunk pins (it leaves the current main graph while its closed rows stay as timeline history), and releases any AsyncAPI channel-message contracts it owned so another Producer may claim them. Version lines are kept and existing Consumer pins still resolve. Answers {"tag_cleared", "trunk_pins_closed", "contracts_released"}.

Retiring is a role, like releasing: the caller needs the releaser role — which is what a build pipeline holds, so CI can retire the moment sanshain.yaml drops a protocol — or a maintainer grant on this Producer. Admins and root hold both implicitly. Otherwise 403. A plain authenticated caller may still provide.

Combining it with anything a publish needs answers 400: a specification body, a stability, trunk, or tag. A retire is not a build, so a request declaring both is a contradiction rather than one with defaults to fill in.

With dry_run: true the permission check and the Producer lookup run and nothing is retired; the returned counts are zero because nothing happened.

Default false.

dry_runboolean—

Validate and classify without storing anything.

Default false.

trunkboolean—

Marks this build as the trunk stream's: the stored version entry becomes "trunk's current version" (shown in the main graph), as opposed to e.g. a release-branch GA backport. Orthogonal to stability; no effect on version rules. Omit when not using trunk/graph features.

Default false.

tagstring—

Marks this build as a sanshain-branch's (release-branch hotfix): the producer's member version within that branch is updated. Mutually exclusive with trunk: both together, or a tag naming no existing sanshain-branch (a releaser must create it first), still store the spec but record it in the dev graph only, with a Sanshain-Warning header.

Example (placeholder values derived from the schema)

{
  "producername": "string",
  "openapi_yaml": "string",
  "stability": "snapshot",
  "retired": false,
  "dry_run": false,
  "trunk": false,
  "tag": "string"
}

ProvideAsyncApiPayload#

FieldTypeRequiredDescription
producernamestringyes

Name of the Producer.

asyncapi_yamlstring—

The full AsyncAPI document as a YAML string. Its info.version is the authoritative version of this Provide. See ProvidePayload.openapi_yaml for when it may be omitted.

stabilityStability—

See ProvidePayload.stability.

One of snapshot, ga.

retiredboolean—

See ProvidePayload.retired.

Default false.

dry_runboolean—

Default false.

trunkboolean—

See ProvidePayload.trunk.

Default false.

tagstring—

See ProvidePayload.tag.

Example (placeholder values derived from the schema)

{
  "producername": "string",
  "asyncapi_yaml": "string",
  "stability": "snapshot",
  "retired": false,
  "dry_run": false,
  "trunk": false,
  "tag": "string"
}

ProvideProtoPayload#

FieldTypeRequiredDescription
producernamestringyes

Name of the Producer.

proto_contentstring—

The full .proto file. Must carry exactly one // sanshain-version: MAJOR.MINOR.PATCH comment. See ProvidePayload.openapi_yaml for when it may be omitted.

stabilityStability—

See ProvidePayload.stability.

One of snapshot, ga.

retiredboolean—

See ProvidePayload.retired.

Default false.

dry_runboolean—

Default false.

trunkboolean—

See ProvidePayload.trunk.

Default false.

tagstring—

See ProvidePayload.tag.

Example (placeholder values derived from the schema)

{
  "producername": "string",
  "proto_content": "string",
  "stability": "snapshot",
  "retired": false,
  "dry_run": false,
  "trunk": false,
  "tag": "string"
}

RetiredProtocolResponse#

What retiring an API family actually shed. Answered by a provide call carrying retired: true, in place of ProvideResponse — a retire publishes no version, so it reports no version.

All three counts are zero under dry_run, which checks permission and stops without retiring anything.

FieldTypeRequiredDescription
tag_clearedstring | null—

The capability auto-tag removed — messaging for AsyncAPI, grpc for proto. Null for OpenAPI, which carries no such tag, and null when the family had none to clear.

trunk_pins_closedintegeryes

Open trunk pins closed. They leave the current main graph; their closed rows stay as timeline history, so the era the Producer did provide this family remains visible.

contracts_releasedintegeryes

AsyncAPI channel-message contracts released for another Producer to claim. Always zero for OpenAPI and proto, which own no contracts.

Example (placeholder values derived from the schema)

{
  "tag_cleared": "string",
  "trunk_pins_closed": 0,
  "contracts_released": 0
}

ProvideResponse#

FieldTypeRequiredDescription
versionstringyes

The version read from the provided document.

stabilityStabilityyes

Which of exactly two states a stored version is in. Declared by the caller on every Provide; a state of the stored version, never part of the version string. Snapshots are overwritable and expire when unused; GA is immutable and permanently claims its number.

One of snapshot, ga.

content_hashstringyes

SHA-256 (sha256: prefixed) of the provided document after its line endings were normalized to LF and a leading BOM was removed.

changesobjectyes

Endpoint diff relative to what this exact version stored before (snapshot overwrite / promotion); a brand-new version reports its whole endpoint set as inserts. All zero = idempotent no-op.

changes.insertsinteger—
changes.updatesinteger—
changes.deletesinteger—
promotedboolean—

True when this Provide released an existing snapshot in place (snapshot → GA under the same number), including the byte-identical case where changes is all zero.

harvested_subscriptionsobject[]—

AsyncAPI subscribe operations harvested from this provide as version-less consumer edges (only present for /provide/asyncapi, and omitted when empty). Each is validated against the producer's GA channel contract: on a GA provide an unsatisfiable expectation is rejected with 409; on a snapshot it is reported here as drift.

Perspective convention: Sanshain reads AsyncAPI 2.x publish/subscribe from the application's perspective — publish means "this service publishes", subscribe means "this service consumes" (the 2.x specification's client-perspective reading is the inverse). This matches the 3.x send/receive mapping. A document authored with the spec-literal 2.x reading harvests exactly backwards.

harvested_subscriptions[].channelstringyes
harvested_subscriptions[].message_namestringyes
harvested_subscriptions[].ownerstring | null—

The Producer that owns the channel's PUB contract, or absent when no GA provider owns it yet (an unfulfilled subscription).

harvested_subscriptions[].driftstring | null—

Advisory note when the subscription's expectation is not satisfiable by the current contract (snapshot provides only).

Example (placeholder values derived from the schema)

{
  "version": "string",
  "stability": "snapshot",
  "content_hash": "string",
  "changes": {
    "inserts": 0,
    "updates": 0,
    "deletes": 0
  },
  "promoted": false,
  "harvested_subscriptions": [
    {
      "channel": "string",
      "message_name": "string",
      "owner": "string",
      "drift": "string"
    }
  ]
}

RequireBundlePayload#

FieldTypeRequiredDescription
consumernamestringyes

Name of the requiring Consumer.

producernamestringyes

Name of the Producer.

versionstringyes

The exact pinned version (MAJOR.MINOR.PATCH). A leading v and omitted MINOR/PATCH are accepted and normalized (v1 → 1.0.0).

api_typestring—

One of openapi, asyncapi, proto.

Default "openapi".

endpointsobject[]yes
endpoints[].pathstringyes
endpoints[].methodstringyes
dry_runboolean—

Default false.

trunkboolean—

See trunk on /require.

Default false.

tagstring—

See tag on /require.

Example (placeholder values derived from the schema)

{
  "consumername": "string",
  "producername": "string",
  "version": "string",
  "api_type": "openapi",
  "endpoints": [
    {
      "path": "string",
      "method": "string"
    }
  ],
  "dry_run": false,
  "trunk": false,
  "tag": "string"
}

CatalogPushPayload#

FieldTypeRequiredDescription
catalog_info_yamlstringyes

The system's Backstage catalog-info.yaml, as text. May hold several documents.

trunkboolean—

A trunk build (ADR-0004): the periphery updates the main graph as well as the dev graph.

Default false.

Example (placeholder values derived from the schema)

{
  "catalog_info_yaml": "string",
  "trunk": false
}

CatalogPushResponse#

FieldTypeRequiredDescription
systemstringyes
trunkbooleanyes
resourcesstring[]yes

The recorded refs, resource:<namespace>/<name>, lower-cased and sorted.

recordedbooleanyes

Whether the push was recorded. false when Sanshain could not read the file as the system's Component — the reason is in the Sanshain-Warning header, and the previous periphery stays.

Example (placeholder values derived from the schema)

{
  "system": "string",
  "trunk": false,
  "resources": [
    "string"
  ],
  "recorded": false
}

ValidationReport#

FieldTypeRequiredDescription
validbooleanyes
api_typestringyes

One of openapi, asyncapi, proto.

versionstring—

The version the document declares, when parseable.

endpoint_countinteger—

How many endpoints/channels/RPCs the split found.

endpointsstring[]—

Preview of the first entries the split would store, as "METHOD path" strings (at most 20).

errorstring—

Why the document is invalid; absent when valid.

Example (placeholder values derived from the schema)

{
  "valid": false,
  "api_type": "openapi",
  "version": "string",
  "endpoint_count": 0,
  "endpoints": [
    "string"
  ],
  "error": "string"
}

ProducerVersionInfo#

FieldTypeRequiredDescription
api_typestringyes

One of openapi, asyncapi, proto.

versionstringyes
stabilityStabilityyes

Which of exactly two states a stored version is in. Declared by the caller on every Provide; a state of the stored version, never part of the version string. Snapshots are overwritable and expire when unused; GA is immutable and permanently claims its number.

One of snapshot, ga.

content_hashstringyes
provided_bystringyes

The authenticated Actor credited with this version's content — the last provider. A same-content promotion preserves the snapshot's provider, so the human who built it stays on the released version.

created_atstringyes

When this version first appeared on the line.

updated_atstringyes

Last successful Provide (overwrite or promotion).

last_required_atstring | null—

Last successful require against this version.

endpoint_countinteger (int64)yes
expires_atstring | null—

Use-based expiry for snapshots when cleanup is enabled; absent for GA (never age-culled) or when cleanup is off.

Example (placeholder values derived from the schema)

{
  "api_type": "openapi",
  "version": "string",
  "stability": "snapshot",
  "content_hash": "string",
  "provided_by": "string",
  "created_at": "string",
  "updated_at": "string",
  "last_required_at": "string",
  "endpoint_count": 1,
  "expires_at": "string"
}

WebhookEnvelope#

The JSON a webhook receiver gets (#47) — the body of the json format, and the facts behind the slack and teams bodies. No path serves it: Sanshain POSTs it to the URL a hook was registered with, signed with Sanshain-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>"> under the hook's secret, with Sanshain-Event (the type) and X-Sanshain-Delivery (the delivery id) alongside. See the CI integration guide for the receiver's side.

FieldTypeRequiredDescription
idstring (uuid)yes

Unique per event; a redelivery carries the same id.

typeWebhookEventTypeyes

One of version.published, provide.rejected, version.deprecated, dependency.recorded, branch.cut, catalog.recorded, webhook.test.

occurred_atstring (date-time)yes

RFC 3339, UTC, second precision.

sanshain_versionstringyes

The instance version that emitted the event.

dataone of WebhookVersionPublishedData, WebhookProvideRejectedData, WebhookVersionDeprecatedData, WebhookDependencyRecordedData, WebhookBranchCutData, WebhookCatalogRecordedData, WebhookTestDatayes

Per type — see the Webhook*Data schemas.

linksmap of string (uri)—

Deep links into the UI, keyed producer, version, consumer, graph, audit or admin as the type warrants. Omitted when the instance does not know its public base URL.

Example (placeholder values derived from the schema)

{
  "id": "string",
  "type": "version.published",
  "occurred_at": "2026-01-31T12:00:00Z",
  "sanshain_version": "string",
  "data": {
    "producer": "string",
    "api_type": "openapi",
    "version": "string",
    "stability": "snapshot",
    "promoted": false,
    "stream": "string",
    "actor": "string",
    "compatibility": {
      "against": "string",
      "impact": "none"
    }
  },
  "links": {
    "key": "https://example.org/"
  }
}

WebhookEventType#

Type: string. One of version.published, provide.rejected, version.deprecated, dependency.recorded, branch.cut, catalog.recorded, webhook.test.

WebhookStream#

dev, trunk, or a Sanshain-branch's name.

Type: string.

WebhookVersionPublishedData#

A Provide succeeded. The idempotent no-op emits nothing.

FieldTypeRequiredDescription
producerstringyes
api_typestringyes

One of openapi, asyncapi, proto.

versionstringyes
stabilityStabilityyes

Which of exactly two states a stored version is in. Declared by the caller on every Provide; a state of the stored version, never part of the version string. Snapshots are overwritable and expire when unused; GA is immutable and permanently claims its number.

One of snapshot, ga.

promotedbooleanyes

A Snapshot was promoted to GA in place.

streamWebhookStreamyes

dev, trunk, or a Sanshain-branch's name.

actorstring | nullyes
compatibilityobject | nullyes

On a GA Provide with a previous GA on the line — the classification against it.

compatibility.againststringyes
compatibility.impactstringyes

One of none, patch, minor, major.

Example (placeholder values derived from the schema)

{
  "producer": "string",
  "api_type": "openapi",
  "version": "string",
  "stability": "snapshot",
  "promoted": false,
  "stream": "string",
  "actor": "string",
  "compatibility": {
    "against": "string",
    "impact": "none"
  }
}

WebhookProvideRejectedData#

A Provide was refused by a version rule. Dry-runs emit nothing.

FieldTypeRequiredDescription
producerstringyes
api_typestringyes

One of openapi, asyncapi, proto.

versionstringyes

The attempted Version.

rulestringyes

One of immutable_ga, snapshot_on_ga, breaking_without_major, message_contract_conflict, ga_requires_releaser.

messagestringyes
proposed_versionstring | nullyes

The next free Version to publish as instead, where one exists.

actorstring | nullyes

Example (placeholder values derived from the schema)

{
  "producer": "string",
  "api_type": "openapi",
  "version": "string",
  "rule": "immutable_ga",
  "message": "string",
  "proposed_version": "string",
  "actor": "string"
}

WebhookVersionDeprecatedData#

A Provide newly marked elements deprecated (not deprecated in the previous GA).

FieldTypeRequiredDescription
producerstringyes
api_typestringyes

One of openapi, asyncapi, proto.

versionstringyes
stabilityStabilityyes

Which of exactly two states a stored version is in. Declared by the caller on every Provide; a state of the stored version, never part of the version string. Snapshots are overwritable and expire when unused; GA is immutable and permanently claims its number.

One of snapshot, ga.

elementsobject[]yes
elements[].pathstringyes
elements[].methodstringyes
document_deprecatedbooleanyes

Every element of the document is deprecated.

Example (placeholder values derived from the schema)

{
  "producer": "string",
  "api_type": "openapi",
  "version": "string",
  "stability": "snapshot",
  "elements": [
    {
      "path": "string",
      "method": "string"
    }
  ],
  "document_deprecated": false
}

WebhookDependencyRecordedData#

A require recorded an edge the Consumer did not hold yet in the declared stream.

FieldTypeRequiredDescription
consumerstringyes
producerstringyes
api_typestringyes

One of openapi, asyncapi, proto.

versionstringyes

The Pin.

stabilityStabilityyes

Which of exactly two states a stored version is in. Declared by the caller on every Provide; a state of the stored version, never part of the version string. Snapshots are overwritable and expire when unused; GA is immutable and permanently claims its number.

One of snapshot, ga.

snapshot_pinnedbooleanyes
streamWebhookStreamyes

dev, trunk, or a Sanshain-branch's name.

endpointsobject[]yes

The endpoints new to the edge.

endpoints[].pathstringyes
endpoints[].methodstringyes

Example (placeholder values derived from the schema)

{
  "consumer": "string",
  "producer": "string",
  "api_type": "openapi",
  "version": "string",
  "stability": "snapshot",
  "snapshot_pinned": false,
  "stream": "string",
  "endpoints": [
    {
      "path": "string",
      "method": "string"
    }
  ]
}

WebhookBranchCutData#

A Sanshain-branch was created. Global hooks only.

FieldTypeRequiredDescription
namestringyes
sourcestringyes

trunk, or the source branch's name.

as_ofstring (date-time)yes
cut_atstring (date-time)yes
actorstringyes

Example (placeholder values derived from the schema)

{
  "name": "string",
  "source": "string",
  "as_of": "2026-01-31T12:00:00Z",
  "cut_at": "2026-01-31T12:00:00Z",
  "actor": "string"
}

WebhookCatalogRecordedData#

A catalog push ran, readable or not.

FieldTypeRequiredDescription
systemstringyes
trunkbooleanyes
recordedbooleanyes
warningstring | nullyes

Why nothing was recorded, when recorded is false.

resourcesintegeryes

The number of Peripheral Resources recorded.

Example (placeholder values derived from the schema)

{
  "system": "string",
  "trunk": false,
  "recorded": false,
  "warning": "string",
  "resources": 0
}

WebhookTestData#

The "send test event" button, to one hook alone.

FieldTypeRequiredDescription
hook_idinteger (int64)yes
messagestringyes

Example (placeholder values derived from the schema)

{
  "hook_id": 1,
  "message": "string"
}