REST API

Quickstart

Get an API key, point at your instance, make your first request — and learn the conventions you'll see across all 400+ endpoints.

Prerequisites

Step 1: Authentication

Two authentication modes are supported. Pick whichever fits your use case:

Option A — API key (recommended for service accounts)

  1. Sign in to your Etlworks instance.
  2. Open Settings → Users, select the user, click Edit.
  3. Generate an API key under API Key. Optionally set an expiration.
  4. Copy the key now — you won't see it again after closing the dialog.
Treat keys like passwords

API keys inherit the generating user's role and tenant scope. Anyone with the key can do whatever that user can do. Store keys in a secrets manager — never commit them to git or paste into chat.

Send the key on every request via the Authorization header:

HTTP
Authorization: Bearer YOUR_API_KEY

Option B — Username/password → JWT

For interactive apps, exchange username + password for a short-lived JWT, then send the JWT on subsequent calls. Same Authorization: Bearer … header.

Shell
curl -s -X POST https://app.etlworks.com/rest/v1/token-auth \
  -H "Content-Type: application/json" \
  -d '{"login":"alice@example.com","password":"…"}'
# → { "token": "eyJhbGciOi…" }

JWTs expire (default: 24 hours). Refresh by re-authenticating, or call POST /v1/token-auth/refresh with a still-valid token.

Step 2: Base URL

HTTP
https://your-instance.etlworks.com/rest

Replace your-instance.etlworks.com with your deployment hostname. Etlworks Cloud customers use app.etlworks.com. For on-premise installations use the host you configured.

All endpoints in this reference are relative to /rest. So /v1/flows in the docs means https://your-instance.etlworks.com/rest/v1/flows on the wire.

Step 3: Your first request

List your flows. This is the cheapest call you can make — pure read, returns fast, exercises auth.

Shell
curl -s https://app.etlworks.com/rest/v1/flows \
  -H "Authorization: Bearer YOUR_API_KEY"

You should see a JSON array of Flow objects (timestamps are unix milliseconds):

JSON
[
  {
    "id": 12345,
    "name": "Stripe to Snowflake — daily",
    "description": "Daily incremental sync of charges into the warehouse",
    "flowType": "ETL.database.event",
    "tags": ["production","stripe"],
    "enabledSchedules": 1,
    "disabledSchedules": 0,
    "latestFinishedStatus": "success",
    "createdBy": "alice@example.com",
    "created": 1715366462000,
    "modifiedBy": "alice@example.com",
    "modified": 1715369462000
  }
]
401 Unauthorized?

Check the Authorization header is Bearer <key> with a single space and no quotes. The key string itself has no Bearer prefix.

404 Not Found?

Confirm your base URL is correct and includes /rest. The full path for this call is /rest/v1/flows — not /v1/flows or /api/v1/flows.

Step 4: Make a write call

Run a flow. Substitute a real flow ID from Step 3. The request body is a flat map of {string: string} parameters — not wrapped in {"params": …}. Send {} if your flow takes no parameters.

Shell
curl -s -X POST https://app.etlworks.com/rest/v1/flows/12345/run \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"runDate":"2026-05-09"}'

The response is a FlowExecutionResponse with the auditId you'll use to poll progress:

JSON
{
  "flowId": 12345,
  "auditId": 8721934,
  "status": 0
}

The numeric status is the start outcome (0 = started, 1 = already running, 2 = canceled, etc.) — not the run's progress. To track progress, poll GET /v1/executions/{flowId}?auditId={auditId}; that returns a FlowAuditRecord with a string status field whose values follow the FlowStatus enum: queued, running, success, warning, error, canceled. Loop until you see one of the terminal four. The run-and-wait example on the examples page shows the full pattern.


Content types

The API speaks JSON throughout, with two exceptions: file uploads (multipart/form-data) and a handful of legacy export endpoints that return text/html for browser-rendered diagrams. Defaults:

Pagination

Many list endpoints — particularly /v1/audit, /v1/executions/{flowId}/history, and bulk metric queries — accept offset/limit query parameters:

ParamTypeDefaultDescription
limitinteger50Max items returned in one call. Hard cap is 200 on most endpoints.
offsetinteger0Skip the first N items.

Where supported, total counts come back via the X-Total-Count response header.

Not every list endpoint paginates

Some catalog endpoints — including GET /v1/flows — return the full list in scope today. Inspect a list response: if it always contains every item regardless of limit, treat it as un-paginated. The per-group reference pages (rolling out in Phase 2) will document pagination behavior per endpoint.

Filtering & sort

Most list endpoints accept domain-specific filters as query params:

FilterExampleBehavior
q?q=stripeSubstring match against the resource's name field.
tag?tag=productionRestrict to resources with this tag (repeatable).
sort?sort=name or ?sort=-createdAtSort field, optionally with leading - for descending.
since?since=2026-05-01T00:00:00ZISO-8601 timestamp; only resources updated after this point.

Specific filters per endpoint are documented in the API reference.

Error handling

HTTPMeaningAction
200Success. Some endpoints return application-level errors in the body — check success: false.Parse normally.
201Created (returned by POST endpoints that create resources).Use the id from the response.
204No content (DELETE endpoints).Empty body is expected.
400Bad request — missing field, malformed JSON, or invalid values.Inspect the error message and fix the request.
401Unauthorized — missing/invalid/expired token.Re-authenticate or rotate the API key.
403Forbidden — the calling user lacks permission for this action.Adjust the user's role or use a different API key.
404Resource not found.Check the ID exists and the user can see it.
409Conflict — duplicate name, optimistic-lock failure, or concurrent edit.Refetch the resource and retry with the latest version.
415Unsupported media type — you sent the wrong Content-Type.Most endpoints want application/json.
429Too many requests.Back off; honor the Retry-After header.
500Internal server error.Retry with backoff; if it persists, contact support.

Error response shape (4xx and 5xx):

JSON
{
  "status": 400,
  "error": "Bad Request",
  "message": "Field 'name' is required",
  "path": "/rest/v1/flows"
}

Rate limits

Cloud accounts share a soft limit of 600 requests/minute per organization. Hitting the limit returns 429 Too Many Requests with a Retry-After header (seconds). On-premise deployments are uncapped by default but configurable via system settings.

Heavy bulk operations (export/import, large list pulls) should use pagination and stagger requests rather than burst.


You're set

You've got auth, base URL, your first read and write, and the conventions you'll see everywhere. From here: