> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wirekite.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Application Programming Interface

> Complete reference for the Wirekite REST API — every endpoint customers can call from scripts or applications.

## Getting Started

### Server

The API is a single Go binary, `wirekite-api`, listening on HTTPS.

* **Default port:** `9443` (override with `--port`)
* **Base URL:** `https://<host>:9443`
* **Version prefix:** every path begins with `/v1/`
* **TLS:** self-signed cert at `api/certs/server.crt` by default; override with `--cert` / `--key`

**Start command:**

```bash theme={null}
./api/wirekite-api --port 9443 --cert ./api/certs/server.crt --key ./api/certs/server.key
```

### Authentication

All endpoints except `/v1/health` require a Bearer token:

```
Authorization: Bearer <api_key>
```

Keys live in `api/config/api_keys.json`. Create one before calling protected endpoints:

```json theme={null}
{
  "keys": {
    "k_a1b2c3...": { "name": "default", "created_at": "2026-04-24T00:00:00Z" }
  }
}
```

Auth failures return `401 Unauthorized` with `error.code = "AUTH_FAILED"`.

### Response Envelope

Every response uses the same JSON shape:

```json theme={null}
{
  "success": true,
  "result": { },
  "error": null,
  "logs": { "extractor": "…tail of log…", "loader": "…" },
  "duration": "2m15s"
}
```

* `result` — endpoint-specific payload; `null` on error.
* `error` — `{ "code": "...", "message": "...", "details": {...} }` on error; `null` on success.
* `logs` — optional, included for synchronous operations.
* `duration` — optional, elapsed wall-clock time.

### Error Codes

| `error.code`                  | HTTP      | Meaning                                                        |
| ----------------------------- | --------- | -------------------------------------------------------------- |
| `AUTH_FAILED`                 | 401       | Missing or invalid Bearer token                                |
| `INVALID_REQUEST`             | 400       | Malformed body / bad parameters                                |
| `MIGRATION_NOT_FOUND`         | 404       | Migration does not exist                                       |
| `SOURCE_NOT_FOUND`            | 404 / 400 | Source does not exist / not created                            |
| `TARGET_NOT_FOUND`            | 404 / 400 | Target does not exist / not created                            |
| `OPERATION_CONFLICT`          | 409       | Another operation already running on this migration            |
| `OPERATION_NOT_FOUND`         | 404       | Operation id unknown                                           |
| `CONNECTION_REFUSED`          | 200 / 500 | Connection test failed (details in `error.details`)            |
| `SCHEMA_APPLY_FAILED`         | 500       | Schema apply exited non-zero                                   |
| `OBJECTS_APPLY_FAILED`        | 500       | Objects apply exited non-zero                                  |
| `EXTRACTOR_FAILED`            | 500       | Data/change extractor exited non-zero                          |
| `LOADER_FAILED`               | 500       | Data/change loader exited non-zero                             |
| `ORCHESTRATOR_FAILED`         | 500       | Orchestrator startup failed                                    |
| `RESET_FAILED`                | 500       | Reset operation failed                                         |
| `VALIDATE_FAILED`             | 500       | Data validation failed                                         |
| `DATA_FAILED`                 | 500       | Data migration failed                                          |
| `REPLICATION_FAILED`          | 500       | Replication failed                                             |
| `FIREBOLT_SCHEMA_PUBLIC_ONLY` | 400       | Firebolt target requires `public` schema (use `schema_rename`) |

### Synchronous vs. Asynchronous

| Category         | Behavior                                                                                             | Endpoints                                                                                                                                                                                     |
| ---------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Synchronous**  | HTTP blocks until done (≤ 15 min). Response includes `result` + `logs` + `duration`.                 | All CRUD, `/prereqs`, `/validate`, `/schema/extract`, `/schema/apply`, `/objects/apply`, `/reset`, `/validate` (data), `/sources/test`, `/targets/test`, `/queues/test`, pause/resume, health |
| **Asynchronous** | Returns immediately with `operation_id`. Poll `/v1/migrations/:name/status` or `/v1/operations/:id`. | `/data`, `/replication`, `/replication-queue`, `/data-replication`                                                                                                                            |

Async endpoints accept an optional `X-Request-Id: <uuid>` header. Repeating the same request id returns the same operation — safe for retries across flaky networks.

### Conventions in examples

The examples below use these shell variables:

```bash theme={null}
HOST=localhost               # or your server host
TOKEN=<your_api_key>
CURL='curl -sk'              # -k skips self-signed cert verification
```

***

## All Endpoints at a Glance

