REST API preview
Call the authenticated tapstate control-plane endpoints and interpret preview responses
The preview server exposes an HTTP control plane. This API exists only after a separate tapstate server is running; installing the CLI does not start the server or expose an HTTP port. The server image includes the web application at the server's root URL.
The API has no stable OpenAPI publication or versioned URL namespace yet; pin the tapstate release and treat request and response compatibility as prerelease.
Use the web application for interactive work. Use REST for integrations that handle authentication, coded errors, and contract changes.
Base URLs and authentication
Public endpoints and self-hosted sign-in endpoints:
| Method | Path | Purpose |
|---|---|---|
GET | /healthz | Process/engine liveness: 200 ok, or 503 broken after engine loss. |
GET | /version | Report the server release and its versioned contracts. |
GET | /.well-known/tapstate | Read issuer, cluster identity, API version, and authentication modes before presenting a cached credential. |
GET | /connector-icons/{id} | Read icon bytes for a registered connector; returns 404 if unavailable. |
POST | /auth/bootstrap | Create the first administrator from loopback, once. |
POST | /auth/login | Exchange username/password for a short-lived bearer token; it can also create a revocable user session. |
Read the server version
GET /version is available before authentication so a client can identify the
server before it logs in or reasons about supported operations:
curl -sS http://127.0.0.1:8080/version{
"version": "0.7.0",
"dslVersions": ["tapstate/v1"],
"dataVersion": 12
}version identifies the server release. dslVersions lists the accepted
authoring grammars, and dataVersion identifies the control-store schema.
The example shows v0.7.0 after its startup migrations. Use the reported values
when checking an upgrade; installing a newer CLI does not change them.
Use /healthz for process/engine liveness. An engine lost to out-of-memory
returns 503 with broken. A 200 response does not verify source, target, or
MongoDB health and does not identify the running build.
On a self-hosted server, authenticate /api/** requests with:
Authorization: Bearer <token>See Bootstrap and authenticate for the request sequence.
Cloud uses the managed browser session instead of the self-hosted bearer and local-user flow. Endpoint availability depends on the deployment mode.
| Method | Path | Purpose |
|---|---|---|
GET | /api/auth/me | Read the authenticated identity; the response is not cacheable. |
GET | /api/cluster/context | Read the cluster context from the current Cloud session; Cloud only. |
Cloud sign-in also exposes these system routes:
| Method | Path | Purpose |
|---|---|---|
GET | /auth/exchange | Exchange a provider-issued code, set the Cloud session cookie, and redirect to /. |
POST | /auth/invalidate-session | Provider back-channel callback to invalidate a Cloud session. |
The invalidation callback requires jti, ts, nonce, and sign parameters
verified against the provider's signature over POST|ts|nonce|jti. A user
bearer token does not authorize this callback. Both routes are Cloud-only and
return non-cacheable responses.
Create and use a user session
These endpoints manage local user sessions on a self-hosted server.
| Method | Path | Purpose |
|---|---|---|
POST | /auth/session | Exchange a saved user session for a fresh bearer token. |
POST | /auth/logout | Revoke a saved user session. |
POST /auth/login remains compatible with callers that send only username
and password: its token is the short-lived bearer credential for /api/**.
Set createSession to true only when the client needs a revocable user
session that it can later exchange for fresh bearer credentials:
{
"username": "admin",
"password": "<password>",
"createSession": true
}When requested, the response also includes sessionToken,
sessionIdleExpiresAt, and sessionAbsoluteExpiresAt. Keep the session token
secret. It is not a Bearer token and must not be sent to /api/**.
To obtain a fresh access token, send the session token to POST /auth/session:
Authorization: TapstateSession <sessionToken>The response has the ordinary access-token fields (token, accessExpiresAt,
issuer, principal, and scopes); use its token as the Bearer value for
/api/**. Send the same TapstateSession header to POST /auth/logout to
revoke the session. A successful logout returns HTTP 204.
Artifact endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /api/artifacts:apply | Parse, validate, and apply a batch of raw YAML drafts. |
POST | /api/artifacts:validate | Parse and validate a batch of raw YAML drafts without storing them. |
GET | /api/artifacts | List stored artifacts; optional kind query parameter. |
GET | /api/artifacts/{id} | Read one stored artifact. |
DELETE | /api/artifacts/{id} | Permanently remove one stored artifact; requires If-Match with the current content hash. |
Artifact deletion requires a quoted 64-character content hash from the artifact's current representation:
If-Match: "<64-hex-content-hash>"Read the artifact first to obtain the hash. The contentHash field is the
unquoted 64-character value returned by GET /api/artifacts/{id} (and by the
artifact list); wrap that value in quotes only when forming the If-Match
header:
{
"id": "orders_source",
"kind": "source",
"canonicalForm": "...",
"contentHash": "<64-hex-content-hash>"
}The server returns 428 when the header is missing or malformed, 412 when the hash is stale, and 204 after the artifact is removed.
Source artifacts have a display-only canonicalForm: connector config is
omitted, while contentHash still identifies the full stored resource. Do not
use this output as a backup or reapply it as a complete Source definition.
Use the structured Source endpoints to edit an existing connection.
Apply request:
{
"drafts": [
{
"source": "source/orders_source.tap.yml",
"content": "version: tapstate/v1\nkind: source\nid: orders_source\nconnector: mysql\nconfig:\n host: mysql.invalid\n port: \"3306\"\n database: appdb\n username: tapstate\nmode: snapshot\n"
}
]
}The server reparses the YAML and returns one CREATED, UPDATED, or UNCHANGED outcome per draft.
The validate endpoint returns a validation report. HTTP 200 can contain
valid: false and entries in diagnostics; check valid before treating the
workspace as accepted. For example, a v0.6 View that still includes schema
reports dsl.unknown-field in that list.
Apply can return dsl.upsert-needs-key after source discovery. Its params
identify the affected source, table, and resource path. Select a source
table with a discovered primary key, or use append only for insert-only
delivery. A unique index, target-side key, or tables[].pk does not satisfy the
current runtime check.
Preview pipeline data
| Method | Path | Purpose |
|---|---|---|
POST | /api/artifacts:preview | Execute a bounded, no-write sample preview and stream NDJSON events. |
The request contains pipelineId and a drafts array in the apply-request
shape. Optional fields are outputId, rootLimit, and sampleId. A draft
can include expectedContentHash to guard against a changed stored resource.
The request is limited to 256 drafts and 4 MiB.
Send Accept: application/x-ndjson, application/json to accept both the event
stream and JSON errors returned before streaming starts.
The successful response content type is application/x-ndjson. Read each event's kind
and payload; HTTP 200 means the stream opened, not that the preview
succeeded. run.completed and run.failed are terminal events. Closing the
connection cancels the preview.
Preview has a 45-second deadline and uses a default and maximum rootLimit
of 100 root samples. It neither applies the candidate resources nor writes to
the target. Results can use mongo-extended-json when the MongoDB target's
codec can render them, or logical-json otherwise; inspect the result format.
Sampling does not verify continuous CDC delivery. For draft compilation
without sample reads, use draft:preview below.
Connector and connection endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /api/connectors:register | Register a base64-encoded connector JAR. |
GET | /api/connectors | List the bundled and registered online catalog. |
GET | /api/connectors/{id}/icon | Read the registered connector icon, or 404 if unavailable. |
GET | /api/connectors/{id} | Read one connector's normalized fields and capabilities. |
POST | /api/connections:test | Run and store a connector connection test synchronously. |
GET | /api/connections/{id}/test-result | Read the latest stored test report. |
POST | /api/connections:discover-schema | Run and store source discovery synchronously. |
GET | /api/connections/{id}/schema | Read the latest stored discovered model. |
Use tapstate register instead of manually base64-encoding large JARs when possible.
Connection test request:
{
"id": "orders_source",
"connectorId": "mysql",
"settings": {
"deploymentMode": "standalone",
"host": "mysql.invalid",
"port": "3306",
"database": "appdb",
"username": "tapstate"
}
}A connector that completes its checks normally returns HTTP 200 even when the report has outcome: "FAILED". Inspect the outcome and checks. A connector that throws during testing returns a coded error response instead.
Connection test and discovery execute synchronously and can take longer than ordinary reads. Configure the caller and any reverse proxy with an appropriate timeout, and do not retry a write blindly.
The CLI and MCP gateway wait for connection tests and discovery without a fixed request deadline. A timeout imposed by another caller or proxy does not prove that the server stopped the operation; read its stored result before retrying.
Source endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /api/sources:draft | Validate a structured Source draft and render canonical YAML without storing it. |
POST | /api/sources | Create one structured source. |
GET | /api/sources | List structured sources. |
GET | /api/sources/{id} | Read a secret-redacted source and its ETag. |
PUT | /api/sources/{id} | Replace a source using If-Match. |
DELETE | /api/sources/{id} | Delete a source using If-Match. |
GET | /api/sources/{id}/schema | Read the discovered schema scoped specifically to the source's selected tables. |
GET /api/sources/{id}/schema filters the discovered schema to only the tables declared in the source's tables configuration. To inspect the full catalog of all discovered tables across the database, use GET /api/connections/{id}/schema.
When running POST /api/connections:test or POST /api/connections:discover-schema for an existing stored source, omitting secret fields (such as password) automatically reuses the credentials saved on the server.
POST /api/sources:draft is an authoring check, not a create operation. It
returns a redacted Source view and canonical YAML only when the live connector
catalog can supply the complete connector contract; it creates no artifact or
audit record.
Update and delete require the quoted 64-character content hash returned in ETag. Missing or stale preconditions return HTTP 428 or 412. Secret values are not returned; use the structured secret-clearing contract deliberately when replacing a source.
Use one write model consistently. Prefer /api/artifacts:apply for resource
authoring and pipeline batches. Use the structured source endpoints when a
client needs source-specific draft and ETag workflows.
Sample sources and State Store setup
| Method | Path | Purpose |
|---|---|---|
GET | /api/sample-sources | List deployment-configured sample sources. |
GET | /api/sample-sources/catalog | Read sample definitions and availability. |
POST | /api/sample-sources:install | Install the deployment's default sample set synchronously. |
POST | /api/sample-sources/installations | Start a background installation for selected sample IDs. |
GET | /api/sample-sources/installations/{jobId} | Read the initiating user's installation progress. |
POST | /api/state-store | Test and save a user-supplied MongoDB Atlas State Store connection; Cloud only. |
GET | /api/views | List collections maintained by pipelines. |
To install selected samples, send {"ids":["<available-sample-id>"]}. Select
1–10 distinct IDs whose catalog entries are available. The initial response
contains a job id, per-item stages, and finished: false. Poll the job until
finished is true, then inspect every item's stage and errorCode. A finished
job can contain failures. Jobs are in-memory and can expire; use the same
authenticated identity when polling.
POST /api/state-store accepts the Atlas connector configuration directly.
It requires a passed connection test before creating the atlas-store Source
with the store: "true" label, and refuses a second configured State Store.
This user connection is separate from Cloud's internal metadata and operator
state databases. The endpoint is unavailable in self-hosted mode.
Read data through a declared source
The data-browser endpoints inspect the data behind an existing source. Each request resolves the source's configured connection; callers cannot supply a database URI, database name, or database-native query command.
Data access boundary: These endpoints are read-scoped, but they can return source records and do not create audit events in this preview. There is no separate data-browser permission, so any authenticated caller with read access can use them. Keep the REST control plane off untrusted networks, use HTTPS for remote access, and issue a dedicated read-scoped token to each integration.
| Method | Path | Purpose |
|---|---|---|
GET | /api/sources/{sourceId}/collections | List collections held by a declared source's database. |
GET | /api/sources/{sourceId}/collections/{collection}/stats | Read connector-reported metadata for one collection. |
POST | /api/sources/{sourceId}/collections/{collection}:find | Read a bounded preview of rows from one collection. |
Collection listing returns the database's actual collections, not only
resources declared in a workspace. The stats response can include
numOfRows, storageSize, and avgObjSize; unavailable values are not zero.
numOfRows is an estimate from connector metadata rather than a counted total.
The find request body is optional. It can contain filter, sort, and
limit:
{
"filter": {
"field": "status",
"op": "eq",
"value": "paid"
},
"sort": {
"field": "total",
"dir": "desc"
},
"limit": 25
}filter is either one {field, op, value} term or one all or any
combination of terms. Supported operators are eq, ne, gt, gte, lt,
lte, in, exists, and contains. in requires a non-empty list,
exists requires a Boolean value, and contains requires text. The API rejects
database-native operators and arbitrary nested Boolean groups.
Use asc or desc for sort.dir. A literal dotted field name must escape the
dot in the JSON value, for example "price\\.usd". Such a field can be
filtered but cannot be sorted; the server returns
data-browser.unorderable-field.
find is a one-shot preview, not a paginated query. The default is 10 rows and
the maximum is 200. The response includes rows, approximateTotal, and
moreAvailable; it has no cursor or next-page token. approximateTotal might
be unavailable for a filtered read.
In this preview, row previews are available only for sources that use the
mongodb connector. Collection listing and statistics can still be available
for other connectors. A row read against another connector returns
data-browser.connector-not-browsable.
Stream collection changes
Connect to this WebSocket endpoint to stream changes from one collection:
/api/data-browser/{source}/{collection}/tailOn a self-hosted server, the WebSocket handshake requires the same bearer
credential as other /api requests. To filter the stream, provide the structured filter as URL-encoded
JSON in the filter query parameter. Each message contains a kind of
INSERT, UPDATE, or DELETE, an at timestamp, and any available before
or after row.
The stream is supported only for MongoDB sources in this preview. It reports
changes as they reach the store, so rapid intermediate updates might not appear
as separate messages. A stream with no displayed change for 10 minutes closes
with data-browser.follow-idle; a deleted source or a connector stream failure
also ends the connection with a coded reason.
Machine-token endpoints
On a self-hosted server, these endpoints require an administrator credential:
| Method | Path | Purpose |
|---|---|---|
POST | /api/tokens | Create a machine token with read, write, or admin scope. |
GET | /api/tokens | List secret-free token descriptors. |
POST | /api/tokens/{id}:revoke | Revoke a machine token immediately. |
The create response returns the bearer value exactly once. Store it in a secret
manager or process environment; later list responses do not return it. Scope is
ordered read < write < admin, and a token can call operations at or below
its own grade.
Create a write-scoped token with an administrator credential:
curl --fail-with-body --silent --show-error \
-X POST "$TAPSTATE_URL/api/tokens" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
--data '{"scope":"write"}'The response has this shape. The token value is returned only in this create
response:
{
"tokenId": "<token-id>",
"scope": "WRITE",
"token": "<one-time-bearer-value>",
"createdAt": "<timestamp>"
}List secret-free token metadata with GET /api/tokens. Revoke a token with
POST /api/tokens/<token-id>:revoke; revocation returns HTTP 204 and takes
effect immediately.
Pipeline lifecycle endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /api/pipelines/{id}:start | Request a transition to RUNNING. |
POST | /api/pipelines/{id}:stop | Request a transition to STOPPED; requires a purgeState request body. |
POST | /api/pipelines/{id}:pause | Request a transition to PAUSED. |
POST | /api/pipelines/{id}:resume | Request a transition to RUNNING from PAUSED. |
Stopping a pipeline requires a JSON body stating whether to clear internal checkpoint offsets and operator state:
{
"purgeState": false
}- Set
purgeStatetofalseto keep checkpoints and operator state intact so a future start resumes where it left off. - Set
purgeStatetotrueto purge this pipeline's checkpoint position and operator state so the next start has no position to carry on from (on a dedicated source connection, this restarts from the source beginning; on a shared mining chain where other pipelines are running, shared chain state and initial-load records are preserved while this pipeline starts fresh). - Omitting the body or
purgeStatereturns HTTP400with codelifecycle.purge-state-not-stated.
Each lifecycle response returns desired state:
{
"pipelineId": "orders_to_operational_state",
"targetState": "RUNNING",
"revision": "<content-hash>"
}The response proves that the intent was accepted. Read observed status and destination data before reporting execution success.
Pipeline layout endpoints
Persist and retrieve visual node coordinates and canvas viewport state for pipeline editors.
| Method | Path | Purpose |
|---|---|---|
GET | /api/pipelines/{id}/layout | Read node canvas positions and viewport zoom for one pipeline. |
PUT | /api/pipelines/{id}/layout | Save node positions and viewport. |
Example layout document:
{
"nodes": {
"orders_source": { "x": 120.0, "y": 80.0 },
"shape_order": { "x": 360.0, "y": 80.0 },
"operational_state": { "x": 600.0, "y": 80.0 }
},
"viewport": {
"x": 0.0,
"y": 0.0,
"zoom": 1.0
}
}- Path identity: The request body omits the pipeline ID; the URL path owns resource identity.
- Diagnostics: Returns
200 OKwith the saved layout view. If{id}does not exist, returns HTTP404(pipeline.not-found).
Pipeline definition endpoints
Create or replace complete pipeline resource definitions:
| Method | Path | Purpose |
|---|---|---|
GET | /api/pipelines | List the unified catalog, including draft-only pipelines; accepts optional offset and limit. |
GET | /api/pipelines:artifacts | List artifact-backed pipeline views; accepts optional offset and limit. |
GET | /api/pipelines/{id} | Read one structured pipeline view and its ETag. |
POST | /api/pipelines | Create a pipeline resource. Returns 201 Created with ETag. |
PUT | /api/pipelines/{id} | Replace a pipeline definition. Requires optimistic concurrency control with If-Match. |
- Concurrency control:
PUT /api/pipelines/{id}enforces optimistic concurrency control using theIf-Matchheader. Pass the quoted SHA-256 ETag fromGET(e.g.If-Match: "<hash>"). Omitting or misformattingIf-Matchreturns HTTP428(pipeline.precondition-required). If the ETag does not match the server's current stored hash, the request returns HTTP412(pipeline.version-conflict). - Duplicate ID rejection:
POST /api/pipelinesreturns HTTP409(pipeline.already-exists) if{id}is already registered.
Pipeline draft endpoints
A draft stores authoring state separately from the published pipeline artifact. Saving or compiling a draft does not start a pipeline.
| Method | Path | Purpose |
|---|---|---|
GET | /api/pipeline-drafts | List draft summaries; accepts optional offset and limit. |
GET | /api/pipelines/{id}/draft | Read the saved draft and its revision ETag. |
POST | /api/pipelines/{id}/draft | Create a draft; returns 201 and its ETag. |
PUT | /api/pipelines/{id}/draft | Replace a draft using If-Match. |
DELETE | /api/pipelines/{id}/draft | Discard a draft using If-Match; returns 204. |
POST | /api/pipelines/{id}/draft:preview | Compile the saved draft or an unsaved candidate to an artifact and DSL. |
POST | /api/pipelines/{id}/draft:publish | Compile and apply a draft at the stated revision. |
POST | /api/pipelines/{id}/draft:rebase | Update the draft's base artifact hash after an explicit conflict review. |
Draft bodies include pipelineId, mode (graph or wizard), and the
matching graph or wizard document. pipelineId must match the path. The
server owns revision increments, timestamps, and updatedBy.
Draft ETag values are quoted positive integer revisions, such as "3".
They differ from artifact SHA-256 hashes. Copy the draft's current ETag into
If-Match for replacement or deletion. Missing or malformed preconditions
return 428 (pipeline-draft.precondition-required); a changed revision
returns 412 (pipeline-draft.revision-conflict).
draft:preview with no body compiles the current saved revision. A body can
specify revision, and optionally candidate containing an unsaved draft.
It returns artifact and dsl, without applying resources or reading data.
Publish with {"revision":3,"artifactHash":"<current-artifact-hash>"}.
For a new pipeline, the hash can be null. A changed underlying artifact
returns 409 (pipeline-draft.artifact-conflict); review the newer artifact
before rebasing with an explicit revision and artifactHash (string or
null). Rebase preserves the draft edits; it does not merge them automatically.
Publishing does not start or restart the pipeline. Discarding a draft does
not delete its published artifact.
Pipeline position endpoints
Inspect and manage checkpoint resume positions across mining chains:
| Method | Path | Purpose |
|---|---|---|
GET | /api/pipelines/{id}/position | Read checkpoint resume tokens for each mining chain feeding the pipeline. |
PUT | /api/pipelines/{id}/position | Update checkpoint resume positions for a stopped pipeline. |
Position document shape:
{
"pipelineId": "orders_to_operational_state",
"chains": [
{
"chainId": "chain_mysql_appdb",
"sourceId": "orders_source",
"tables": ["orders"],
"resumeFrom": {
"token": "<opaque-resume-token>"
},
"recordedAt": "2026-09-17T03:00:00Z",
"targetAcked": null,
"sharedWith": []
}
]
}- Per-chain granularity: Positions belong to physical mining chains rather than individual tables.
sharedWithlists any other pipelines reading the same chain. - Modifications require stopped state:
PUT /api/pipelines/{id}/positionmodifiesresumeFrom.token. The pipeline and all pipelines sharing its mining chains must be stopped; otherwise, the write is refused with HTTP409(position.write-back-while-live).
Schema evolution and derived schema endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/pipelines/{id}/derived-schema | Compare recorded, derived, and target column models across pipeline steps. |
POST | /api/pipelines/{id}:accept-derived-schema | Synchronize the pipeline to current source schemas and lock the next start to the derived output. |
GET /api/pipelines/{id}/derived-schema returns a list of step reports comparing three column models:
recorded: The schema previously recorded for that step (omitted for uninferrable steps such as custom scripts).derived: The current schema worked out from stored discovery models through transforms.target: The column layout declared on the destination table.
POST /api/pipelines/{id}:accept-derived-schema copies stored source discovery models into the pipeline definition and adopts the derived columns as the baseline for future runs (run source schema discovery first when upstream database structures change). Attempting to accept a derived schema while the pipeline is active is rejected with HTTP 500 (actuation.schema-sync-while-running).
Observation endpoints
| Method | Path | Boundary |
|---|---|---|
GET | /api/pipelines/{id}/status | Lifecycle intent, display phase, and available runtime observation |
GET | /api/pipelines/{id}/metrics | Open metric map and optional perTableOffset |
GET | /api/pipelines/{id}/metrics/history | Bounded output-rate and selected-table lag history |
GET | /api/pipelines/{id}/explain | Explanation derived from current observation evidence |
GET | /api/pipelines/{id}/snapshot | Per-table initial-load progress when available |
GET | /api/pipelines/{id}/logs | Bounded node-local log tail |
The status response separates lifecycle intent from runtime evidence:
| Field | Meaning |
|---|---|
state | Observed lifecycle state, or NEW before the first runtime observation. |
desiredState | Requested lifecycle intent, when one exists. |
displayState | Catalog display phase, including DRAFT and STARTING. |
hasArtifact | Whether a published pipeline artifact exists. |
observedState, observedAt, observedAgeMillis | Runtime state and its age; omitted before the first observation. |
failure | Observed failure with code, params, and message; omitted when absent. |
plan, awaitingRebalance | Execution-plan details and pending placement changes, when available. |
A saved draft can have displayState: "DRAFT" and hasArtifact: false.
A start request can produce displayState: "STARTING" while state is still
NEW. Metrics, snapshot, and explain require an applied pipeline and a runtime
observation. An applied pipeline without an observation returns
monitor.no-observation; a draft without a published artifact returns
lifecycle.unknown-pipeline.
WebSocket endpoints:
| Path | Behavior |
|---|---|
/api/pipelines/{id}/status/watch | Emits status when the observed state changes. |
/api/pipelines/{id}/logs/follow | Emits newly observed node-local log lines. |
On a self-hosted server, the WebSocket handshake uses the same bearer header.
It returns HTTP 401 or 403 instead of upgrading when authentication or
authorization fails.
Status watch waits for runtime observations; it does not invent a first frame for a known but unobserved pipeline. An unknown pipeline is refused with a coded error.
Pipeline history
GET /api/pipelines/{id}/metrics/history accepts:
| Parameter | Required | Contract |
|---|---|---|
from | Yes | Inclusive RFC3339 timestamp with a UTC offset. |
to | Yes | Exclusive RFC3339 timestamp with a UTC offset; later than from, at most 15 days apart. |
resolution | No | auto (default), raw, PT5M, PT30M, PT1H, PT3H, or PT6H. |
limit | No | Page point budget, 1–1000; default 240. |
table | No | Repeat for each table whose lag is needed; at most 20. Omit for output rates only. |
cursor | No | Opaque nextCursor from the preceding page of the same query. |
For example, with a bearer token held in TAPSTATE_TOKEN:
curl --fail-with-body --get "$TAPSTATE_SERVER/api/pipelines/orders/metrics/history" \
-H "Authorization: Bearer $TAPSTATE_TOKEN" \
--data-urlencode 'from=2026-09-29T01:00:00Z' \
--data-urlencode 'to=2026-09-29T02:00:00Z' \
--data-urlencode 'resolution=PT5M' \
--data-urlencode 'table=orders' \
--data-urlencode 'table=shipments'Set TAPSTATE_SERVER to your server's HTTP or HTTPS base URL. Use a range
containing your pipeline run.
The response includes the requested and effective ranges, retentionCutoff,
effectiveResolution, status, consistency, segments, gaps,
unavailable, and nullable nextCursor. Rates describe target-confirmed
records and bytes. Table lag is the age of the last acknowledgement. Do not
turn gaps, counter resets, or unavailable values into zeroes.
NO_RETAINED_SAMPLES can be a successful empty result. Samples default to a
60-second interval and 15-day retention. Pagination is EVENTUAL: it is not
a frozen audit snapshot. To continue, repeat all query parameters unchanged
and add cursor. A signed cursor lasts 10 minutes; nextCursor: null ends
the walk.
| Code | HTTP | Response |
|---|---|---|
control.malformed-request | 400 | Correct the time range, resolution, or limit. |
monitor.invalid-cursor | 400 | Repeat the original query with its cursor, or start a new query. |
monitor.cursor-expired | 410 | Start a new query without the expired cursor. |
monitor.query-budget-exceeded | 400 | Reduce the range or selected tables; a request can scan at most 25,000 raw samples. |
Pipeline explanations
GET /api/pipelines/{id}/explain interprets the current observation. It does
not run a new pipeline or inspect every target row. NO_MATCH means no
explanation rule matched. Missing observations return
monitor.no-observation; see Observe a pipeline.
Cluster topology
| Method | Path | Boundary |
|---|---|---|
GET | /api/cluster/members | Authenticated cluster membership and pipeline placement read |
The response contains clusterId, nullable topologyRevision, members,
and pipelines. Pipeline placement is included in this response; there is no
separate pipeline topology endpoint. A missing revision means unknown, not
revision zero. A refused read is not an empty healthy cluster. See
Configure a cluster.
Error responses
Coded failures use:
{
"code": "lifecycle.illegal-transition",
"params": {
"from": "RUNNING",
"verb": "start"
},
"message": "..."
}Treat code as the stable machine key. message is reader-facing and can change.
engine.job-failed is a pipeline failure code, not a request-level HTTP error.
When it appears as failure.code in a status response or status watch frame, it
means that the pipeline stopped because its job failed. Inspect
failure.message and the pipeline logs for the underlying cause.
Common status mappings:
| HTTP status | Typical meaning |
|---|---|
400 | Malformed JSON, DSL validation failure, or a request rejected as bad input |
401 | Missing, invalid, expired, or rejected credential |
403 | Authenticated but insufficient scope |
404 | Artifact, source, connector, pipeline, test result, schema, or observation not found |
409 | Closed bootstrap, an illegal/incompatible lifecycle state, or an artifact refusal such as artifact.in-use |
412 | Source or artifact content hash, or draft revision, is stale |
428 | An update or delete omitted a valid version precondition |
500 | Coded server or connector failure without a client-attributable mapping |
501 | Endpoint is present but not implemented |
Artifact errors
Artifact edits and deletions use a shared artifact.* error domain:
The GET /api/artifacts/{id} read path currently returns HTTP 404
without a structured error body when the ID is absent. Handle the HTTP status
first and do not require an artifact.not-found JSON envelope for that read;
the coded error is used by artifact mutation/precondition paths.
| Code | Meaning |
|---|---|
artifact.not-found | The requested artifact does not exist. A delete that already succeeded returns this code if it is attempted again. |
artifact.precondition-required | The request did not provide a valid version precondition (428). |
artifact.version-conflict | The supplied content hash is stale (412). |
artifact.in-use | Another resource still refers to the artifact (409). |
artifact.pipeline-not-stopped | The artifact's pipeline is not stopped, so removal is refused (409). |
artifact.reclaim-incomplete | The deletion succeeded, but dependent cleanup did not finish. Do not retry the deletion. Record the reason and residue parameters and contact the server operator; a retry returns artifact.not-found. |
Lifecycle and execution errors
| Code | Meaning |
|---|---|
lifecycle.purge-state-not-stated | A stop request omitted the mandatory purgeState boolean field (400). |
lifecycle.illegal-transition | The requested transition is forbidden from the pipeline's current lifecycle state (409). |
lifecycle.incompatible-revision | The pipeline's target revision is older than its latest applied revision (409). |
position.write-back-while-live | Attempted to update checkpoint positions while a pipeline reading the chain is active (409). |
position.chain-not-read | The position document specifies a chain not consumed by this pipeline (400). |
position.field-not-editable | An immutable position attribute other than resumeFrom.token (such as resumeFrom.epoch or resumeFrom.seq) was altered (400). |
actuation.schema-sync-while-running | Attempted to accept a derived schema while the pipeline is active (500). |
Unexpected server errors can return HTTP 500 without the coded envelope.
Preserve the status, response body, method, path, and tapstate version when
reporting one.
Version boundary
- The preview supports both interactive connected sessions and one-shot connected
commands seeded with
-cand-u, plus explicit creation and exchange of a revocable user session. - No
v1API prefix or compatibility guarantee exists yet.