Registering API Specs
Register OpenAPI 3.0 documents with the SEAL Gateway so workflows and the API Explorer can call HTTP operations on your behalf.
Registering API Specs
An API spec is the gateway's source of truth for an external HTTP API. It is an OpenAPI 3.0 document plus the metadata the gateway needs to talk to that API on behalf of an agent: a base URL, a credential resolution path, and an internal table of operations keyed by operationId.
You register a spec once. After that, workflows reference it by ID, and the API Explorer can issue ad-hoc requests against any operation in it.
An API spec on its own does nothing. It is a passive description. The gateway only makes outbound calls when a workflow step or an explorer request names an operation defined in a registered spec.
The shape of a spec
When you POST to /v1/specs, the gateway accepts the following request body:
| Field | Type | Required | Purpose |
|---|---|---|---|
name | string | yes | Human-readable label. Must be non-empty. |
base_url | string | yes | Absolute URL prefix prepended to every operation path. Trailing slashes are trimmed. |
inline_json | object | one of | The full OpenAPI document inlined in the request body. |
source_fetch_url | string | one of | A URL the gateway will GET to retrieve the OpenAPI document. |
source_url | string | no | A stable identifier for the upstream document. If a spec with the same source_url already exists, registration is deduplicated and returns the existing spec's ID. |
credential_path | object | yes | One of the five credential resolution strategies. Tells the gateway how to obtain credentials at invocation time. |
Exactly one of inline_json or source_fetch_url must be supplied. If both are missing the gateway returns 400 Validation.
After registration the gateway parses the OpenAPI document, indexes every operationId together with its method and path, and stores the parsed operations map on the spec. Operations without an operationId are dropped — give every operation an explicit operationId in the source document.
Inline vs source_fetch_url
Use inline (inline_json) when | Use source_fetch_url when |
|---|---|
| The OpenAPI document is generated by your build pipeline and you commit it. | The upstream vendor publishes a stable URL (Petstore, Stripe, GitHub, etc.). |
| You want byte-for-byte reproducibility — the document is captured at registration time. | You want the gateway to resolve the document fresh from upstream. |
| The document is small enough to send in a single HTTP request. | The vendor updates the document independently and you accept a snapshot at registration. |
In both cases the document is frozen on the spec record once registered. Re-fetching does not happen automatically — re-register the spec to pick up upstream changes (see Versioning below).
Example: register the Petstore OpenAPI
The Swagger Petstore is the canonical OpenAPI 3.0 reference document and is convenient for end-to-end testing. Register it with a StaticRef credential pointing at an OpenBao KV entry that holds an API key.
curl -X POST https://gateway.example.com/v1/specs \
-H "Authorization: Bearer $OPERATOR_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "petstore",
"base_url": "https://petstore3.swagger.io/api/v3",
"source_fetch_url": "https://petstore3.swagger.io/api/v3/openapi.json",
"source_url": "https://petstore3.swagger.io/api/v3/openapi.json",
"credential_path": {
"StaticRef": {
"key": "kv/data/petstore/api-key"
}
}
}'A successful response returns the spec's UUID:
{ "id": "8b3a4c1e-9f2d-4a1c-b0a7-2e5f7d6c1a44" }If a spec with the same source_url was registered earlier, the response is the existing record's ID with a deduplication marker:
{ "id": "8b3a4c1e-9f2d-4a1c-b0a7-2e5f7d6c1a44", "deduplicated": true }Example: register an internal Terraform Cloud spec
Terraform Cloud's REST API is a private, dynamic-credential target — the right credential strategy is SystemJit, which mints a short-lived token from an OpenBao secrets engine on every call.
curl -X POST https://gateway.example.com/v1/specs \
-H "Authorization: Bearer $OPERATOR_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "terraform-cloud",
"base_url": "https://app.terraform.io/api/v2",
"inline_json": {
"openapi": "3.0.0",
"info": { "title": "Terraform Cloud", "version": "v2" },
"paths": {
"/runs": {
"post": {
"operationId": "create_run",
"summary": "Create a new run",
"responses": { "201": { "description": "Created" } }
}
},
"/runs/{run_id}": {
"get": {
"operationId": "get_run",
"parameters": [
{ "name": "run_id", "in": "path", "required": true,
"schema": { "type": "string" } }
],
"responses": { "200": { "description": "OK" } }
}
},
"/plans/{plan_id}/json-output": {
"get": {
"operationId": "get_plan_output",
"parameters": [
{ "name": "plan_id", "in": "path", "required": true,
"schema": { "type": "string" } }
],
"responses": { "200": { "description": "OK" } }
}
}
}
},
"credential_path": {
"SystemJit": {
"openbao_engine_path": "terraform-cloud",
"role": "automation-runner"
}
}
}'The OpenBao engine at terraform-cloud/ issues bearer tokens for the automation-runner role, the gateway exchanges fresh per-invocation, and never persists the resulting token.
Lifecycle
Once registered, a spec is queryable, listable, and deletable:
| Endpoint | Method | Purpose |
|---|---|---|
/v1/specs | GET | List specs visible to the caller's tenant (tenant-scoped specs plus system-global ones). |
/v1/specs/{id} | GET | Return the full spec record, including the parsed operations map and the raw OpenAPI document. |
/v1/specs/{id} | DELETE | Remove the spec. |
List
curl -H "Authorization: Bearer $OPERATOR_JWT" \
https://gateway.example.com/v1/specsThe list endpoint returns a summary projection (id, name, source_url) — the full OpenAPI document is omitted for transfer efficiency. Fetch a single spec by ID to retrieve the full record.
Get
curl -H "Authorization: Bearer $OPERATOR_JWT" \
https://gateway.example.com/v1/specs/8b3a4c1e-9f2d-4a1c-b0a7-2e5f7d6c1a44Delete
curl -X DELETE \
-H "Authorization: Bearer $OPERATOR_JWT" \
https://gateway.example.com/v1/specs/8b3a4c1e-9f2d-4a1c-b0a7-2e5f7d6c1a44Deletion semantics. The current implementation deletes the spec record without checking for workflows that still reference it. A workflow whose api_spec_id no longer resolves will fail at invocation time with a NotFound("api spec not found for workflow") error — the workflow itself is left in place. Either delete dependent workflows first, or be ready to re-register the spec if you need to recover.
Versioning
The gateway has no built-in versioning for API specs. Treat the combination of name and id as immutable. There is no PUT /v1/specs/{id} endpoint — the only way to change a spec is to register a new one.
To upgrade a spec:
- Register the new OpenAPI document under a new
name(e.g.petstore-v3.1) and capture the new ID. - Update each workflow that referenced the old spec to point at the new ID. Because there is no
PUT /v1/workflows/{id}either, this means re-registering the workflow under the new ID. - Delete the old spec when no workflows reference it.
If you maintain a stable source_url and only need to refresh the upstream document, registering with the same source_url is the wrong path — it deduplicates and returns the existing record. Pick a different source_url value (for example by appending a version) when you intentionally want to register a fresh snapshot.
Tenant scope
Every spec is registered against the caller's tenant. A spec with tenant_id = null is system-global and visible to every tenant; one with a populated tenant_id is visible only to that tenant. The list endpoint already filters by tenant, so there is no manual scoping to do — what you see is what your tenant can use.
System-global specs are typically registered out-of-band by a platform operator with a service-account JWT that carries no tenant claim.
Audit events
Every successful registration emits an ApiSpecRegistered event into the gateway's event store, recording:
- the spec ID
- the spec name
- the registering operator
- the registration timestamp
These events are visible through the gateway's Observability surface and are the audit trail for spec lifecycle. Deletion is not currently emitted as an event — track removals through Postgres directly if you need a hard audit record of disappearances.
Common errors
| Status | Cause |
|---|---|
400 Validation | Neither inline_json nor source_fetch_url provided; name empty; base_url empty; OpenAPI document had no operations with operationId. |
400 Http | The gateway tried to fetch source_fetch_url and the upstream returned an error or non-JSON body. |
401 Unauthorized | Missing or invalid operator JWT. |
404 NotFound | GET or DELETE against an ID that no longer exists. |
Next steps
- Authoring Workflows — chain operations from a registered spec into a single named tool.
- API Explorer — issue an ad-hoc request against any operation in a registered spec and slice the response with JSONPath.
- Credential Resolution — pick the right
credential_pathstrategy for your spec.
Credential Resolution
How the SEAL Gateway resolves upstream credentials at invocation time — the five resolution strategies, their configuration, and their failure modes.
Authoring Workflows
Chain operations from a registered API spec into a single named tool — Handlebars body templates, JSONPath extractors, error policies, and a worked Terraform Cloud example.