| Method | Path                                            | Sync?         |
| ------ | ----------------------------------------------- | ------------- |
| GET    | `/v1/health`                                    | sync (public) |
| PUT    | `/v1/sources/:name`                             | sync          |
| GET    | `/v1/sources/:name`                             | sync          |
| GET    | `/v1/sources`                                   | sync          |
| DELETE | `/v1/sources/:name`                             | sync          |
| POST   | `/v1/sources/test`                              | sync          |
| PUT    | `/v1/targets/:name`                             | sync          |
| GET    | `/v1/targets/:name`                             | sync          |
| GET    | `/v1/targets`                                   | sync          |
| DELETE | `/v1/targets/:name`                             | sync          |
| POST   | `/v1/targets/test`                              | sync          |
| POST   | `/v1/queues/test`                               | sync          |
| PUT    | `/v1/migrations/:name`                          | sync          |
| GET    | `/v1/migrations/:name`                          | sync          |
| GET    | `/v1/migrations`                                | sync          |
| DELETE | `/v1/migrations/:name`                          | sync          |
| POST   | `/v1/prereqs`                                   | sync          |
| POST   | `/v1/validate`                                  | sync          |
| POST   | `/v1/migrations/:name/schema/extract`           | sync          |
| POST   | `/v1/migrations/:name/schema/apply`             | sync          |
| POST   | `/v1/migrations/:name/objects/apply`            | sync          |
| POST   | `/v1/migrations/:name/data`                     | **async**     |
| POST   | `/v1/migrations/:name/replication`              | **async**     |
| POST   | `/v1/migrations/:name/replication/pause`        | sync          |
| POST   | `/v1/migrations/:name/replication/resume`       | sync          |
| POST   | `/v1/migrations/:name/replication-queue`        | **async**     |
| POST   | `/v1/migrations/:name/replication-queue/pause`  | sync          |
| POST   | `/v1/migrations/:name/replication-queue/resume` | sync          |
| POST   | `/v1/migrations/:name/data-replication`         | **async**     |
| POST   | `/v1/migrations/:name/reset`                    | sync          |
| POST   | `/v1/migrations/:name/validate`                 | sync          |
| GET    | `/v1/migrations/:name/status`                   | sync          |
| GET    | `/v1/operations`                                | sync          |
| GET    | `/v1/operations/:id`                            | sync          |
| POST   | `/v1/operations/:id/cancel`                     | sync          |
| POST   | `/v1/operations/:id/stop`                       | sync          |

***

## Health

### `GET /v1/health`

Public, no auth. Returns `200 OK`.

```bash theme={null}
$CURL https://$HOST:9443/v1/health
# → {"status":"ok"}
```

***

## Sources

Supported `type`: `mysql`, `mariadb`, `postgres`, `oracle`, `sqlserver`.

### `PUT /v1/sources/:name`

Create or replace a source.

**Request body:**

```json theme={null}
{
  "type": "mysql",
  "parameters": {
    "host": "10.0.0.1",
    "port": "3306",
    "username": "wirekite",
    "password": "secret",
    "database": "app"
  }
}
```

**Response `result`:** `{ "name": "mysql-prod", "type": "mysql" }`

```bash theme={null}
$CURL -X PUT https://$HOST:9443/v1/sources/mysql-prod \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"type":"mysql","parameters":{"host":"10.0.0.1","port":"3306","username":"wirekite","password":"secret","database":"app"}}'
```

### `GET /v1/sources/:name`

Returns the source; sensitive params (password, etc.) are masked as `"***"`.

### `GET /v1/sources`

Returns `{ "sources": [ { name, type, parameters } ... ] }` (sensitive masked).

### `DELETE /v1/sources/:name`

Deletes the source. Fails with `INVALID_REQUEST` if any migration still references it.

### `POST /v1/sources/test`

Test a connection without saving. Body has the same shape as `PUT`.

**Response `result`:** `{ "connected": true, "message": "…" }`

***

## Targets

Supported `type`:

* **Database targets:** `mysql`, `mariadb`, `postgres`, `oracle`, `sqlserver`, `snowflake`, `bigquery`, `spanner`, `firebolt`, `databricks`, `singlestore`, `mongodb`
* **Queue targets:** `kafka`, `redpanda`, `pubsub`

### `PUT /v1/targets/:name`

Create or replace a target.

**Database target body** (e.g. Spanner):

```json theme={null}
{
  "type": "spanner",
  "parameters": {
    "project": "my-project",
    "instance": "my-instance",
    "database": "my-db",
    "credentials_file": "/path/to/sa.json"
  }
}
```

**Queue target body** (Kafka/Redpanda):

```json theme={null}
{
  "type": "kafka",
  "parameters": {
    "brokers": "broker1:9092,broker2:9092",
    "topic": "wirekite-changes",
    "batchSize": "1",
    "compression": "snappy"
  }
}
```

`batchSize` optional (default `1`); `compression` optional (`none|snappy|lz4|zstd`).

**Response `result`:** `{ "name": "...", "type": "..." }`

### `GET /v1/targets/:name`

Get one (sensitive masked).

### `GET /v1/targets`

List all.

### `DELETE /v1/targets/:name`

Fails if referenced by a migration.

### `POST /v1/targets/test`

Test a database target connection. Body same shape as `PUT`. Queue types are rejected — use `/v1/queues/test` instead.

### `POST /v1/queues/test`

Test a Kafka/Redpanda broker + topic without saving.

```json theme={null}
{ "type": "kafka", "brokers": "broker1:9092,broker2:9092", "topic": "wirekite-changes" }
```

**Response `result`:** `{ "ok": true, "message": "broker reachable, topic 'wirekite-changes' found" }`

***

## Migrations

A migration binds one source to one or both of `fileTarget` (DB target) and `queueTarget` (Kafka/Redpanda target). At least one must be provided. Most operations require `fileTarget`; queue-only CDC uses `/replication-queue`.

### `PUT /v1/migrations/:name`

Create or replace a migration.

**Body:**

```json theme={null}
{
  "source": "mysql-prod",
  "fileTarget": "spanner-prod",
  "queueTarget": "kafka-prod",
  "tables": ["app.customers", "app.orders"],
  "threads": 4,
  "rows_per_file": 50000,
  "schema_rename": "app:public",
  "logDetail": "basic"
}
```

Required: `source`, `tables`, and at least one of `fileTarget` / `queueTarget`.
Optional: `threads`, `rows_per_file`, `schema_rename`, `logDetail` (`"basic"` default, or `"verbose"`).

**Response `result`:** `{ "name": "...", "source": "...", "fileTarget": "...", "queueTarget": "...", "tables": <count> }`

### `GET /v1/migrations/:name`

Return the migration config.

### `GET /v1/migrations`

List all as `{ "migrations": [ { name, source, fileTarget, queueTarget, tables: <count> } ... ] }`.

### `DELETE /v1/migrations/:name`

Delete migration + working directory. Rejects with `OPERATION_CONFLICT` if an operation is still running.

***

## Prerequisites & Validation

### `POST /v1/prereqs`

Get SQL that must be run on source and target before migration works. Does **not** execute anything.

**Body:**

```json theme={null}
{
  "source": { "type": "mysql", "parameters": {"host":"...","port":"3306","username":"...","database":"app"} },
  "target": { "type": "spanner", "parameters": {"project":"...","instance":"...","database":"..."} },
  "tables": ["app.customers","app.orders"],
  "mode": "data+replication",
  "schema_rename": "app:public"
}
```

`mode`: one of `data`, `replication`, `data+replication` (controls which grants are included).

**Response `result`:**

```json theme={null}
{
  "source_commands": "GRANT SELECT ON app.customers TO 'wirekite'@'%';\n...",
  "target_commands": "CREATE SCHEMA public;\nGRANT ... ;\n..."
}
```

### `POST /v1/validate`

Actually connect and check every prerequisite is satisfied. Body same shape as `/prereqs`, plus valid `password` values.

**Response `result`:**

```json theme={null}
{
  "ready": false,
  "checks": [
    { "name": "source: SELECT on app.customers", "passed": true },
    { "name": "source: binlog_format=ROW", "passed": false, "error": "binlog_format is 'STATEMENT'" }
  ]
}
```

***

## Schema

### `POST /v1/migrations/:name/schema/extract`

Extract DDL from source. Requires `fileTarget`. Produces `tables-schema.sql`, `objects-schema.sql`, `drop-tables.sql` inside the migration working directory. Times out after 15 minutes.

**Response `result`:** `{ "sql_file": "/path/to/tables-schema.sql", "tables": <count> }`

### `POST /v1/migrations/:name/schema/apply`

Apply DDL to target. Idempotent.

**Body (optional):** `{ "sql_file": "/custom/path.sql" }` — defaults to the extracted `tables-schema.sql`.

**Response `result`:** `{ "tables_created": <count> }`

***

## Objects

### `POST /v1/migrations/:name/objects/apply`

Apply database objects (views, procedures, triggers, functions) to the target. Runs after `schema/extract`. Partial failures are reported rather than aborting.

**Response `result`:** `{ "objects_succeeded": <count>, "objects_failed": <count> }`

***

## Data Migration (async)

### `POST /v1/migrations/:name/data`

Start data load (extract → move → load). Returns immediately.

**Body (optional):**

```json theme={null}
{ "resume": false }
```

Optional header: `X-Request-Id: <uuid>` for safe retries.

**Response `result`:**

```json theme={null}
{ "operation_id": "op-20260424-a1b2c3", "status": "running", "started_at": "2026-04-24T10:00:00Z" }
```

Poll `/v1/migrations/:name/status` or `/v1/operations/:id` for progress.

***

## Replication (async)

### `POST /v1/migrations/:name/replication`

Start CDC replication to `fileTarget`. Requires `schema/extract` to have been run.

**Body (optional):**

```json theme={null}
{ "resume": false, "position": "mysql-bin.000123:456" }
```

`position` format is source-type-specific (MySQL: `file:offset`; Oracle: SCN; SQL Server: hex LSN). Ignored when `resume=true`.

**Response `result`:** `{ "operation_id": "...", "status": "running", "started_at": "..." }`

### `POST /v1/migrations/:name/replication/pause`

Pause the running replication (process stays alive).

### `POST /v1/migrations/:name/replication/resume`

Resume a paused replication.

**Response `result`:** `{ "migration": "...", "action": "paused" | "resumed" }`

***

## Replication to Queue (async)

Use when the migration has `queueTarget` only (no `fileTarget`). Migrations that have a `fileTarget` are rejected by this endpoint.

### `POST /v1/migrations/:name/replication-queue`

Start queue-only CDC. No schema extract required.

**Body (optional):** `{ "resume": false, "position": "..." }`

**Response `result`:** `{ "operation_id": "...", "status": "running", "started_at": "...", "mode": "change-queue" }`

### `POST /v1/migrations/:name/replication-queue/pause` and `/resume`

Same shape as `/replication/pause|resume`.

***

## Data + Replication (async, chained)

### `POST /v1/migrations/:name/data-replication`

Two-phase: data load → hand off position → start CDC. Requires `fileTarget`.

**Body (optional):** `{ "resume": false, "position": "..." }`

Smart resume: if `position.pkt` already exists the data phase is skipped and CDC resumes from there; otherwise the data phase re-runs.

**Response `result`:** `{ "operation_id": "...", "status": "running", "started_at": "..." }`

***

## Reset

### `POST /v1/migrations/:name/reset`

Reset the target. Requires `fileTarget`.

**Body (optional):** `{ "mode": "drop" | "truncate" | "metadata" }` (default `drop`).

**Response `result`:**

* drop: `{ "mode": "drop", "tables_dropped": N, "drop_failed": M }`
* truncate: `{ "mode": "truncate", "tables_truncated": N, "truncate_failed": M }`
* metadata: `{ "mode": "metadata" }`

***

## Validate Data

### `POST /v1/migrations/:name/validate`

Compare row counts / checksums between source and target. Safe to run during active replication. Requires `fileTarget`.

**Body (optional):** `{ "sample_percent": 100 }` (0 or 100 = all rows; 1–99 = sample).

**Response `result`:**

```json theme={null}
{ "tables_checked": 12, "tables_matched": 11, "tables_failed": 1, "mismatches": ["app.orders"] }
```

***

## Status

### `GET /v1/migrations/:name/status`

Get live progress of the currently running operation, or a summary of the last completed operation when idle.

**Running (data phase):**

```json theme={null}
{
  "operation_id": "op-20260424-a1b2c3",
  "mode": "data",
  "status": "running",
  "phase": "loading",
  "started_at": "2026-04-24T10:00:00Z",
  "tables": { "total": 12, "extracted": 8, "loaded": 5 },
  "rows":   { "extracted": 3200000, "loaded": 2100000 },
  "current_table": "orders",
  "log_tail": "…"
}
```

**Running (change phase):**

```json theme={null}
{
  "operation_id": "...",
  "mode": "change",
  "status": "running",
  "phase": "replication",
  "rows": { "inserts": 45000, "updates": 12000, "deletes": 800 },
  "lag":  { "pending_files": 2 },
  "log_tail": "…"
}
```

**Idle:**

```json theme={null}
{
  "operation_id": null,
  "status": "idle",
  "last_operation": { "id": "...", "mode": "data", "status": "completed", "finished_at": "...", "duration": "2h15m" }
}
```

***

## Operations

Generic long-running-operation tracking across all async ops.

### `GET /v1/operations`

List with filters + pagination.

**Query params** (all optional): `migration=<name>`, `status=<running|completed|failed|cancelled|stopped>`, `mode=<data|change|change-queue|data-replication>`, `page_size=<1-200>` (default 50), `page_token=<offset>`.

**Response `result`:**

```json theme={null}
{
  "operations": [
    { "id":"…", "migration":"…", "mode":"data", "status":"running",
      "started_at":"…", "finished_at":"…", "pid":12345,
      "request_id":"…", "error": null }
  ],
  "total": 42,
  "next_page_token": "50"
}
```

### `GET /v1/operations/:id`

Full detail including `result`, `error`, and per-binary `logs` tails.

### `POST /v1/operations/:id/cancel`

Graceful termination (SIGTERM).

**Response `result`:** `{ "operation_id":"…", "status":"cancelled" }`

### `POST /v1/operations/:id/stop`

Force kill (SIGKILL) + cleanup of orphaned child processes.

**Response `result`:** `{ "operation_id":"…", "status":"stopped" }`
