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
- An Etlworks account (cloud, hybrid, or on-premise). Sign up free if you don't have one.
- A user with permission to generate API keys (admin, or a role with the
API accesscapability). - Either
curl, Python 3.7+, or any HTTP client you prefer.
Step 1: Authentication
Two authentication modes are supported. Pick whichever fits your use case:
Option A — API key (recommended for service accounts)
- Sign in to your Etlworks instance.
- Open Settings → Users, select the user, click Edit.
- Generate an API key under API Key. Optionally set an expiration.
- Copy the key now — you won't see it again after closing the dialog.
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:
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.
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
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.
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):
[
{
"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
}
]
Check the Authorization header is Bearer <key> with a single space and no quotes. The key string itself has no Bearer prefix.
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.
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:
{
"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:
- Request body:
application/jsonon all POST/PUT calls (Content-Type required). - Response body:
application/json, except endpoints explicitly marked otherwise. - Character encoding: UTF-8 throughout.
Pagination
Many list endpoints — particularly /v1/audit, /v1/executions/{flowId}/history, and bulk metric queries — accept offset/limit query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Max items returned in one call. Hard cap is 200 on most endpoints. |
offset | integer | 0 | Skip the first N items. |
Where supported, total counts come back via the X-Total-Count response header.
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:
| Filter | Example | Behavior |
|---|---|---|
q | ?q=stripe | Substring match against the resource's name field. |
tag | ?tag=production | Restrict to resources with this tag (repeatable). |
sort | ?sort=name or ?sort=-createdAt | Sort field, optionally with leading - for descending. |
since | ?since=2026-05-01T00:00:00Z | ISO-8601 timestamp; only resources updated after this point. |
Specific filters per endpoint are documented in the API reference.
Error handling
| HTTP | Meaning | Action |
|---|---|---|
200 | Success. Some endpoints return application-level errors in the body — check success: false. | Parse normally. |
201 | Created (returned by POST endpoints that create resources). | Use the id from the response. |
204 | No content (DELETE endpoints). | Empty body is expected. |
400 | Bad request — missing field, malformed JSON, or invalid values. | Inspect the error message and fix the request. |
401 | Unauthorized — missing/invalid/expired token. | Re-authenticate or rotate the API key. |
403 | Forbidden — the calling user lacks permission for this action. | Adjust the user's role or use a different API key. |
404 | Resource not found. | Check the ID exists and the user can see it. |
409 | Conflict — duplicate name, optimistic-lock failure, or concurrent edit. | Refetch the resource and retry with the latest version. |
415 | Unsupported media type — you sent the wrong Content-Type. | Most endpoints want application/json. |
429 | Too many requests. | Back off; honor the Retry-After header. |
500 | Internal server error. | Retry with backoff; if it persists, contact support. |
Error response shape (4xx and 5xx):
{
"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've got auth, base URL, your first read and write, and the conventions you'll see everywhere. From here: