Observability API
Sagaweaw exposes a complete REST API for monitoring sagas, querying metrics, and managing dead letters. All endpoints require token authentication.
Security
By default, all endpoints return 403 if the token is not configured.
Configuration
sagaweaw:
observability:
enabled: true
token: ${SAGAWEAW_TOKEN} # required
previous-token: ${SAGAWEAW_PREVIOUS_TOKEN} # optional — zero-downtime rotation
auth:
max-attempts: 10 # failed logins before IP lockout (0 = disabled)
lockout-minutes: 15 # lockout duration
To completely disable the API:
sagaweaw:
observability:
enabled: false
Authentication
Pass the token in either of these two formats:
# Bearer token (standard)
Authorization: Bearer <token>
# Alternative header
X-Sagaweaw-Token: <token>
Rate limiting
After max-attempts consecutive failures from the same IP, all endpoints return 429 Too Many Requests with a Retry-After header indicating when the lockout expires. Expired lockouts are cleaned up lazily on the next request.
Token rotation
Set previous-token alongside token to accept both credentials in parallel. Any request authenticated with the previous token is logged as a WARN. Once all clients have switched to the new token, remove previous-token.
Saga Endpoints
GET /api/sagas
Lists sagas with pagination and optional filters.
Query params:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | int | 0 | Page (0-indexed) |
size | int | 50 | Items per page |
status | string | — | Filter by status (FAILED, COMPLETED, etc.) |
name | string | — | Search by saga name (contains, case-insensitive) |
id | string | — | Search by saga ID prefix |
idempotencyKey | string | — | Exact search by idempotency key |
from | ISO 8601 | — | Start date (2024-01-01T00:00:00Z) |
to | ISO 8601 | — | End date |
Examples:
# Last 50 sagas
curl -H "Authorization: Bearer $TOKEN" /api/sagas
# Sagas that failed today
curl -H "Authorization: Bearer $TOKEN" \
"/api/sagas?status=FAILED&from=2024-01-15T00:00:00Z"
# Search by name (contains)
curl -H "Authorization: Bearer $TOKEN" \
"/api/sagas?name=pix"
# Search by ID prefix
curl -H "Authorization: Bearer $TOKEN" \
"/api/sagas?id=01934abc"
# Search by idempotency key
curl -H "Authorization: Bearer $TOKEN" \
"/api/sagas?idempotencyKey=order-abc-123"
# Pagination
curl -H "Authorization: Bearer $TOKEN" \
"/api/sagas?page=2&size=25"
Response — array of SagaInstance:
[
{
"id": "01934...",
"name": "pix-payment",
"status": { "type": "COMPLETED", "completedAt": "2024-01-15T10:05:00Z", "durationMs": 312 },
"contextJson": "{\"transactionId\":\"...\"}",
"steps": [
{
"name": "validate-dict",
"order": 0,
"status": { "type": "COMPLETED" },
"attempt": 1,
"maxAttempts": 3,
"durationMs": 45,
"executedAt": "2024-01-15T10:04:59Z",
"completedAt": "2024-01-15T10:05:00Z"
}
],
"createdAt": "2024-01-15T10:04:58Z",
"updatedAt": "2024-01-15T10:05:00Z",
"version": 4
}
]
GET /api/sagas/{id}
Returns a specific saga by ID.
curl -H "Authorization: Bearer $TOKEN" /api/sagas/01934abc-...
- 200 — saga found
- 404 — saga does not exist
GET /api/sagas/{id}/events
Returns the immutable audit log of the saga — all state transitions in chronological order.
curl -H "Authorization: Bearer $TOKEN" /api/sagas/01934abc-.../events
Response:
[
{
"id": "...",
"sagaId": "01934abc-...",
"stepName": null,
"eventType": "SAGA_STARTED",
"payload": null,
"createdAt": "2024-01-15T10:04:58Z"
},
{
"id": "...",
"sagaId": "01934abc-...",
"stepName": "validate-dict",
"eventType": "STEP_COMPLETED",
"payload": "{\"durationMs\":45}",
"createdAt": "2024-01-15T10:05:00Z"
}
]
- 200 — event list (may be empty)
- 404 — saga does not exist
POST /api/sagas/{id}/reprocess
Re-executes a saga from the step that failed.
curl -X POST -H "Authorization: Bearer $TOKEN" /api/sagas/01934abc-.../reprocess
- 202 — reprocessing started
- 404 — saga does not exist
Metrics Endpoints
GET /api/sagas/metrics
Returns aggregated metrics for all sagas.
curl -H "Authorization: Bearer $TOKEN" /api/sagas/metrics
Response:
{
"total": 1520,
"started": 3,
"executing": 12,
"completed": 1480,
"compensated": 18,
"failed": 7,
"deadLetters": 2,
"successRate": 98.7,
"byName": [
{
"name": "pix-payment",
"total": 900,
"completed": 885,
"failed": 5
}
]
}
| Field | Description |
|---|---|
successRate | completed / (completed + failed + compensated) * 100, rounded to 1 decimal |
deadLetters | Pending dead letters (not reprocessed) |
byName | Breakdown by saga name, ordered by volume |
GET /api/sagas/stats/by-name
Returns statistics by saga type, including average duration.
curl -H "Authorization: Bearer $TOKEN" /api/sagas/stats/by-name
Response:
[
{
"name": "pix-payment",
"total": 900,
"completed": 885,
"failed": 5,
"avgDurationMs": 312
},
{
"name": "order-processing",
"total": 620,
"completed": 595,
"failed": 2,
"avgDurationMs": 1840
}
]
| Field | Description |
|---|---|
avgDurationMs | Average duration in milliseconds (only COMPLETED sagas) |
GET /api/sagas/steps/stats
Returns step statistics grouped by saga name. Used by the dashboard to identify bottlenecks.
curl -H "Authorization: Bearer $TOKEN" /api/sagas/steps/stats
Response:
[
{
"stepName": "transmit-to-bacen",
"sagaName": "pix-payment",
"avgDurationMs": 245,
"total": 900,
"failed": 12
},
{
"stepName": "validate-dict",
"sagaName": "pix-payment",
"avgDurationMs": 18,
"total": 900,
"failed": 0
}
]
Ordered by avgDurationMs descending. avgDurationMs may be null if no step has finished yet.
The dashboard uses this endpoint to calculate the time proportion of each step relative to the saga total. A step consuming >40% of the saga's total time appears in red — regardless of the absolute value in ms.
CSV Export Endpoints
GET /api/dead-letters/export?format=csv
GET /api/sagas/export?status=FAILED&from=2025-01-01
Returns text/csv. No dependencies on iText/PDFBox — generated with plain Java.
Dead Letter Endpoints
Dead letters are steps that exhausted all retry attempts and need manual intervention. The API exposes only pending ones — reprocessed items leave the queue and their history remains in the saga's event log.
GET /api/dead-letters
Lists pending dead letters. Optionally filter by saga.
# all pending
curl -H "Authorization: Bearer $TOKEN" /api/dead-letters
# only for a specific saga
curl -H "Authorization: Bearer $TOKEN" "/api/dead-letters?sagaId=01934abc-..."
| Parameter | Description |
|---|---|
sagaId | Filter by saga ID (optional) |
includeReprocessed | Include already-reprocessed items — default false (ignored when sagaId is set) |
Response:
[
{
"id": "...",
"sagaId": "01934abc-...",
"sagaName": "pix-payment",
"stepName": "transmit-to-bacen",
"errorMessage": "Connection timeout after 3 attempts",
"errorTrace": "java.net.SocketTimeoutException: ...",
"contextSnapshot": "{\"transactionId\":\"...\"}",
"createdAt": "2024-01-15T10:10:00Z"
}
]
| Field | Description |
|---|---|
errorMessage | Summary message from the last exception |
errorTrace | Full stack trace |
contextSnapshot | JSON of the saga context at the time of failure |
GET /api/dead-letters/{id}
Returns a specific dead letter by ID.
curl -H "Authorization: Bearer $TOKEN" /api/dead-letters/abc-...
- 200 — dead letter found
- 404 — does not exist
POST /api/dead-letters/{id}/reprocess
Marks the dead letter as reprocessed and restarts the corresponding step in the saga.
curl -X POST -H "Authorization: Bearer $TOKEN" /api/dead-letters/abc-.../reprocess
- 202 — reprocessing started
- 404 — dead letter does not exist
The dead letter disappears from the pending queue. The step is re-executed with the configured retry policy. The result (success or new failure) appears in the saga event log at /api/sagas/{id}/events.