Tsumugi Docs
Search

INFO

This page was translated by an LLM. Some details and nuances may differ from the original.

REST API

When auth is configured, Tsumugi handles requests to endpoints under /api.
If it is not configured, Tsumugi responds to every request with 404.
Authentication applies only to /api/*.
The dashboard HTML contains no job data, so it is served without requiring authentication.
If the request is not authenticated, the dashboard shows a token input field.
Get the OpenAPI definition with GET /api/openapi.json.

Common responses

StatusMeaning
401Authentication failed
501The operation is not configured
503Configuration is incomplete, or the service is temporarily unavailable

The 503 body includes the reason.
If there are unapplied migrations, the body includes the names of the unapplied files.
If bindings are missing, the body includes the list of missing bindings.
If the service is temporarily unavailable, wait and retry.

In a configuration with no registered Flow, the endpoints that start, resume, and cancel a Run respond with 501.
/api/openapi.json responds even when configuration is incomplete.
If the runId format is invalid, endpoints that include /api/runs/:id respond with 400.

Jobs

GET /api/jobs

Gets the list of jobs.
The response contains an array of jobs and the total count.

Query parameters

NameDefaultDescription
stateNoneFilter by state
bindingNoneFilter by binding name
idNoneExact match on job ID
unique_keyNoneExact match on uniqueKey
concurrency_keyNoneExact match on concurrencyKey
created_fromNoneLower bound of created_at. In epoch milliseconds, inclusive
created_toNoneUpper bound of created_at. In epoch milliseconds, inclusive
limit20Maximum 100
offset0Starting position
sortupdated_atOne of updated_at, created_at, binding, state, priority, attempts
orderdescAscending only when asc is specified

When multiple conditions are specified, the list includes the jobs that satisfy all of them.
Key searches are exact match; partial match is not available.
If an invalid value is given for sort or order, the response uses the default order instead of returning 400.
If a non-integer is given for created_from or created_to, that condition is ignored.

{
  "jobs": [
    {
      "id": "MAIL#0:xxxxxxxxxxxxxxxxxxxxxxxx",
      "binding": "MAIL",
      "state": "FAILED",
      "priority": 0,
      "attempts": 3,
      "max_attempts": 3,
      "created_at": 1753000000000,
      "updated_at": 1753000060000,
      "dispatched_at": 1753000030000,
      "retryable": true
    }
  ],
  "total": 1
}

retryable is an estimate calculated from the retention period.
Whether a retry is allowed is determined when the retry request is made.
Retrying a job that is past its retention period returns 410.

GET /api/jobs/:id

Gets the details of one job.
If the job is not found, the response is 404.

{
  "job": {
    "id": "MAIL#0:xxxxxxxxxxxxxxxxxxxxxxxx",
    "binding": "MAIL",
    "state": "FAILED",
    "payload": "{\"to\":\"a@example.com\"}",
    "result": null,
    "priority": 0,
    "attempts": 3,
    "max_attempts": 3,
    "progress": null,
    "concurrency_key": "domain:example.com",
    "unique_key": null,
    "guarantee": "at-least-once",
    "created_at": 1753000000000,
    "updated_at": 1753000060000,
    "dispatched_at": 1753000030000,
    "run_after": null,
    "expires_at": null,
    "cancel_requested_at": null,
    "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
    "retryable": true,
    "logs": [
      {
        "attempt": 1,
        "timestamp": 1753000011000,
        "message": "Payment requested"
      }
    ],
    "attempts_log": [
      {
        "attempt": 1,
        "state": "FAILED",
        "started_at": 1753000010000,
        "finished_at": 1753000012000,
        "error": "payment failed: 502"
      }
    ]
  }
}
FieldDescription
resultThe return value of perform. null unless the job succeeded
run_afterThe scheduled execution time of a scheduled job
expires_atThe deadline for starting execution. A job past its deadline transitions to CANCELLED without being executed
cancel_requested_atThe time at which cancellation of a running job was requested. null if not requested
attempts_logThe attempt history, newest attempt first
logsLogs saved with ctx.log(). In the order received; each entry has the attempt number, the recording time (epoch milliseconds), and the message. An empty array if there are no logs. For the limits on the number of entries and the message length, see Logs
traceparentThe trace information specified at enqueue. null if not specified

POST /api/jobs

Enqueues a job.
The request body is JSON in the following format:

{
  "binding": "MAIL",
  "payload": { "to": "a@example.com", "subject": "hi" },
  "maxAttempts": 5,
  "delayMs": 60000,
  "expiresInMs": 600000,
  "priority": 10,
  "concurrencyKey": "domain:example.com",
  "uniqueKey": "mail:a@example.com:hi",
  "partitionKey": "tenant-42",
  "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}

binding and payload are required; specify a binding registered in performers.
maxAttempts, delayMs, expiresInMs, and priority must be integers.
Fields that are not in the example above (such as timeoutMs and backoff) are ignored, and the default values are used.

expiresInMs is the deadline for execution, counted from the time the job is enqueued.
A job past its deadline is not executed and transitions to CANCELLED.
partitionKey is the key that determines which shard a job is enqueued to, for a binding with shards set to 2 or more, and it is required for that binding.

traceparent is optional.
If it is not specified in the body, the value of the traceparent header is used; if both are specified, the value in the body takes precedence.
Use the W3C Trace Context version 00 format described in Enqueuing jobs for the value.
The specified value is carried over on retries and passed to the performer as ctx.traceparent.

If uniqueKey matches an existing job, the response is also 201, no new job is created, and the body contains the existing job ID.

StatusMeaning
201Created. The body is { "id": "..." }
400Invalid JSON, or validation failure (unregistered binding, non-integer value, invalid traceparent)

POST /api/jobs/:id/retry

Transitions a FAILED or STALLED job to the SCHEDULED state.
A job that was deleted after its retention period cannot be retried.

StatusMeaning
200Retried
400Invalid job ID format
409The job state is neither FAILED nor STALLED
410The job does not exist, or was deleted after its retention period

For 409, wait until the job's state changes, then request again.

POST /api/jobs/:id/cancel

Cancels a job.
The targets are jobs in SCHEDULED, QUEUED, or RUNNING.

A SCHEDULED job transitions to CANCELLED immediately.
QUEUED and RUNNING jobs may already be executing, so the cancellation is recorded as a request.
In this case, the response includes requested: true.
The job transitions to CANCELLED when the performer acknowledges the request and finishes, or when no result is reported within reaperGraceMs (default 30 seconds) of the request.
For details, see Cancelling a running job.

{ "ok": true, "requested": true }

If cancellation is requested again for a job whose cancellation has already been requested, the response is also requested: true.
A request for a job that has finished returns 409.
All other responses are the same as for retry.

POST /api/jobs/bulk-retry

POST /api/jobs/bulk-cancel

Retries or cancels multiple jobs at once.
Specify the targets either as an array of job IDs or as filter conditions.

To specify by job ID, provide ids; to specify by filter conditions, omit ids.

{ "ids": ["MAIL#0:xxxxxxxxxxxxxxxxxxxxxxxx", "MAIL#0:yyyyyyyyyyyyyyyyyyyyyyyy"] }
{ "binding": "MAIL", "state": "FAILED", "created_from": 1753000000000, "limit": 200 }
FieldRequiredDescription
idsOptionalAn array of job IDs. 1 to 200 entries
bindingOptionalFilter by binding name
stateOptionalFor bulk-retry: FAILED, STALLED. For bulk-cancel: SCHEDULED, QUEUED, RUNNING
unique_keyOptionalExact match
concurrency_keyOptionalExact match
created_fromOptionalLower bound of created_at
created_toOptionalUpper bound of created_at
limitOptionalThe number of jobs to process in one request. At least 1; the default and the maximum are both 200

When ids is specified, the other conditions are ignored.
Specify integers for created_from, created_to, and limit.

If state is omitted from the conditions, bulk-retry targets jobs in FAILED and STALLED, and bulk-cancel targets jobs in SCHEDULED, QUEUED, and RUNNING.
If any other state is specified, the response is 400.
In bulk-retry, jobs that are past failedRetentionMs are excluded from the targets.
In bulk-cancel, jobs for which cancellation has already been requested are excluded until CANCELLED is finalized.
When jobs are specified directly with ids, requesting again for a job that has already been requested does not process it twice.

{
  "ok": ["MAIL#0:xxxxxxxxxxxxxxxxxxxxxxxx"],
  "failed": [{ "id": "MAIL#0:yyyyyyyyyyyyyyyyyyyyyyyy", "reason": "gone" }],
  "remaining": 320
}

Even if processing fails for some of the jobs, the status code is 200.
In bulk-cancel, jobs whose cancellation was recorded as a request are also included in ok.
The reason in failed corresponds to the errors returned by the endpoints that retry and cancel a single job.
invalid-state and gone correspond to 409 and 410, respectively.
If ids contains a value that is not a valid job ID, that value is included in failed as invalid-id.

remaining is the number of jobs that were not processed because they exceeded limit, when the targets are specified by conditions.
Resend the same request until it reaches 0.
When the targets are specified by ids, remaining is always 0.
A job that does not match the conditions at the time of the request is included in failed, even if it appears in the list.

POST /api/jobs/:id/reschedule

Changes the execution time of a scheduled job. The target is a SCHEDULED job.

{ "runAt": 1753003600000, "priority": 10 }
FieldRequiredDescription
runAtConditionalAn absolute time. Cannot be specified together with delayMs
delayMsConditionalA time relative to the current time. Cannot be specified together with runAt
priorityOptionalSpecify to change the priority at the same time

Specify exactly one of runAt and delayMs.
If both are specified, or neither is specified, the response is 400.
runAt, delayMs, and priority must be integers.

The job ID does not change.
Duplicate detection by uniqueKey also continues.
The status codes are the same as for retry.

Statistics and diagnostics

GET /api/stats

Gets the number of jobs in each state.

{
  "byState": { "SCHEDULED": 12, "RUNNING": 3, "COMPLETED": 480, "FAILED": 2 },
  "oldestScheduledMs": 45000
}

oldestScheduledMs is the time in milliseconds that has elapsed since the oldest SCHEDULED job was enqueued.
If there are no SCHEDULED jobs, the value is null.

GET /api/metrics

Gets the failure rate and duration for each binding, based on the recorded metrics.

Query parameters

NameDefaultDescription
hours24The number of hours to aggregate. An integer from 1 to 720
bindingNoneFilter by binding name
{
  "hours": 24,
  "bindings": [
    {
      "binding": "MAIL",
      "total": 1200,
      "failed": 18,
      "failureRate": 0.015,
      "avgDurationMs": 820,
      "maxDurationMs": 5400,
      "p95DurationMs": 2100,
      "avgAttempts": 1.04
    }
  ],
  "series": [{ "at": 1753000000000, "total": 50, "failed": 1, "avgDurationMs": 810 }]
}

Even for periods in which metrics are sampled, the values are close to the actual counts.
If hours is out of range, or binding is an invalid name, the response is 400.
In a configuration without metrics, the response is 501.
502 is the response when the metrics could not be retrieved, and the body includes the reason.

GET /api/diagnostics

Gets, for each binding, the reasons that waiting jobs are not being executed. It also includes the number of running jobs and the current throughput settings.

{
  "bindings": {
    "MAIL": {
      "active": 2,
      "blocked": { "paused": false, "capacity": true, "tokens": false, "perKey": false, "perKeyTokens": false },
      "policy": {
        "paused": false,
        "concurrency": 100,
        "perKeyConcurrency": 1,
        "rate": null,
        "perKeyRate": null,
        "agingIntervalMs": 60000,
        "reaperGraceMs": 30000
      }
    }
  }
}
FieldMeaning
activeThe number of running jobs
blockedThe settings that are limiting the execution of waiting jobs
policyThe throughput settings currently in effect

blocked

FieldLimit
pausedPaused
capacityConcurrency
tokensRate
perKeyPer-key concurrency
perKeyTokensPer-key rate

GET /api/bindings

Gets the list of binding names to which jobs can be enqueued.
All binding names registered in performers are included, even bindings that have never been executed.

{ "bindings": ["CHARGE", "MAIL"] }

Throughput settings

POST /api/bindings/:binding/policy

Changes the throughput settings at runtime.
The changes take precedence over the bindings settings.

{ "paused": true, "concurrency": 5 }
FieldDescription
pausedPauses enqueuing. Monitoring of running jobs continues while paused
concurrencyConcurrency. An integer of 0 or greater
perKeyConcurrencyPer-key concurrency. An integer of 0 or greater
rate{ tokens, intervalMs }. null removes the rate limit
perKeyRate{ tokens, intervalMs }. The per-key rate. null removes the limit
agingIntervalMsThe aging interval. null disables aging
reaperGraceMsThe grace period before a job is judged unresponsive. An integer of 0 or greater

Only the specified fields are changed; omitted fields are kept.
On success, the response includes the settings after the change and the number of shards to which the change was applied.
The following is an example for a change to paused and concurrency.

{
  "binding": "MAIL",
  "shards": 1,
  "policy": {
    "paused": true,
    "concurrency": 5,
    "perKeyConcurrency": 1,
    "rate": null,
    "perKeyRate": null,
    "agingIntervalMs": 60000,
    "reaperGraceMs": 30000
  }
}

The change is applied to all shards that belong to the target binding.
If the binding is split into shards, a network partition or a temporary failure may cause the change to fail on some shards.
In that case, the response is HTTP status code 500, and the body includes the numbers of the shards that failed.

{ "error": "the change reached 3 of 4 shards, send the same request again", "binding": "MAIL", "shards": 4, "failed": [2] }

Sending the same request again applies the change to the remaining shards.
The resulting settings are the same no matter how many times the request is sent.
After the change, the bindings settings no longer apply.

POST /api/bindings/:binding/policy/reset

Discards the runtime changes and returns to the bindings settings.
From the next enqueue, the bindings settings apply again.

{ "ok": true }

If applying the reset fails on some shards, as with a policy change, the response is 500 and the body includes failed.

Schedules

In a configuration without schedules defined, the endpoints in this section respond with 501.
For details, see Schedules.

GET /api/schedules

Gets the list of schedules.
Unlike the job list, the state is reflected in real time.

{
  "schedules": [
    {
      "name": "nightly",
      "kind": "flow",
      "target": "REPORT",
      "every_ms": null,
      "cron": "0 3 * * *",
      "time_zone": "Asia/Tokyo",
      "overlap": "skip",
      "paused": false,
      "next_occurrence_at": 1767722400000,
      "last_occurrence_at": 1767636000000,
      "last_fired_at": 1767636001200,
      "last_job_id": null,
      "last_run_id": "REPORT:nightly-1767636000000",
      "last_skipped_at": null,
      "skipped_count": 0,
      "last_error": null
    }
  ]
}
FieldMeaning
nameThe name of the schedule. The key in schedules
kindjob or flow
targetThe binding name or Flow name
every_msThe fixed interval. null when cron is specified
cronThe cron expression. null when everyMs is specified
time_zoneThe IANA time zone used for cron and for display. UTC for a fixed interval
overlapskip or overlap
pausedWhether the schedule is paused
next_occurrence_atThe next scheduled execution
last_occurrence_atThe scheduled time of the previous execution. null if it has never been executed
last_fired_atThe actual time of the previous execution. The difference from last_occurrence_at is the execution delay
last_job_idThe ID of the job enqueued by the previous execution. null when kind is flow
last_run_idThe ID of the Run started by the previous execution. null when kind is job
last_skipped_atThe time of the most recent occurrence that was not executed. null if there is none
skipped_countThe number of occurrences that were not executed because the previous execution had not finished
last_errorThe reason the previous execution failed. null if it succeeded

Fields whose names end in _at are epoch milliseconds.

POST /api/schedules/:name/pause

While a schedule is paused, no periodic executions take place.
Jobs enqueued before the pause are not affected.

{ "ok": true }

POST /api/schedules/:name/resume

Resumes a paused schedule.
Occurrences that passed while the schedule was paused are not executed.
The next execution after resuming is the first scheduled time after the moment of resuming.

{ "ok": true }

POST /api/schedules/:name/trigger

Executes the schedule once, immediately.

{ "id": "REPORT:nightly-1767650000000-1-manual", "kind": "flow" }

id is the ID of the job that was enqueued or the ID of the Run that was started.
kind indicates whether id refers to a job or a Run.
The schedule is executed even while paused, and the overlap check is not performed.
The time of the next periodic execution does not change.
If the execution fails, the response is 500 with the error details.
The value of last_error in the list is the same as these error details.

Run

GET /api/flows

Gets the registered Flow names.
All Flow names registered in flows are included, whether or not they have been executed.

{ "flows": ["GREETINGS", "PIPELINE"] }

GET /api/runs

Gets the list of Runs.
For each Run, the progress of its nodes is included.

Query parameters

NameDefaultDescription
stateNoneOne of RUNNING, COMPLETED, FAILED, CANCELLED
flowNoneFilter by Flow name
limit20Maximum 100
offset0Paging
{
  "runs": [
    {
      "id": "GREETINGS:cl9x0a1b2c3d",
      "flow": "GREETINGS",
      "state": "RUNNING",
      "node_total": 6,
      "node_done": 4,
      "node_failed": 0,
      "created_at": 1767225600000,
      "updated_at": 1767225603000
    }
  ],
  "total": 1
}

GET /api/runs/:id

Gets one Run and the nodes it contains.
The nodes include both the static nodes defined in advance and the nodes added dynamically at run time.

{
  "run": {
    "id": "GREETINGS:cl9x0a1b2c3d",
    "flow": "GREETINGS",
    "state": "RUNNING",
    "input": "{\"prefix\":\"hello\"}",
    "node_total": 6,
    "node_done": 4,
    "node_failed": 0,
    "created_at": 1767225600000,
    "updated_at": 1767225603000,
    "parent_run_id": null,
    "parent_node_id": null,
    "retryable": false
  },
  "nodes": [
    {
      "id": "greet",
      "binding": "GREET",
      "state": "RUNNING",
      "container": true,
      "parent": null,
      "origin": "static",
      "after": ["list"],
      "job_id": null,
      "child_run_id": null,
      "result": null,
      "error": null,
      "position": 1,
      "created_at": 1767225600000,
      "updated_at": 1767225603000
    }
  ]
}

A node whose container is true is a fan-out node.
A fan-out node has no job, so job_id is null.
result contains the aggregate values of the child nodes.
A node that has a parent was added at run time.
origin is fanOut or spawn.
For a Run that was started as a subflow, parent_run_id and parent_node_id contain the IDs of the parent Run and node.
For a subflow node, child_run_id contains the runId of the child Run that was started.

POST /api/runs

Starts a Run.
The request body is JSON that contains the Flow name and the input.

{ "flow": "GREETINGS", "input": { "prefix": "hello" }, "id": "order-1234" }
FieldRequiredDescription
flowRequiredA registered Flow name. 400 if it is not registered
inputRequiredAny JSON. No type checking is applied
idOptionalThe local part of the runId. A second start with the same ID responds with the existing runId
deadlineMsOptionalThe deadline for the whole Run, in milliseconds. A positive integer. When the deadline is exceeded, the Run is aborted and transitions to FAILED. Takes precedence over the deadline in the Flow definition

On success, the response is 201 and the body contains the runId of the Run that was started.
The runId in the following example is GREETINGS:order-1234, which is built from flow and id.

{ "id": "GREETINGS:order-1234" }

POST /api/runs/:id/retry

Resumes a failed Run.
The results of completed nodes are carried over as they are.
Nodes in FAILED, STALLED, SKIPPED, and CANCELLED are executed again as not yet executed.
Of the child nodes created by fan-out, only the child nodes that were aborted because the Run's deadline passed are executed again.
Child nodes that were executed and failed are not executed again.

Only a FAILED Run can be resumed.
A Run in any other state responds with 409.
A Run that does not exist, or a Run that is past its retention period, responds with 410.

POST /api/runs/:id/cancel

Cancels a Run.
Nodes that have not started are not started, and a cancellation request is sent to the nodes that are running.
Running jobs are not stopped.
The Run transitions to CANCELLED when the performer acknowledges the request and finishes, or when no result is reported within reaperGraceMs (default 30 seconds) of the request.
Only a RUNNING Run can be cancelled.

GET /api/openapi.json

Gets the OpenAPI 3.1 definition that describes the parameters and response formats of all endpoints.

Edit this page

Published: Last updated: