Introduction
API for the revenexx Integration Studio — connects Revenue Cloud to external systems (ERP, PIM, CRM) via Temporal workflows.
The Integrations Service API provides endpoints for managing connections to external systems,
workflow definitions, and integration events.
All endpoints require a valid Zitadel JWT token. The active tenant is taken
from the `X-Tenant-Id` request header (a `tenant_id` JWT claim is accepted as
a legacy fallback for development tokens). Tenant isolation is enforced on
every request.
**Base URL:** `/v1`
**Versioning:** URL path prefix per [ADR-0036](https://atlas.revenexx.dev/adr/adr-0036-api-versioning-strategy).
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer {ZITADEL_JWT}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Authenticate via a Zitadel-issued OIDC JWT. Tenant context is taken from the X-Tenant-Id request header; the tenant_id JWT claim is accepted as a legacy fallback for development tokens.
Audit Log
Read access to the append-only audit trail (credential / secret / workflow /
trigger create-edit-delete, plus workflow run start/finished/failed). Two
planes: the public feed is auto-scoped to the caller's org + tenant; the
admin feed (registered under v1/admin) filters all three identity fields
optionally. Results are newest-first and cursor-paginated.
List audit entries (public)
requires authentication
Returns the audit entries for the caller's org + tenant, newest first.
Optionally narrowed by sub (actor), resource_type and action.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/audit-logs?sub=289347298347&resource_type=workflow&action=create&limit=50&cursor=eyJpZCI6NDJ9" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/audit-logs"
);
const params = {
"sub": "289347298347",
"resource_type": "workflow",
"action": "create",
"limit": "50",
"cursor": "eyJpZCI6NDJ9",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 42,
"org_id": "acme-org",
"tenant_id": "acme-production",
"actor_sub": "289347298347",
"via_system": false,
"channel": "api",
"action": "create",
"resource_type": "workflow",
"resource_id": "1",
"metadata": {
"name": "Sync orders"
},
"created_at": "2026-06-18T10:00:00Z"
}
],
"meta": {
"per_page": 50,
"next_cursor": null,
"prev_cursor": null
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
actor_sub
string
The acting user subject (sub); null for system-initiated entries (via_system = true).
metadata
object
Action-specific detail recorded with the entry; null when none was captured.
Auth
Endpoints for verifying authentication and tenant context.
Get authenticated user info
requires authentication
Returns full user profile, resolved tenant context, and raw token claims. Useful for debugging authentication and understanding which data Zitadel provides.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/me" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/me"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"user": {
"id": 1,
"zitadel_id": "289347298347",
"email": "user@acme.com",
"name": "Jane Doe"
},
"context": {
"tenant_id": "acme-production",
"active_plane": "public",
"roles": [
"admin",
"user"
]
},
"claims": {
"iss": "https://id.revenexx.com",
"sub": "289347298347",
"aud": [
"project-id"
]
}
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Credential Types
Read-only catalogue of the credential types registered via node packages.
Drives the credential form (fields + validation) and the credentials-ref
type picker in the editor. Globally registered, not tenant-scoped.
List credential types
requires authentication
Returns the latest registered version of each credential type.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/credential-types" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/credential-types"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"slug": "revenexx:smtp",
"version": "1.0.0",
"name": "SMTP",
"description": "Send mail over SMTP",
"icon": null,
"images": [
{
"src": "https://api.example.com/v1/node-packages/images/2b1c0000-0000-4000-8000-000000000000?expires=1750000000&signature=abcdef",
"alt": {
"en": "SMTP logo"
},
"title": {
"en": "SMTP"
},
"category": "logo"
}
],
"auth_kind": "secret",
"fields": []
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Show a credential type
requires authentication
Returns the latest registered version of the credential type identified by its namespaced slug.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/credential-types/revenexx:smtp" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/credential-types/revenexx:smtp"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": {
"slug": "revenexx:smtp",
"version": "1.0.0",
"name": "SMTP",
"description": "Send mail over SMTP",
"icon": null,
"images": [
{
"src": "https://api.example.com/v1/node-packages/images/2b1c0000-0000-4000-8000-000000000000?expires=1750000000&signature=abcdef",
"alt": {
"en": "SMTP logo"
},
"title": {
"en": "SMTP"
},
"category": "logo"
}
],
"auth_kind": "secret",
"fields": []
}
}
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Credentials
Tenant-scoped credential instances of a registered {@see CredentialType}.
The encrypted config / durable_creds blobs are never returned. Runtime
resolution happens in the broker, not here — this controller only stores and
proxies the on-demand connection test.
OAuth redirect callback (public)
The provider redirects the user's browser here with code + state.
Unauthenticated by necessity — the single-use state (looked up in the
cache) is the CSRF protection and carries the tenant + PKCE verifier.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/credentials/oauth/callback" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/credentials/oauth/callback"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Connected):
{
"status": "connected",
"credential_id": 1
}
Example response (400, Missing or invalid state):
{
"message": "Invalid or expired OAuth state."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List credentials
requires authentication
Returns the current tenant's credential instances, optionally filtered to
a single credential type via ?type={slug}. Secrets are never exposed.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/credentials?type=revenexx%3Asmtp" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/credentials"
);
const params = {
"type": "revenexx:smtp",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 1,
"tenant_id": "acme-production",
"credential_type_slug": "revenexx:smtp",
"name": "Production SMTP",
"status": "active",
"public_config": {},
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T10:00:00Z"
}
]
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a credential
requires authentication
Validates the submitted config against the credential type's declared
fields, then stores it encrypted. Returns 201 with the masked instance.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/credentials" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"credential_type_slug\": \"revenexx:smtp\",
\"name\": \"Mailpit\",
\"config\": {
\"host\": \"smtp.example.com\",
\"port\": 587,
\"username\": \"mailer\",
\"password\": \"sekret\"
}
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/credentials"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"credential_type_slug": "revenexx:smtp",
"name": "Mailpit",
"config": {
"host": "smtp.example.com",
"port": 587,
"username": "mailer",
"password": "sekret"
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, Created):
{
"id": 1,
"tenant_id": "acme-production",
"credential_type_slug": "revenexx:smtp",
"name": "Production SMTP",
"status": "active",
"public_config": {},
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T10:00:00Z"
}
Example response (422, Validation failed):
{
"message": "The given data was invalid.",
"errors": {
"config": [
"..."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Test an unsaved credential
requires authentication
Runs the broker connection test against an inline type + config, before the instance is persisted. Non-blocking.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/credentials/test" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"credential_type_slug\": \"revenexx:smtp\",
\"config\": {
\"host\": \"smtp.example.com\",
\"port\": 587,
\"username\": \"mailer\",
\"password\": \"sekret\"
}
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/credentials/test"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"credential_type_slug": "revenexx:smtp",
"config": {
"host": "smtp.example.com",
"port": 587,
"username": "mailer",
"password": "sekret"
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Tested):
{
"ok": true,
"message": null
}
Example response (200, Failed):
{
"ok": false,
"message": "Invalid auth key"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
ok
boolean
Whether the values authenticate.
message
string|null
Why not, where the credential type said why — bounded to 500 characters, the same bound the stored reason on a saved credential is held to. Always present; null when there is nothing to say.
Show a credential
requires authentication
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/credentials/9c1b4f2a-7d3e-4a6b-8c5d-1e2f3a4b5c6d" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/credentials/9c1b4f2a-7d3e-4a6b-8c5d-1e2f3a4b5c6d"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"id": 1,
"tenant_id": "acme-production",
"credential_type_slug": "revenexx:smtp",
"name": "Production SMTP",
"status": "active",
"public_config": {},
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T10:00:00Z"
}
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a credential
requires authentication
Updates the name and/or config. When config is supplied it is
re-validated against the credential type's declared fields.
Example request:
curl --request PATCH \
"https://integrations.revenexx.com/api/v1/credentials/9c1b4f2a-7d3e-4a6b-8c5d-1e2f3a4b5c6d" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"name\": \"Mailpit (staging)\",
\"config\": {
\"host\": \"smtp.example.com\",
\"port\": 587,
\"username\": \"mailer\",
\"password\": \"sekret\"
}
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/credentials/9c1b4f2a-7d3e-4a6b-8c5d-1e2f3a4b5c6d"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"name": "Mailpit (staging)",
"config": {
"host": "smtp.example.com",
"port": 587,
"username": "mailer",
"password": "sekret"
}
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Updated):
{
"id": 1,
"tenant_id": "acme-production",
"credential_type_slug": "revenexx:smtp",
"name": "Production SMTP",
"status": "active",
"public_config": {},
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T11:00:00Z"
}
Example response (404, Not found):
{
"message": "Not found."
}
Example response (422, Validation failed):
{
"message": "The given data was invalid.",
"errors": {
"config": [
"..."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a credential
requires authentication
Example request:
curl --request DELETE \
"https://integrations.revenexx.com/api/v1/credentials/9c1b4f2a-7d3e-4a6b-8c5d-1e2f3a4b5c6d" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/credentials/9c1b4f2a-7d3e-4a6b-8c5d-1e2f3a4b5c6d"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, Deleted):
Empty response
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Test a saved credential
requires authentication
Runs the credential type's connection test in the broker against the
stored config and records the outcome on the instance (last_test_at,
last_test_ok, last_test_message), so the answer to "do these
credentials actually work?" survives a reload — status cannot answer
that, it only tracks the OAuth refresh lifecycle. The stored config is
never modified.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/credentials/9c1b4f2a-7d3e-4a6b-8c5d-1e2f3a4b5c6d/test" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/credentials/9c1b4f2a-7d3e-4a6b-8c5d-1e2f3a4b5c6d/test"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200, Tested):
{
"ok": true,
"message": null,
"last_test_at": "2026-07-16T21:00:00+00:00",
"last_test_ok": true
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Begin 3-legged OAuth setup
requires authentication
Returns the provider authorize URL for the credential. The caller (the cockpit) opens it; the provider redirects back to the public callback.
Node Packages
Stream a node-package image.
requires authentication
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/node-packages/images/architecto?expires=16&signature=architecto" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/node-packages/images/architecto"
);
const params = {
"expires": "16",
"signature": "architecto",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Valid signature):
"<binary image stream>"
Example response (403, Missing, tampered, or expired signature):
{
"message": "Invalid signature."
}
Example response (404, Unknown image, or stored file missing):
{
"message": "Not Found"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Nodes
Globally registered node definitions, addressed publicly by
<slug>/<version>. Read endpoints additionally accept the alias
latest in place of an explicit version. The DELETE endpoint refuses
the alias and requires an exact version, so the caller always knows which
row they're touching.
Nodes are created and updated exclusively through the node-package upload
(POST /v1/admin/orgs/{org_id}/node-packages), which extracts the manifests
from the published tarball — there is no standalone create/update endpoint,
since a node always ships with its runtime code.
Authentication is required for every endpoint, but the resource itself is not tenant-scoped — per-tenant entitlement (which tenant may use which node) is the responsibility of a future app-registration flow.
List nodes
requires authentication
Returns the registered nodes ordered by slug ascending, then
semver descending. Optionally filters to a single namespace via the
namespace query parameter.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/nodes?namespace=revenexx" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/nodes"
);
const params = {
"namespace": "revenexx",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"name": "HTTP request",
"namespace": "revenexx",
"slug": "revenexx:http-request",
"version": "1.0.0",
"package": {
"name": "@revenexx/integrations-nodes-core",
"version": "0.2.0",
"label": "Core"
},
"manifest_version": "v0-draft",
"manifest": {},
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T10:00:00Z"
}
]
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List versions for a node
requires authentication
Returns the registered versions for a given slug, semver-descending.
The slug must match at least one stored row, otherwise a 404 is
returned — there's no representation for "an empty list of
versions". The response intentionally only contains version
strings; callers that need the full row should follow up with
GET /nodes/{slug}/{version}.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/nodes/revenexx:http-request/versions" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/nodes/revenexx:http-request/versions"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
"2.5.0",
"1.10.0",
"1.0.0"
]
}
Example response (404, Unknown slug):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Show a node
requires authentication
Returns a single node by (slug, version). The version may be the
literal string latest, in which case the highest semver registered
for the slug is returned.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/nodes/revenexx:http-request/1.0.0" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/nodes/revenexx:http-request/1.0.0"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"name": "HTTP request",
"namespace": "revenexx",
"slug": "revenexx:http-request",
"version": "1.0.0",
"package": {
"name": "@revenexx/integrations-nodes-core",
"version": "0.2.0",
"label": "Core"
},
"manifest_version": "v0-draft",
"manifest": {},
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T10:00:00Z"
}
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Resolve dynamic node config
requires authentication
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/nodes/revenexx:api/1.0.0/config:resolve" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"target\": \"*\",
\"config\": {
\"customerId\": \"42\"
},
\"locale\": \"de\",
\"search\": \"acme\"
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/nodes/revenexx:api/1.0.0/config:resolve"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"target": "*",
"config": {
"customerId": "42"
},
"locale": "de",
"search": "acme"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Resolved):
{
"fields": [
{
"key": "customerId",
"type": "string"
}
],
"outputs": [
{
"name": "success"
}
]
}
Example response (404, Unknown node):
{
"message": "Not found."
}
Example response (409, Node version has no built code):
{
"message": "This node version has no built code, so it cannot be executed. Its code is built when its package is registered."
}
Example response (502, Node runtime unavailable or failed):
{
"message": "Node runtime resolve failed: ..."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Test-execute a node
requires authentication
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/nodes/revenexx:api/1.0.0/execute:test" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"config\": {
\"customerId\": \"42\"
},
\"inputs\": {
\"payload\": {
\"email\": \"test@acme.com\"
}
},
\"timeout_ms\": 30000,
\"workflow_id\": 12
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/nodes/revenexx:api/1.0.0/execute:test"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"config": {
"customerId": "42"
},
"inputs": {
"payload": {
"email": "test@acme.com"
}
},
"timeout_ms": 30000,
"workflow_id": 12
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Executed):
{
"outputs": {
"customer": {
"id": "42"
}
},
"branch": "success",
"logs": [
{
"level": "info",
"message": "fetched customer 42"
}
]
}
Example response (404, Unknown node):
{
"message": "Not found."
}
Example response (409, Node version has no built code):
{
"message": "This node version has no built code, so it cannot be executed. Its code is built when its package is registered."
}
Example response (422, Node threw):
{
"error": {
"message": "rate limited",
"code": "RATE_LIMITED"
},
"logs": []
}
Example response (502, Node runtime unavailable):
{
"message": "Node runtime execute failed: ..."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a node
requires authentication
Removes the node version identified by (slug, version). The
latest alias is rejected to prevent ambiguous deletes.
Example request:
curl --request DELETE \
"https://integrations.revenexx.com/api/v1/nodes/revenexx:http-request/1.0.0" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/nodes/revenexx:http-request/1.0.0"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, Deleted):
Empty response
Example response (400, Latest alias rejected):
{
"message": "The `latest` alias is not allowed for mutating requests."
}
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Run Dead Letters
A trigger firing that could not start a run — and left no failed run row to retry — is captured here so it can be replayed once the cause is resolved (typically the workflow bundle finishing its build) or explicitly discarded.
List dead-lettered firings for a workflow
requires authentication
Newest first. Only firings for the addressed workflow, scoped to the authenticated tenant.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/dead-letters?page=1&per_page=25" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/dead-letters"
);
const params = {
"page": "1",
"per_page": "25",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 0,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List the tenant's missed firings
requires authentication
Every trigger firing across the tenant that never became a run, newest first. Optionally narrowed by handling state, by workflow and by a time window.
The sibling of GET /v1/runs: a firing that produced a run belongs
there, one that produced none belongs here. Each row names the workflow
it belongs to, which the per-workflow list can leave to the request path
and this one cannot.
No total is reported — GET /v1/dead-letters/summary answers "how many",
including the count of those still waiting.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/dead-letters?status[]=pending&workflow_id=1&since=2026-08-01T00%3A00%3A00Z&until=2026-08-21T00%3A00%3A00Z&page=1&per_page=25" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/dead-letters"
);
const params = {
"status[0]": "pending",
"workflow_id": "1",
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-21T00:00:00Z",
"page": "1",
"per_page": "25",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (0, Success):
{
"data": [
{
"id": 7,
"workflow_id": 1,
"workflow_name": "Sync orders",
"trigger_id": 3,
"source": "schedule",
"trigger_handle": "9f1c8e2a-0b4d-4f7e-9a3c-1d2e3f4a5b6c",
"reason": "Workflow bundle not ready (build_",
"status": "pending",
"created_at": "2026-08-21T02:00:00Z",
"updated_at": "2026-08-21T02:00:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"has_more": false
}
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Example response (403, No tenant context):
{
"message": "Tenant context is required."
}
Example response (500):
Show headers
cache-control: no-cache, private
content-type: application/json
x-request-id: fc60e865-6f17-444f-b9c3-6dc8b49bae51
access-control-allow-origin: *
{
"message": "Server Error"
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
workflow_name
string
The name of the workflow whose firing was missed.
reason
string
Why the firing never became a run, in the platform's words. Empty when nothing was recorded.
meta
object
has_more
boolean
Whether a further page exists. This list reports no total — see GET /v1/dead-letters/summary.
Count the tenant's missed firings
requires authentication
How many firings never became runs, broken down by handling state, under
exactly the narrowing GET /v1/dead-letters accepts.
by_status.pending is the count the studio shows on its Missed runs tab
and on the dashboard: the firings still waiting to be sent through or put
aside. Every state appears, zeros included, so "none waiting" cannot read
as "not loaded yet".
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/dead-letters/summary?status[]=pending&workflow_id=1&since=2026-08-01T00%3A00%3A00Z&until=2026-08-21T00%3A00%3A00Z&page=1&per_page=25" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/dead-letters/summary"
);
const params = {
"status[0]": "pending",
"workflow_id": "1",
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-21T00:00:00Z",
"page": "1",
"per_page": "25",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": {
"total": 7,
"by_status": {
"pending": 4,
"replayed": 2,
"discarded": 1
}
}
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Example response (403, No tenant context):
{
"message": "Tenant context is required."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
total
integer
How many missed firings match the narrowing.
by_status
object
A count per handling state. Every state appears, including the ones at zero.
Replay a dead-lettered firing
requires authentication
Starts a fresh run from the captured trigger payload + handle. Requires
the workflow to be runnable again; marks the dead letter replayed.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/dead-letters/1/replay" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/dead-letters/1/replay"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (0, Workflow not runnable):
{
"message": "Workflow bundle is not ready (build_"
}
Example response (202, Accepted):
{
"id": 5,
"workflow_id": 1,
"status": "running"
}
Example response (404, Not found):
{
"message": "Not found."
}
Example response (409, Already handled):
{
"message": "Dead letter has already been replayed or discarded."
}
Example response (422, Trigger no longer resolvable):
{
"message": "Workflow has no active manual trigger; pass trigger_handle to start a specific trigger."
}
Example response (503, Worker unavailable):
{
"message": "Workflow worker is unavailable."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Discard a dead-lettered firing
requires authentication
Marks the dead letter discarded without starting a run.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/dead-letters/1/discard" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/dead-letters/1/discard"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200, Discarded):
{
"id": 1,
"status": "discarded"
}
Example response (404, Not found):
{
"message": "Not found."
}
Example response (409, Already handled):
{
"message": "Dead letter has already been replayed or discarded."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Runs
Every run of every workflow the tenant owns, in one list the platform orders
and pages itself. The studio's Runs page reads this; a workflow's own Runs tab
reads the per-workflow subresource
(GET /workflows/{workflowId}/runs), whose contract is unchanged.
Its own controller rather than more methods on {@see WorkflowRunController}: that class orchestrates starting, cancelling, resuming and inspecting a run and carries the services to do it. This is a read, and it needs none of them.
List the tenant's runs
requires authentication
Newest first, across every workflow the tenant owns, and optionally in another order — by when a run started, or by how long it took. Optionally narrowed by outcome, by the kind of trigger that started the run, by workflow, and by a time window; the filters narrow together, and the order applies to what they left.
The rows are leaner than the per-workflow list's: a run's result and
captured trigger payload are not included, because they can run to
megabytes each. Read them from GET /workflows/{workflowId}/runs/{runId}.
No total is reported. GET /v1/runs/summary answers "how many", and
answers it under the same narrowing — a page cannot, and pretending
otherwise is what made clients read every page to count.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/runs?status[]=failed&workflow_id=1&since=2026-08-01T00%3A00%3A00Z&until=2026-08-21T00%3A00%3A00Z&page=1&per_page=25&trigger_type=schedule&search=orders&sort=duration&direction=desc" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/runs"
);
const params = {
"status[0]": "failed",
"workflow_id": "1",
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-21T00:00:00Z",
"page": "1",
"per_page": "25",
"trigger_type": "schedule",
"search": "orders",
"sort": "duration",
"direction": "desc",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 42,
"workflow_id": 1,
"workflow_name": "Sync orders",
"status": "failed",
"error": "Node revenexx:http-request failed: 500",
"trigger_handle": "9f1c8e2a-0b4d-4f7e-9a3c-1d2e3f4a5b6c",
"trigger_type": "schedule",
"temporal_workflow_id": "acme-production:1:42",
"temporal_run_id": "018f...",
"started_at": "2026-08-21T02:00:00Z",
"completed_at": "2026-08-21T02:00:07Z",
"created_at": "2026-08-21T02:00:00Z",
"updated_at": "2026-08-21T02:00:07Z"
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"has_more": false
}
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Example response (403, No tenant context):
{
"message": "Tenant context is required."
}
Example response (422, Unknown status):
{
"message": "The given data was invalid.",
"errors": {
"status.0": [
"The selected status.0 is invalid."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
workflow_name
string
The name of the workflow the run belongs to. Always present — a run's workflow cannot be missing.
trigger_type
string
What kind of trigger started the run (manual, schedule, webhook, event). Null when that trigger has since been deleted, or when the run predates the platform recording it.
error
string
Why the run failed, in the platform's words; null for a run that did not fail.
meta
object
has_more
boolean
Whether a further page exists. This list reports no total — see GET /v1/runs/summary.
Count the tenant's runs
requires authentication
How many runs the tenant has, broken down by outcome, under exactly the
narrowing GET /v1/runs accepts — so a figure and the list beside it can
never disagree.
Separate from the list on purpose. Coupling "how many" to "which page" is what made clients read every page to count, and a tenant-wide COUNT on a list that refreshes itself every few seconds is the most expensive query this surface could ask for. Asked on its own, it can be asked less often.
Every status is present, zeros included. An absent key would leave "none of those" and "not loaded" reading alike, which is the confusion these figures exist to remove.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/runs/summary?status[]=failed&workflow_id=1&since=2026-08-01T00%3A00%3A00Z&until=2026-08-21T00%3A00%3A00Z&page=1&per_page=25&trigger_type=schedule&search=orders&sort=duration&direction=desc" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/runs/summary"
);
const params = {
"status[0]": "failed",
"workflow_id": "1",
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-21T00:00:00Z",
"page": "1",
"per_page": "25",
"trigger_type": "schedule",
"search": "orders",
"sort": "duration",
"direction": "desc",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": {
"total": 124,
"in_flight": 3,
"by_status": {
"pending": 1,
"running": 2,
"completed": 100,
"failed": 19,
"cancelled": 1,
"terminated": 1
}
}
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Example response (403, No tenant context):
{
"message": "Tenant context is required."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
total
integer
How many runs match the narrowing.
in_flight
integer
How many are still expected to finish (pending + running), derived here so every surface counts it the same way.
by_status
object
A count per run status. Every status appears, including the ones at zero.
The latest run of each workflow
requires authentication
One row per workflow that has ever run — its most recent run — newest workflow first. Workflows that have never run are absent rather than present-and-empty.
Its own endpoint because it is a different question from the feed, not a
page of it. specs/workflows.md AC-4 promises a row says how its workflow
last ran, and that a workflow which never ran claims no outcome at all —
so reading it off the first page of the tenant's runs would make a
workflow whose last run happens to be older than a hundred others claim it
had never run, which is the mirror of the lie that criterion forbids.
Unpaged: there is at most one row per workflow, the same bound
GET /v1/workflows already lives with.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/runs/latest-per-workflow" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/runs/latest-per-workflow"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 42,
"workflow_id": 1,
"workflow_name": "Sync orders",
"status": "failed",
"error": "Node revenexx:http-request failed: 500",
"trigger_handle": "9f1c8e2a-0b4d-4f7e-9a3c-1d2e3f4a5b6c",
"trigger_type": "schedule",
"temporal_workflow_id": "acme-production:1:42",
"temporal_run_id": "018f...",
"started_at": "2026-08-21T02:00:00Z",
"completed_at": "2026-08-21T02:00:07Z",
"created_at": "2026-08-21T02:00:00Z",
"updated_at": "2026-08-21T02:00:07Z"
}
]
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Example response (403, No tenant context):
{
"message": "Tenant context is required."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Schedules
Every scheduled trigger the tenant owns, on one list, each saying when it fires next and when it last really ran.
The per-workflow trigger list cannot answer this surface's questions: how many schedules are live across the tenant, and which timezones its schedules are actually in, are counts over the tenant rather than over one workflow — so a client assembling them from a fan-out could only ever report what it had managed to load.
List the tenant's schedules
requires authentication
Every schedule trigger across every workflow, and nothing else — a
webhook or a manual trigger is not a schedule and is not listed.
Served whole rather than paged, like the tenant's workflows and
credentials: there is one row per schedule, the page that shows them
narrows and sorts them in the browser, and a count of what is live is only
true if it covers all of them. meta therefore describes the whole
tenant, not a page.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/schedules?active=1&timezone=Europe%2FBerlin&next_fire_count=1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/schedules"
);
const params = {
"active": "1",
"timezone": "Europe/Berlin",
"next_fire_count": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 13,
"workflow_id": 1,
"workflow_name": "Sync orders",
"handle": "9f1c8e2a-0b4d-4f7e-9a3c-1d2e3f4a5b6c",
"name": null,
"active": true,
"workflow_active": true,
"schedule": {
"cron": "0 3 * * *",
"timezone": "Europe/Berlin",
"timezone_source": "schedule",
"holds_firings": true,
"not_firing_reason": null,
"next_fire_at": "2026-08-22T01:00:00Z",
"next_fire_times": [
{
"at": "2026-08-22T01:00:00Z",
"local": "2026-08-22T03:00:00+02:00"
}
],
"last_dispatch_at": "2026-08-21T01:00:00Z",
"last_run": {
"id": 4711,
"status": "completed",
"error": null,
"started_at": "2026-08-21T01:00:01Z",
"completed_at": "2026-08-21T01:00:44Z"
}
},
"created_at": "2026-06-15T10:00:00Z",
"updated_at": "2026-06-15T10:00:00Z"
}
],
"meta": {
"total": 1,
"live_count": 1,
"timezones": [
"Europe/Berlin"
]
}
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Example response (403, No tenant context):
{
"message": "Tenant context is required."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
workflow_name
string
The workflow the schedule belongs to.
active
boolean
Whether the schedule itself is live. Distinct from workflow_active: a live schedule on a deactivated workflow does not fire, and schedule.not_firing_reason says which of the two is the cause.
workflow_active
boolean
Whether the workflow the schedule belongs to is active.
schedule
object
The firings the platform holds and the last run it performed — the same block the per-workflow trigger list returns.
meta
object
live_count
integer
How many of the tenant's schedules are live, counted over the tenant rather than the response.
timezones
string[]
The effective timezones the tenant's schedules actually run in, sorted. A schedule carrying none contributes the platform default.
Schemas
Public, read-only access to the JSON-Schema definitions used to validate
versioned payloads (currently the workflow blob and the node manifest).
Schemas are global — they describe the shape of payloads regardless of
tenant — so these endpoints are intentionally unauthenticated.
List schema versions
Returns the identifiers of every registered schema version for the given domain, in registration order (which is also the order in which they are accepted by the validation layer).
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/schemas/workflow" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://integrations.revenexx.com/api/v1/schemas/workflow"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"domain": "workflow",
"versions": [
"v0-draft"
]
}
Example response (404, Unknown domain):
{
"message": "Unknown schema domain."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Show a schema version
Returns the serialized JSON-Schema definition for one specific
(domain, version) pair. The payload uses the standard JSON Schema
vocabulary (type, properties, required, …) and is suitable for
client-side validation or form generation.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/schemas/workflow/v0-draft" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://integrations.revenexx.com/api/v1/schemas/workflow/v0-draft"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"domain": "workflow",
"version": "v0-draft",
"schema": {
"title": "Workflow Definition (v0-draft)",
"type": "object"
}
}
Example response (404, Unknown version):
{
"message": "Unknown schema version."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
State Store
The operator's window into the tenant state store (PO-374): which namespaces exist, what they hold, and the ability to correct a single entry when a sync went wrong.
This exists because the support question is never abstract — it is "why was article X created twice?", and the answer is one lookup away if somebody can see the correlation and the run that wrote it. Without this view the store would be a black box that only the workflow author can reason about.
Deliberately read-and-correct, not read-and-write: there is no bulk import. That is the door through which "let's just load our master data in here" walks in, and the store is not a database.
List state namespaces
requires authentication
Every namespace in the current tenant, with its entry count.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/state/namespaces" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/state/namespaces"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 1,
"name": "article",
"role": "mapping",
"visibility": "shared",
"owner_workflow_id": null,
"entries": 2,
"ttl_seconds": null,
"updated_at": "2026-08-26T10:00:00Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List the entries of a namespace
requires authentication
Paginated, narrowable by key with ?q=.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/state/namespaces/1/entries?q=pim%3A12345" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"q\": \"b\",
\"per_page\": 22
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/state/namespaces/1/entries"
);
const params = {
"q": "pim:12345",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"q": "b",
"per_page": 22
};
fetch(url, {
method: "GET",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 7,
"key": "pim:12345",
"value": "erp:A-8891",
"last_run_id": 42,
"updated_at": "2026-08-26T10:00:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 1,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete one entry
requires authentication
The correction path for a wrong correlation or a stuck cursor. Audited, because an operator editing what a run wrote is exactly the kind of change somebody will want to trace back later.
Example request:
curl --request DELETE \
"https://integrations.revenexx.com/api/v1/state/namespaces/1/entries/7" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/state/namespaces/1/entries/7"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, Deleted):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a namespace
requires authentication
Removes the namespace and every entry in it. The explicit act a shared namespace requires: it outlives the workflows that declared it, so nothing else ever removes one.
Example request:
curl --request DELETE \
"https://integrations.revenexx.com/api/v1/state/namespaces/1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/state/namespaces/1"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, Deleted):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Templates
Catalogue of the workflow templates registered via node packages, plus the "install" actions a UI uses to turn one into a real workflow. Listing/showing is global; instantiation is scoped to the current tenant.
List templates
requires authentication
Returns the latest registered version of each template, without the
(heavy) embedded workflow definition blob — that ships only on show.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/templates" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/templates"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"slug": "revenexx:bc-sales-order-digest",
"version": "1.0.0",
"category": "sales",
"level": "beginner",
"name": "Daily Sales-Order Digest",
"shortDescription": "Email a daily summary of Business Central sales orders.",
"description": null,
"icon": "mdi:email-newsletter",
"industries": [
"any"
],
"vendors": [
"microsoft",
"business-central"
],
"images": [
{
"src": "https://api.example.com/v1/node-packages/images/2b1c0000-0000-4000-8000-000000000000?expires=1750000000&signature=abcdef",
"alt": {
"en": "Business Central banner"
},
"title": {
"en": "Business Central"
},
"category": "banner"
}
],
"triggerTypes": [
"schedule"
]
}
]
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Show a template
requires authentication
Returns the latest registered version of the template identified by its
namespaced slug, including the embedded workflow definition used to
instantiate a new workflow.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/templates/revenexx:slack-to-crm" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/templates/revenexx:slack-to-crm"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": {
"slug": "revenexx:bc-sales-order-digest",
"version": "1.0.0",
"category": "sales",
"level": "beginner",
"name": "Daily Sales-Order Digest",
"shortDescription": "Email a daily summary of Business Central sales orders.",
"description": null,
"icon": "mdi:email-newsletter",
"industries": [
"any"
],
"vendors": [
"microsoft",
"business-central"
],
"images": [
{
"src": "https://api.example.com/v1/node-packages/images/2b1c0000-0000-4000-8000-000000000000?expires=1750000000&signature=abcdef",
"alt": {
"en": "Business Central banner"
},
"title": {
"en": "Business Central"
},
"category": "banner"
}
],
"triggerTypes": [
"schedule"
],
"blob_version": "v0-draft",
"definition": {
"nodeManifestVersion": "v0-draft",
"name": "Daily Sales-Order Digest",
"nodes": [],
"edges": []
},
"triggers": [
{
"handle": "a1c0ffee-0001-4001-8001-000000000001",
"type": "schedule",
"config": {
"cron": "0 7 * * *"
}
}
]
}
}
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Template installation requirements
requires authentication
Lists the credential types and secret keys the template's workflow references, cross-referenced against the current tenant (how many credential instances already exist, whether each secret key is stored) so an install UI can have the user provision what is missing first.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/templates/revenexx:slack-to-crm/requirements" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/templates/revenexx:slack-to-crm/requirements"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": {
"credentialTypes": [
{
"slug": "revenexx:business-central",
"registered": true,
"existingInstances": 1,
"usedBy": [
{
"nodeId": "list_orders",
"configKey": "credentials"
}
],
"version": "1.0.0",
"name": "Business Central",
"authKind": "oauth2-client-credentials",
"fields": []
}
],
"secrets": []
}
}
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Instantiate a template
requires authentication
Creates a new workflow (and its triggers) for the current tenant from the template's blueprint. Trigger handles are regenerated so repeated installs never collide; the workflow and all triggers start inactive. Returns the created workflow.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/templates/revenexx:slack-to-crm/instantiate" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"name\": \"My Slack flow\",
\"credential_bindings\": [
[
\"6ff8f7f6-1eb3-3525-be4a-3932c805afed\"
]
]
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/templates/revenexx:slack-to-crm/instantiate"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"name": "My Slack flow",
"credential_bindings": [
[
"6ff8f7f6-1eb3-3525-be4a-3932c805afed"
]
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, Created):
{
"id": 1,
"name": "Daily Sales-Order Digest",
"description": null,
"blob_definition_version": "v0-draft",
"blob": {},
"active": false,
"revision": 1,
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T10:00:00Z"
}
Example response (404, Not found):
{
"message": "Not found."
}
Example response (422, Validation failed):
{
"message": "The given data was invalid.",
"errors": {
"credential_bindings": [
"The credential_bindings field must be an array."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Tenant Secrets
Per-tenant encrypted key/value store. Values are AES-256-GCM encrypted at rest using a dedicated SECRETS_ENCRYPTION_KEY (separate from APP_KEY) and are never returned by the API.
List secrets
requires authentication
Returns the alphabetically sorted secrets of the current tenant in data —
key plus timestamps, never the value. updated_at is when the value was
last written, which is what "when was this last rotated" asks for;
overwriting a secret with the same plaintext still counts as a write.
Replaces the earlier {"keys": [...]} envelope. Every consumer of this
endpoint is first-party and reads data.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/secrets" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/secrets"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"key": "SLACK_WEBHOOK_URL",
"created_at": "2026-05-04T08:50:00.000000Z",
"updated_at": "2026-05-04T08:50:00.000000Z"
},
{
"key": "STRIPE_API_KEY",
"created_at": "2026-05-05T08:50:00.000000Z",
"updated_at": "2026-07-01T11:02:00.000000Z"
}
]
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create or update a secret
requires authentication
Upserts a secret value for the given key within the current tenant. Returns 201 on creation, 200 on update. The value is never echoed back.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/secrets" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"key\": \"STRIPE_API_KEY\",
\"value\": \"sk_live_abc123\"
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/secrets"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"key": "STRIPE_API_KEY",
"value": "sk_live_abc123"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Updated):
{
"key": "STRIPE_API_KEY",
"created_at": "2026-05-04T08:50:00Z",
"updated_at": "2026-05-05T08:50:00Z"
}
Example response (201, Created):
{
"key": "STRIPE_API_KEY",
"created_at": "2026-05-05T08:50:00Z",
"updated_at": "2026-05-05T08:50:00Z"
}
Example response (422, Validation failed):
{
"message": "The key field format is invalid.",
"errors": {
"key": [
"..."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a secret value
requires authentication
Replaces the value of an existing secret. The key is immutable — create a new secret and delete the old one if you need to rename a key. Returns 404 if the key does not exist for the current tenant.
Example request:
curl --request PATCH \
"https://integrations.revenexx.com/api/v1/secrets/bc-client-secret" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"value\": \"sk_live_new_value\"
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/secrets/bc-client-secret"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"value": "sk_live_new_value"
};
fetch(url, {
method: "PATCH",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Updated):
{
"key": "bc-client-secret",
"created_at": "2026-05-04T08:50:00Z",
"updated_at": "2026-06-12T10:00:00Z"
}
Example response (404, Not found):
{
"message": "Not found."
}
Example response (422, Validation failed):
{
"message": "The value field is required.",
"errors": {
"value": [
"..."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a secret
requires authentication
Removes the secret identified by {key} for the current tenant. The
lookup is explicitly scoped to the current tenant, so a key belonging
to another tenant — or one that does not exist at all — yields a 404
without leaking existence either way.
Example request:
curl --request DELETE \
"https://integrations.revenexx.com/api/v1/secrets/STRIPE_API_KEY" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/secrets/STRIPE_API_KEY"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, Deleted):
Empty response
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Triggers
Manage triggers for a workflow. Triggers are the entry points that can start a workflow execution (manual, schedule, webhook, or event). They live as DB entities rather than inside the workflow blob, and are referenced in blob edges via their stable UUID handle.
List triggers for a workflow
requires authentication
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/triggers?next_fire_count=3" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/triggers"
);
const params = {
"next_fire_count": "3",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 12,
"workflow_id": 1,
"handle": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
"type": "webhook",
"name": "PSP webhook",
"config": {
"method": "POST",
"public": true,
"inbound": {
"endpoint_id": "inb_abc123",
"url": "https://webhooks.revenexx.com/in/inb_abc123"
}
},
"active": true,
"created_at": "2026-06-15T10:00:00Z",
"updated_at": "2026-06-15T10:00:00Z"
}
]
}
Example response (200, A schedule trigger):
{
"data": [
{
"id": 13,
"workflow_id": 1,
"handle": "9f1c8e2a-0b4d-4f7e-9a3c-1d2e3f4a5b6c",
"type": "schedule",
"name": null,
"config": {
"cron": "0 3 * * *",
"timezone": "Europe/Berlin"
},
"active": true,
"schedule": {
"cron": "0 3 * * *",
"timezone": "Europe/Berlin",
"timezone_source": "schedule",
"holds_firings": true,
"not_firing_reason": null,
"next_fire_at": "2026-08-22T01:00:00Z",
"next_fire_times": [
{
"at": "2026-08-22T01:00:00Z",
"local": "2026-08-22T03:00:00+02:00"
}
],
"last_dispatch_at": "2026-08-21T01:00:00Z",
"last_run": {
"id": 4711,
"status": "completed",
"error": null,
"started_at": "2026-08-21T01:00:01Z",
"completed_at": "2026-08-21T01:00:44Z"
}
},
"created_at": "2026-06-15T10:00:00Z",
"updated_at": "2026-06-15T10:00:00Z"
}
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
data
object
schedule
object
Present only on schedule triggers: the firings the platform actually holds, and the last run it really performed.
schedule triggers: the firings the platform actually holds, and the last run it really performed.holds_firings
boolean
Whether the platform will fire this schedule. False whenever the scheduler would never select it — see not_firing_reason.
not_firing_reason
string
Why there is no next firing: trigger_paused, workflow_inactive, workflow_not_ready, no_cron, invalid_cron, unknown_timezone, unsatisfiable. Null when it does fire.
next_fire_times
object[]
The upcoming firings, each as a UTC instant (at) and the same moment in the schedule's own zone (local) — whose offset says which side of a clock change it falls on. Empty when the schedule holds none.
last_dispatch_at
string
When the scheduler last DECIDED to fire this trigger. Stamped before the run exists, so it can differ from last_run — a firing that was dispatched and then lost.
last_run
object
The last run this schedule actually started; null when it has never run.
timezone_source
string
schedule when the zone is the schedule's own, platform_default when it carries none and runs in the platform's.
Create a trigger
requires authentication
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/workflows/1/triggers" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"handle\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
\"type\": \"webhook\",
\"name\": \"PSP webhook\",
\"active\": true,
\"config\": {
\"method\": \"POST\",
\"public\": true,
\"authProfile\": \"token\"
}
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/triggers"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"handle": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
"type": "webhook",
"name": "PSP webhook",
"active": true,
"config": {
"method": "POST",
"public": true,
"authProfile": "token"
}
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, Public webhook trigger):
{
"id": 12,
"workflow_id": 1,
"handle": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
"type": "webhook",
"name": "PSP webhook",
"config": {
"method": "POST",
"public": true,
"inbound": {
"endpoint_id": "inb_abc123",
"url": "https://webhooks.revenexx.com/in/inb_abc123"
}
},
"active": true,
"created_at": "2026-06-15T10:00:00Z",
"updated_at": "2026-06-15T10:00:00Z",
"inbound_secret": "whsec_ing_onetime"
}
Example response (422, Validation failed):
{
"message": "The config.method field is required.",
"errors": {
"config.method": [
"The config.method field is required."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
inbound_secret
string
Present ONLY right after a public webhook endpoint is provisioned — the ingress ingest token, returned once and never stored (ADR-0066).
config
object
inbound
object
Server-owned ingress registration (endpoint_id, url) for a public webhook trigger. Clients cannot set or modify it.
Update a trigger
requires authentication
Example request:
curl --request PUT \
"https://integrations.revenexx.com/api/v1/workflows/1/triggers/1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"type\": \"webhook\",
\"name\": \"PSP webhook\",
\"active\": true,
\"config\": {
\"method\": \"POST\",
\"public\": true,
\"authProfile\": \"token\"
}
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/triggers/1"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"type": "webhook",
"name": "PSP webhook",
"active": true,
"config": {
"method": "POST",
"public": true,
"authProfile": "token"
}
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Public webhook trigger):
{
"id": 12,
"workflow_id": 1,
"handle": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
"type": "webhook",
"name": "PSP webhook",
"config": {
"method": "POST",
"public": true,
"inbound": {
"endpoint_id": "inb_abc123",
"url": "https://webhooks.revenexx.com/in/inb_abc123"
}
},
"active": true,
"created_at": "2026-06-15T10:00:00Z",
"updated_at": "2026-06-15T10:05:00Z",
"inbound_secret": "whsec_ing_onetime"
}
Example response (422, Validation failed):
{
"message": "The config.method field is required.",
"errors": {
"config.method": [
"The config.method field is required."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
inbound_secret
string
Present ONLY right after a public webhook endpoint is provisioned — the ingress ingest token, returned once and never stored (ADR-0066).
config
object
inbound
object
Server-owned ingress registration (endpoint_id, url) for a public webhook trigger. Clients cannot set or modify it.
Delete a trigger
requires authentication
Example request:
curl --request DELETE \
"https://integrations.revenexx.com/api/v1/workflows/1/triggers/1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/triggers/1"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, Deleted):
Empty response
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Rotate the ingest secret of a public webhook trigger
requires authentication
Generates a fresh ingest secret on the trigger's ingress endpoint. The
previous secret stops working immediately. Only available for webhook
triggers that are exposed via the inbound ingress (config.public).
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/workflows/1/triggers/6ff8f7f6-1eb3-3525-be4a-3932c805afed/rotate-secret" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/triggers/6ff8f7f6-1eb3-3525-be4a-3932c805afed/rotate-secret"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200, Rotated):
{
"inbound_secret": "whsec_ing_onetime"
}
Example response (409, Not exposed):
{
"message": "Trigger is not exposed via the inbound ingress; set config.public first."
}
Example response (409, Ingress unavailable):
{
"message": "Secret rotation is not available: the inbound ingress is not configured."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
inbound_secret
string
The freshly generated ingress ingest token — returned once and never stored (ADR-0066).
Webhooks
Inbound endpoint that fires a workflow run for a webhook trigger. The
trigger is addressed by its UUID handle in the URL; authentication is the
standard JWT + X-Tenant-Id used by the rest of the API, so the trigger is
resolved within the caller's tenant. The HTTP request (body, query,
headers, method) is delivered to the workflow as the trigger payload,
reachable via ${{ trigger.payload.* }}.
Fire a webhook trigger
requires authentication
Resolves the webhook trigger by handle, enforces the configured HTTP
method, and starts a run. The mode follows the workflow's execution_mode
(async_only → 202; sync_only/caller_decides → wait and return the
result), and can be overridden per request with ?mode=sync|async when
the policy allows it — a conflicting mode is rejected with 422.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/webhooks/0190a9c0-1111-7000-8000-000000000000?mode=async" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/webhooks/0190a9c0-1111-7000-8000-000000000000"
);
const params = {
"mode": "async",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Completed (sync)):
{
"id": 1,
"workflow_id": 1,
"status": "completed",
"result": {}
}
Example response (202, Accepted (async)):
{
"id": 1,
"workflow_id": 1,
"status": "running"
}
Example response (404, No such webhook trigger):
{
"message": "No active webhook trigger with handle … exists."
}
Example response (405, Method not allowed):
{
"message": "This webhook expects POST."
}
Example response (422, Mode conflicts with policy):
{
"message": "Workflow does not allow sync execution (execution_mode=async_only)."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Workflow Credentials
Read-only introspection of which typed credentials a workflow's nodes
reference through their credentials-ref config fields. Useful for pre-run
checks, access auditing, and impact analysis when a credential instance is
rotated or deleted. The credential values are never touched here — only
the instance UUIDs and their credential type slugs are reported.
List credential ids used across the tenant's workflows
requires authentication
Returns the distinct credential instance UUIDs referenced by every
workflow of the current tenant. Optionally filtered to a single
credential type slug via ?type={slug} and/or to active workflows only
via ?active=true.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/used-credentials?type=revenexx%3Asmtp&active=1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/used-credentials"
);
const params = {
"type": "revenexx:smtp",
"active": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Used credentials):
{
"data": [
"9f3e2c1a-0000-4000-8000-000000000001",
"1ab2c3d4-0000-4000-8000-000000000002"
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List credential type slugs used across the tenant's workflows
requires authentication
Returns the distinct credential type slugs (e.g. revenexx:smtp)
referenced by every workflow of the current tenant. Optionally filtered
to active workflows only via ?active=true.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/used-credential-types?active=1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/used-credential-types"
);
const params = {
"active": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Used credential types):
{
"data": [
"revenexx:smtp",
"revenexx:deepl"
]
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List credential ids used by a workflow
requires authentication
Returns the distinct credential instance UUIDs the workflow's nodes
reference, optionally filtered to a single credential type slug via
?type={slug}.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/credentials?type=revenexx%3Asmtp" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/credentials"
);
const params = {
"type": "revenexx:smtp",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Used credentials):
{
"data": [
"9f3e2c1a-0000-4000-8000-000000000001",
"1ab2c3d4-0000-4000-8000-000000000002"
]
}
Example response (404, Workflow not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
List credential type slugs used by a workflow
requires authentication
Returns the distinct credential type slugs (e.g. revenexx:smtp) the
workflow's nodes reference through their credentials-ref config fields.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/credential-types" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/credential-types"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Used credential types):
{
"data": [
"revenexx:smtp",
"revenexx:deepl"
]
}
Example response (404, Workflow not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Workflow Revisions
Read the archived revision history of a workflow and restore an old
revision. Every blob change to a workflow snapshots the previous revision
into workflow_histories; these endpoints expose that history and let a
client re-apply an old revision as a brand-new one.
All endpoints are scoped to the tenant resolved by the resolve.tenant
middleware. Cross-tenant access returns 404 so the API never leaks the
existence of resources owned by other tenants.
List workflow revisions
requires authentication
Returns the archived revisions of a workflow, newest first, paginated.
List items carry metadata only — use the show endpoint to fetch a
single revision's full blob and triggers.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/revisions?page=1&per_page=25" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/revisions"
);
const params = {
"page": "1",
"per_page": "25",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 7,
"revision": 2,
"name": "Sync orders",
"description": null,
"active": true,
"blob_definition_version": "v0-draft",
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T11:00:00Z",
"archived_at": "2026-05-06T12:00:00Z"
}
],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 1,
"last_page": 1
}
}
Example response (404, Workflow not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Show a workflow revision
requires authentication
Returns a single archived revision in full, including its blob and
triggers snapshot.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/revisions/1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/revisions/1"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"id": 7,
"revision": 1,
"name": "Sync orders",
"description": null,
"active": true,
"blob_definition_version": "v0-draft",
"blob": {},
"triggers": [],
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T11:00:00Z",
"archived_at": "2026-05-06T12:00:00Z"
}
Example response (404, Revision or workflow not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Restore a workflow revision
requires authentication
Re-applies the given archived revision's definition (blob,
blob_definition_version, and triggers) to the workflow as a brand-new
revision. The current state is first snapshotted into the history, the
revision counter is bumped, and a fresh bundle build is dispatched. The
workflow's name, description and execution_mode are left unchanged.
active is preserved too, except that restoring a schema- or
manifest-invalid revision onto an active workflow forces it inactive to
keep the active ⇒ valid invariant (PO-186) — the restore still succeeds.
Restoring a revision whose definition is identical to the current one is
a safe no-op for the revision counter (no new history row is written),
though triggers are still re-synced from the snapshot.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/workflows/1/revisions/1/restore" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/revisions/1/restore"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (200, Restored):
{
"id": 1,
"name": "Sync orders",
"revision": 3,
"build_status": "pending",
"blob": {},
"triggers": []
}
Example response (404, Revision or workflow not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Workflow Runs
Trigger and inspect executions of a workflow. Each run is handed off to the workflow's dedicated Temporal worker, which orchestrates node execution and streams status back.
List runs for a workflow
requires authentication
Returns all runs for the workflow, newest first.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/runs?page=1&per_page=25" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs"
);
const params = {
"page": "1",
"per_page": "25",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [],
"meta": {
"current_page": 1,
"per_page": 25,
"total": 0,
"last_page": 1
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Start a workflow run
requires authentication
Execution behavior depends on workflow execution_mode and optional
request mode:
- Async: starts the run and returns HTTP 202 with status
running. - Sync: waits for worker completion and returns HTTP 200/422/504/503 with the final outcome.
For caller_decides, omitting mode defaults to async execution.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/workflows/1/runs" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"trigger_payload\": {
\"source\": \"api\"
},
\"trigger_handle\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
\"mode\": \"sync\"
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"trigger_payload": {
"source": "api"
},
"trigger_handle": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
"mode": "sync"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Sync completed):
{
"id": 1,
"workflow_id": 1,
"status": "completed",
"result": {
"node-1": {
"ok": true
}
},
"completed_at": "2026-05-22T10:00:30Z"
}
Example response (202, Accepted):
{
"id": 1,
"workflow_id": 1,
"status": "running",
"temporal_workflow_id": "tenant:1:abc",
"started_at": "2026-05-13T10:00:00Z"
}
Example response (404, Workflow not found):
{
"message": "Not found."
}
Example response (422, Unresolved manifest issues (PO-186)):
{
"message": "Workflow configuration has unresolved issues and cannot run. Fix the reported fields and try again.",
"errors": {
"blob.nodes.0.config.credentials": [
"Required config field 'credentials' is missing."
]
}
}
Example response (422, Mode not allowed):
{
"message": "Workflow does not allow sync execution (execution_mode=async_only)."
}
Example response (422, Sync failed):
{
"message": "Workflow execution failed.",
"error": "node execution failed",
"run": {
"id": 1,
"status": "failed"
}
}
Example response (501, Sync not supported by starter):
{
"message": "Synchronous workflow execution is not supported by the configured run starter driver."
}
Example response (503, Worker unavailable):
{
"message": "Workflow worker is unavailable."
}
Example response (504, Sync timeout):
{
"message": "Workflow execution timed out.",
"error": "Sync run for workflow 1 exceeded timeout of 30 seconds."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a workflow run
requires authentication
Returns the current state of a workflow run. If the run is still in progress, the worker is polled for the latest Temporal status.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/runs/1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Completed):
{
"id": 1,
"workflow_id": 1,
"status": "completed",
"result": {},
"completed_at": "2026-05-13T10:01:00Z"
}
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get execution details for a workflow run
requires authentication
Returns the execution description (status, timing, history length,
parent linkage) via {@see WorkflowExecutionInspector}. The inspector
transparently routes between a live Temporal call and the local
snapshot taken when the run reached a terminal status; the response
carries a source field of "temporal" or "snapshot" so clients
know which view they are seeing.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/runs/1/details" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/details"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Live):
{
"source": "temporal",
"data": {
"workflow_id": "tenant:1:abc",
"run_id": "0190...",
"status": "running"
}
}
Example response (200, Snapshot):
{
"source": "snapshot",
"data": {
"workflow_id": "tenant:1:abc",
"run_id": "0190...",
"status": "completed"
}
}
Example response (404, Run not found):
{
"message": "Not found."
}
Example response (409, Run not handed off to Temporal yet):
{
"message": "Workflow run has no Temporal execution yet."
}
Example response (410, History unavailable):
{
"message": "Workflow execution history is no longer available."
}
Example response (503, Temporal unavailable):
{
"message": "Temporal is currently unavailable."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get execution history for a workflow run
requires authentication
Returns one page of history events via {@see WorkflowExecutionInspector}.
Like the details endpoint, the response is hybrid-routed and carries
a source field. The next_page_token is opaque (source-tagged with
tmp: or snp:); pass it verbatim to the next call. Note that
switching sources mid-pagination resets to page 1 (the response's
source will change), which the client should treat as a fresh
pagination.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/runs/1/history?page_size=200" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/history"
);
const params = {
"page_size": "200",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Live page):
{
"source": "temporal",
"data": [
{
"event_id": 1,
"event_type": "workflow_execution_started"
}
],
"next_page_token": null
}
Example response (200, Snapshot page):
{
"source": "snapshot",
"data": [
{
"event_id": 1
}
],
"next_page_token": null
}
Example response (404, Run not found):
{
"message": "Not found."
}
Example response (409, Run not handed off to Temporal yet):
{
"message": "Workflow run has no Temporal execution yet."
}
Example response (410, History unavailable):
{
"message": "Workflow execution history is no longer available."
}
Example response (422, Invalid page token):
{
"message": "Invalid page_token."
}
Example response (503, Temporal unavailable):
{
"message": "Temporal is currently unavailable."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get the per-node steps of a workflow run
requires authentication
Projects the run's flat Temporal history into an ordered list of node
executions (one per scheduling, so a looped node yields one step per
iteration) for the step-through replay inspector: per node the status,
timing, duration, decoded input/config/output and any error. Like the
history endpoint the response is hybrid-routed and carries a source
field (snapshot for terminal snapshotted runs, temporal for live
reads). Node ids match the workflow blob's canvas node ids.
A failed step's error carries the failure's code (as type) and a
bounded message, and never a stack trace. Where the failure came from
a node's own code it also carries frames: the author's file relative
to the package it belongs to, a 1-based line and column, and the
function name where one was recorded, innermost first. Only frames
inside a node package appear. frames is absent where the failure
produced none, or where the artifact the run executed has no source map
stored — which is the case for every run recorded before this existed.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/runs/1/steps?include_payloads=1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/steps"
);
const params = {
"include_payloads": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Projected steps):
{
"source": "snapshot",
"run": {
"status": "completed",
"started_at": "2026-05-22T10:00:00Z",
"completed_at": "2026-05-22T10:00:05Z"
},
"steps": [
{
"node_id": "node-1",
"node_slug": "revenexx:http-request",
"activity_type": "executeNode",
"status": "completed",
"attempt": 0,
"scheduled_event_id": 5,
"duration_ms": 812,
"input": {},
"output": {},
"branch": null,
"error": null
}
]
}
Example response (200, A step that failed inside a node):
{
"source": "snapshot",
"run": {
"status": "failed"
},
"steps": [
{
"node_id": "f-empty-text",
"node_slug": "revenexx:deepl-translate",
"activity_type": "executeNode",
"status": "failed",
"attempt": 0,
"scheduled_event_id": 5,
"duration_ms": 41,
"input": {},
"output": null,
"branch": null,
"error": {
"message": "No text found to translate",
"type": "MISSING_TEXT",
"frames": [
{
"file": "@revenexx/integrations-nodes-core/src/nodes/DeeplTranslateNode.ts",
"line": 5,
"column": 11,
"name": "requireText"
}
]
}
}
]
}
Example response (404, Run not found):
{
"message": "Not found."
}
Example response (409, Run not handed off to Temporal yet):
{
"message": "Workflow run has no Temporal execution yet."
}
Example response (410, History unavailable):
{
"message": "Workflow execution history is no longer available."
}
Example response (503, Temporal unavailable):
{
"message": "Temporal is currently unavailable."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get the current call stack of a running workflow
requires authentication
Issues the built-in __stack_trace query against the workflow worker
and returns the formatted stack trace as plain text. Only meaningful
while the run is still executing; terminal runs return 409.
This is where the workflow is suspended, not where a step failed. For
the latter, a failed step's error.frames on the steps endpoint names
the line the node's author wrote.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1/runs/1/stack-trace" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/stack-trace"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Running):
{
"stack_trace": "coroutine ..."
}
Example response (404, Run not found):
{
"message": "Not found."
}
Example response (409, Run not handed off to Temporal yet):
{
"message": "Workflow run has no Temporal execution yet."
}
Example response (409, Run is no longer running):
{
"message": "Workflow run is no longer running."
}
Example response (410, Execution unknown to Temporal):
{
"message": "Workflow execution history is no longer available."
}
Example response (503, Temporal unavailable):
{
"message": "Temporal is currently unavailable."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Cancel a running workflow run
requires authentication
Requests graceful cancellation of a still-running run: Temporal delivers a
cancellation signal to the workflow, which may run cleanup before closing.
The run stays running until the next status reconcile flips it to
cancelled, so this returns HTTP 202.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/cancel" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"reason\": \"Superseded by a newer run\"
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/cancel"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"reason": "Superseded by a newer run"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (202, Cancellation requested):
{
"id": 1,
"workflow_id": 1,
"status": "running"
}
Example response (404, Run not found):
{
"message": "Not found."
}
Example response (409, Run already terminal):
{
"message": "Workflow run is no longer running."
}
Example response (503, Temporal unavailable):
{
"message": "Temporal is currently unavailable."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Terminate a running workflow run
requires authentication
Forcefully terminates a still-running run with no cleanup and flips it to
terminated immediately.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/terminate" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"reason\": \"Superseded by a newer run\"
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/terminate"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"reason": "Superseded by a newer run"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Terminated):
{
"id": 1,
"workflow_id": 1,
"status": "terminated"
}
Example response (404, Run not found):
{
"message": "Not found."
}
Example response (409, Run already terminal):
{
"message": "Workflow run is no longer running."
}
Example response (503, Temporal unavailable):
{
"message": "Temporal is currently unavailable."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Re-run a previous workflow run
requires authentication
Starts a fresh run from the inputs (trigger payload + trigger handle) of
an existing run, linking the new run to its source via
retried_from_run_id. The source run may be in any state; the re-run
always executes against the workflow's currently-built bundle and runs
asynchronously.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/retry" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/retry"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "POST",
headers,
}).then(response => response.json());Example response (0, Workflow not runnable):
{
"message": "Workflow bundle is not ready (build_"
}
Example response (202, Accepted):
{
"id": 2,
"workflow_id": 1,
"status": "running",
"retried_from_run_id": 1
}
Example response (404, Run not found):
{
"message": "Not found."
}
Example response (422, Unresolved manifest issues (PO-186)):
{
"message": "Workflow configuration has unresolved issues and cannot run. Fix the reported fields and try again.",
"errors": {
"blob.nodes.0.config.credentials": [
"Required config field 'credentials' is missing."
]
}
}
Example response (422, Trigger no longer resolvable):
{
"message": "No active trigger with handle ... exists on this workflow."
}
Example response (503, Worker unavailable):
{
"message": "Workflow worker is unavailable."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Resume a failed workflow run from its failed step
requires authentication
Resets the underlying Temporal execution to the workflow-task boundary
before the failed step and re-drives it: already-completed steps keep
their results, the failed step and everything downstream re-execute. The
resumed run is tracked as a new run linked via resumed_from_run_id.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/resume" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"reason\": \"Superseded by a newer run\"
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1/runs/1/resume"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"reason": "Superseded by a newer run"
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (202, Accepted):
{
"id": 2,
"workflow_id": 1,
"status": "running",
"resumed_from_run_id": 1
}
Example response (404, Run not found):
{
"message": "Not found."
}
Example response (409, Run not resumable state):
{
"message": "Only a failed run can be resumed."
}
Example response (409, Already resumed):
{
"message": "Workflow run has already been resumed."
}
Example response (422, No reset point):
{
"message": "Workflow run cannot be resumed: no suitable reset point in its history."
}
Example response (503, Temporal unavailable):
{
"message": "Temporal is currently unavailable."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Workflows
Per-tenant workflow definitions. Every endpoint is scoped to the tenant
resolved by the resolve.tenant middleware (from the JWT or
X-Tenant-Id header). Cross-tenant access deliberately returns 404 so
that the API does not leak the existence of resources owned by other
tenants.
List workflows
requires authentication
Returns the workflows owned by the current tenant, ordered by name.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"data": [
{
"id": 1,
"name": "Sync orders",
"description": null,
"blob_definition_version": "v0-draft",
"blob": {},
"active": true,
"revision": 1,
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T10:00:00Z"
}
]
}
Example response (401, Missing or invalid token):
{
"message": "Unauthenticated."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Create a workflow
requires authentication
Persists a new workflow under the current tenant. The name must be
unique within the tenant. The blob is validated against the schema
registered for the supplied blob_definition_version.
Example request:
curl --request POST \
"https://integrations.revenexx.com/api/v1/workflows" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"name\": \"Sync orders nightly\",
\"description\": \"Pulls orders from the upstream API every night at 02:00 UTC.\",
\"blob_definition_version\": \"v0-draft\",
\"blob\": {
\"nodes\": [],
\"edges\": []
},
\"active\": true,
\"execution_mode\": \"async_only\",
\"triggers\": [
{
\"handle\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
\"type\": \"webhook\",
\"name\": \"PSP webhook\",
\"config\": {
\"method\": \"POST\"
},
\"active\": true
}
]
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"name": "Sync orders nightly",
"description": "Pulls orders from the upstream API every night at 02:00 UTC.",
"blob_definition_version": "v0-draft",
"blob": {
"nodes": [],
"edges": []
},
"active": true,
"execution_mode": "async_only",
"triggers": [
{
"handle": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
"type": "webhook",
"name": "PSP webhook",
"config": {
"method": "POST"
},
"active": true
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (201, Created):
{
"id": 1,
"name": "Sync orders",
"description": null,
"blob_definition_version": "v0-draft",
"blob": {},
"active": true,
"revision": 1,
"warnings": [],
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T10:00:00Z"
}
Example response (422, Validation failed (structural, or manifest issues on an active save)):
{
"message": "The given data was invalid.",
"errors": {
"blob.nodes.0.config.credentials": [
"Required config field 'credentials' is missing."
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Response
Response Fields
warnings
object[]
Non-blocking manifest cross-validation issues (PO-186). Empty when the blob resolves cleanly; each entry is {path, message} keyed by blob.…. An inactive workflow persists even with warnings; an active save/activation is rejected with 422 instead. Returned by create/update/show, not by the list endpoint.
Show a workflow
requires authentication
Returns a single workflow by id within the current tenant.
Example request:
curl --request GET \
--get "https://integrations.revenexx.com/api/v1/workflows/1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200, Success):
{
"id": 1,
"name": "Sync orders",
"description": null,
"blob_definition_version": "v0-draft",
"blob": {},
"active": true,
"revision": 1,
"warnings": [],
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T10:00:00Z"
}
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Replace a workflow
requires authentication
Performs a full-replace update of the workflow with the given id. All
domain fields are required. The revision counter is incremented
exactly when the supplied blob differs from the stored one, in which
case the previous revision is snapshotted into the workflow history.
Example request:
curl --request PUT \
"https://integrations.revenexx.com/api/v1/workflows/1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production" \
--data "{
\"name\": \"Sync orders nightly\",
\"description\": \"Pulls orders from the upstream API every night at 02:00 UTC.\",
\"blob_definition_version\": \"v0-draft\",
\"blob\": {
\"nodes\": [],
\"edges\": []
},
\"active\": true,
\"execution_mode\": \"async_only\",
\"triggers\": [
{
\"handle\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
\"type\": \"webhook\",
\"name\": \"PSP webhook\",
\"config\": {
\"method\": \"POST\"
},
\"active\": true
}
]
}"
const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
let body = {
"name": "Sync orders nightly",
"description": "Pulls orders from the upstream API every night at 02:00 UTC.",
"blob_definition_version": "v0-draft",
"blob": {
"nodes": [],
"edges": []
},
"active": true,
"execution_mode": "async_only",
"triggers": [
{
"handle": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
"type": "webhook",
"name": "PSP webhook",
"config": {
"method": "POST"
},
"active": true
}
]
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, Updated):
{
"id": 1,
"name": "Sync orders",
"description": null,
"blob_definition_version": "v0-draft",
"blob": {},
"active": true,
"revision": 2,
"warnings": [],
"created_at": "2026-05-06T10:00:00Z",
"updated_at": "2026-05-06T11:00:00Z"
}
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Delete a workflow
requires authentication
Removes the workflow identified by {id} for the current tenant.
Associated history rows are removed via cascade.
Example request:
curl --request DELETE \
"https://integrations.revenexx.com/api/v1/workflows/1" \
--header "Authorization: Bearer {ZITADEL_JWT}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "X-Tenant-Id: acme-production"const url = new URL(
"https://integrations.revenexx.com/api/v1/workflows/1"
);
const headers = {
"Authorization": "Bearer {ZITADEL_JWT}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-Id": "acme-production",
};
fetch(url, {
method: "DELETE",
headers,
}).then(response => response.json());Example response (204, Deleted):
Empty response
Example response (404, Not found):
{
"message": "Not found."
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of this API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.