tapstateDocs
Reference

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 is headless and has no built-in web UI in this release.

The API has no stable OpenAPI publication or versioned URL namespace yet; pin the tapstate release and treat request and response compatibility as prerelease.

Prefer the CLI for interactive use. Use the REST surface when building a controlled integration that can handle authentication, coded errors, synchronous connector operations, and contract changes.

Base URLs and authentication

Unauthenticated endpoints:

MethodPathPurpose
GET/healthzProcess liveness only; returns ok.
GET/versionReport the server release and its versioned contracts.
POST/auth/bootstrapCreate the first administrator from loopback, once.
POST/auth/loginExchange 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": "<server-version>",
  "dslVersions": [],
  "dataVersion": null
}

version identifies the server release. dslVersions is reserved for the authoring grammar versions the server accepts, and dataVersion is reserved for the schema version of its system data. The current response keeps the reserved fields present even when they have no value; do not treat an empty list or null as a missing field.

Use /healthz only for process liveness. It returns ok and does not identify the running build.

Every /api/** endpoint requires:

Authorization: Bearer <token>

See Bootstrap and authenticate for the request sequence.

Create and use a user session

MethodPathPurpose
POST/auth/sessionExchange a saved user session for a fresh bearer token.
POST/auth/logoutRevoke 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

MethodPathPurpose
POST/api/artifacts:applyParse, validate, and apply a batch of raw YAML drafts.
POST/api/artifacts:validateParse and validate a batch of raw YAML drafts without storing them.
GET/api/artifactsList 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.

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.

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.

Connector and connection endpoints

MethodPathPurpose
POST/api/connectors:registerRegister a base64-encoded connector JAR.
GET/api/connectorsList the bundled and registered online catalog.
GET/api/connectors/{id}Read one connector's normalized fields and capabilities.
POST/api/connections:testRun and store a connector connection test synchronously.
GET/api/connections/{id}/test-resultRead the latest stored test report.
POST/api/connections:discover-schemaRun and store source discovery synchronously.
GET/api/connections/{id}/schemaRead 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.

Source endpoints

MethodPathPurpose
POST/api/sources:draftValidate a structured Source draft and render canonical YAML without storing it.
POST/api/sourcesCreate one structured source.
GET/api/sourcesList 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.

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.

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.

MethodPathPurpose
GET/api/sources/{sourceId}/collectionsList collections held by a declared source's database.
GET/api/sources/{sourceId}/collections/{collection}/statsRead connector-reported metadata for one collection.
POST/api/sources/{sourceId}/collections/{collection}:findRead 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}/tail

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

These endpoints require an administrator credential:

MethodPathPurpose
POST/api/tokensCreate a machine token with read, write, or admin scope.
GET/api/tokensList secret-free token descriptors.
POST/api/tokens/{id}:revokeRevoke 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

MethodPath
POST/api/pipelines/{id}:start
POST/api/pipelines/{id}:stop
POST/api/pipelines/{id}:pause
POST/api/pipelines/{id}:resume

Each response is 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.

Observation endpoints

MethodPathBoundary
GET/api/pipelines/{id}/statusStore-backed observed lifecycle state
GET/api/pipelines/{id}/metricsOpen metric map and optional perTableOffset
GET/api/pipelines/{id}/snapshotPer-table initial-load progress when available
GET/api/pipelines/{id}/logsBounded node-local log tail

The status response includes pipelineId and state. When the observed state records a job failure, it also includes a failure object with code, params, and a reader-facing message. The same shape is used in status watch frames. The failure object is omitted when there is no failure to report.

WebSocket endpoints:

PathBehavior
/api/pipelines/{id}/status/watchEmits status when the observed state changes.
/api/pipelines/{id}/logs/followEmits newly observed node-local log lines.

The WebSocket handshake uses the same bearer header. It returns HTTP 401 or 403 instead of upgrading when authentication or authorization fails.

Cluster endpoint:

MethodPathBoundary
GET/api/cluster/membersPresent but returns HTTP 501 in the current preview

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 statusTypical meaning
400Malformed JSON, DSL validation failure, or a request rejected as bad input
401Missing, invalid, expired, or rejected credential
403Authenticated but insufficient scope
404Artifact, source, connector, pipeline, test result, schema, or observation not found
409Closed bootstrap, an illegal/incompatible lifecycle state, or an artifact refusal such as artifact.in-use
412Source or artifact If-Match content hash is stale
428Source or artifact update/delete omitted a valid precondition
500Coded server or connector failure without a client-attributable mapping
501Endpoint 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.

CodeMeaning
artifact.not-foundThe requested artifact does not exist. A delete that already succeeded returns this code if it is attempted again.
artifact.precondition-requiredThe request did not provide a valid version precondition (428).
artifact.version-conflictThe supplied content hash is stale (412).
artifact.in-useAnother resource still refers to the artifact (409).
artifact.pipeline-not-stoppedThe artifact's pipeline is not stopped, so removal is refused (409).
artifact.reclaim-incompleteThe 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.

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 -c and -u, plus explicit creation and exchange of a revocable user session.
  • No v1 API prefix or compatibility guarantee exists yet.

On this page