Syncle

HTTP API

The web interface has no privileged path into Syncle — everything it does goes through the REST API on this page, so anything you can click, you can script. Every route lives under /api, requests and responses are JSON, and a session cookie is the only authentication.

Conventions

The origin that serves the interface serves the API too: the web app proxies /api through to the API server, so with a default install the base URL is http://localhost:3002/api. The API container itself is not published outside the Docker network unless you expose it, so go through the web origin — the installation page covers the ports.

Every successful response is wrapped in a data envelope; every error is an error object with a machine-readable code, a human message, and a details field that is null unless the error carries extra data:

$ curl http://localhost:3002/api/health
{"data":{"ok":true}}

$ curl http://localhost:3002/api/connections
{"error":{"code":"UNAUTHORIZED","message":"Authentication required","details":null}}

Scripts should branch on the code, not the HTTP status alone — two codes share status 400:

CodeStatusMeaning
BAD_REQUEST400Invalid body or query parameter (schema validation)
QUERY_FAILED400The target database rejected a statement
UNAUTHORIZED401No valid session cookie
FORBIDDEN403Signed in, but not allowed to do this
NOT_FOUND404No such resource
CONFLICT409The request contradicts current state, such as deleting a connection a bridge still uses
RATE_LIMITED429Too many login or setup attempts; wait and retry
INTERNAL500Unexpected error
UNSUPPORTED501The engine cannot do what was asked
CONNECTION_FAILED502The target database could not be reached

Authentication

Syncle has a single admin account, created on first run with the setup token (the quickstart walks through that). Signing in sets db_session, a signed httpOnly cookie with SameSite=Lax, valid for the sessionTtlMinutes setting — one week by default. Keep it in a cookie jar and send it back on every call:

# sign in once, keeping the cookie
curl -c cookies.txt -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"your-password"}' \
  http://localhost:3002/api/auth/login

# every later call sends it back
curl -b cookies.txt http://localhost:3002/api/bridges

Repeated failed logins lock the account out per IP and username and answer 429 with code RATE_LIMITED; setup attempts are limited per IP. Changing the password invalidates every outstanding session at once — the response re-issues the cookie for the session that made the change.

EndpointBodyPurpose
GET /api/auth/statusReturns { needsSetup, authenticated, user }; public, so a client can decide which screen to show
POST /api/auth/setup{ username, password, setupToken }Create the admin account on first run and sign in
POST /api/auth/login{ username, password }Sign in; sets the session cookie
POST /api/auth/logoutClear the cookie
GET /api/auth/meThe signed-in user
POST /api/auth/change-password{ currentPassword, newPassword }Change the password; invalidates all other sessions

Workspaces

Workspaces are the top-level container for connections and bridges. A default workspace always exists, so you only meet these routes once you create a second one.

EndpointPurpose
GET /api/workspacesList workspaces
POST /api/workspacesCreate one — { name, color? }
GET /api/workspaces/:idFetch one
PUT /api/workspaces/:idUpdate name or color
DELETE /api/workspaces/:idTears down every bridge inside — CDC slots dropped, watchers stopped, in-flight jobs canceled — then deletes the workspace

Connections

A connection is a saved database config. Passwords, SSH secrets and connection strings are encrypted at rest and redacted in every response. Routes that touch data accept a ?database= query parameter to address one database on the server; leave it off to use the connection's default.

EndpointPurpose
GET /api/connections?workspaceId=List connections
POST /api/connectionsCreate one
POST /api/connections/testTest an unsaved config without storing it
GET /api/connections/:idFetch one
PUT /api/connections/:idUpdate; any pooled connection is evicted
DELETE /api/connections/:idDelete — answers 409 CONFLICT while any bridge still uses it as source or destination
POST /api/connections/:id/testTest a saved connection

Data operations

These are the routes behind the database workbench. What each engine supports varies — the driver's capability flags say which of them apply.

EndpointPurpose
GET /api/connections/:id/databasesList databases on the server
GET /api/connections/:id/schema?database=Introspect tables, columns, keys and indexes
POST /api/connections/:id/browse?database=Paged reads with filters and sort; limit 1–1000, default 100
POST /api/connections/:id/query?database=Ad-hoc query — { statement, params }
POST /api/connections/:id/rowsInsert a row
PATCH /api/connections/:id/rowsUpdate a row, identified by its primary key values
DELETE /api/connections/:id/rowsDelete a row

DDL, backup and restore

EndpointPurpose
POST /api/connections/:id/ddl/databaseCreate a database — { name }
POST /api/connections/:id/ddl/drop-databaseDrop a database (pooled connections to it close first)
POST /api/connections/:id/ddl/table?database=Create a table from a column-definition list
POST /api/connections/:id/ddl/drop-table?database=Drop a table — { table, schema? }
POST /api/connections/:id/ddl/truncate-table?database=Truncate a table
POST /api/connections/:id/backup?database=Dump a database; returns { filename, format, content }
POST /api/connections/:id/restore?database=Restore from { content, format }

Backups come in two formats: json, a portable dump any engine can read back, and sql, a DDL-plus-INSERT script for relational engines only — MongoDB and Redis support just json.

Drivers and settings

GET /api/drivers lists the five supported engines with their labels, default ports, capability flags and the fields their connection forms need — the interface builds its connection dialog from this list, and a script can do the same.

GET /api/settings returns the resolved app settings (stored overrides merged over defaults); PUT /api/settings applies a partial update. The configuration page documents each setting and its default.

Bridges

A bridge is the saved sync path, a job is one execution of it, and a delivery is one row or batch within a job — how bridges work covers the model. The routes below manage all three.

EndpointPurpose
GET /api/bridges?workspaceId=List bridges
GET /api/bridges/statuses?workspaceId=Latest job status per bridge, in one call
POST /api/bridgesCreate a bridge; a draft job is prepared so the timeline shows the planned deliveries right away
GET /api/bridges/:idFetch one
PUT /api/bridges/:idUpdate; a live watch or CDC listener is stopped first and restarted on the new config
DELETE /api/bridges/:idFull lifecycle teardown, then delete

Jobs and deliveries

EndpointPurpose
POST /api/bridges/:id/previewRender what would be delivered without delivering — body { sampleRow?, limit }, limit 1–10, default 3
POST /api/bridges/:id/jobsStart a job; the body may carry resumeJobId, jobId (start a prepared draft) or retryFailedOf
GET /api/bridges/:id/jobsList jobs
GET /api/bridges/:id/jobs/:jobIdJob detail — status, counts, cursor, error
POST /api/bridges/:id/jobs/:jobId/retry-failedRe-queue the same job to re-send only its failed deliveries
POST /api/bridges/:id/jobs/:jobId/cancelCancel; for a watch or CDC job this stops the listener and the job pauses, keeping its cursor
GET /api/bridges/:id/jobs/:jobId/deliveriesDelivery list; filters status= (one of success, failed, skipped), from, to, offset, limit
POST /api/bridges/:id/jobs/:jobId/skipSkip queued deliveries by { sequences }; returns { skipped }

The numeric delivery-list parameters must be non-negative integers — anything else is a 400, not an empty result. Skipping only affects deliveries that are still queued.

Live listening

EndpointPurpose
POST /api/bridges/cdc/readinessProbe whether a source can do CDC — { connectionId, database?, schema?, table }; see CDC setup for what readiness means per engine
POST /api/bridges/:id/watch/startStart live listening; routed to CDC or polling watch by the bridge's trigger
POST /api/bridges/:id/watch/stopStop listening and return the finalized job

Limits

  • JSON request bodies cap at 50 MB — sized so backup and restore payloads, which carry whole dumps, fit.
  • Ad-hoc query results cap at the maxQueryRows setting, default 5000 rows; the configuration page covers raising it globally or per connection.
  • Browse pages return at most 1000 rows per request (default 100).
  • A single skip call accepts at most 10,000 sequence numbers.