Trama

Examples

Use these Trama examples to build saga definitions, run workflows inline or from stored versions, and test retries, compensation, and payload templating. This page is the fastest way to move from understanding Trama to using it.

Best for first-time users Hands-on adoption Copy-and-run samples

Best starting points

If you are new to Trama, start with creating a stored definition, then run the inline example, and finally move to the robust sample with retries, compensation, callbacks, and templates.

Create and Store a Definition

curl -X POST http://localhost:8080/sagas/definitions \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "order-saga",
    "version": "v1",
    "failureHandling": {
      "type": "retry",
      "maxAttempts": 3,
      "delayMillis": 500
    },
    "entrypoint": "reserve-inventory",
    "nodes": [
      {
        "kind": "task",
        "id": "reserve-inventory",
        "action": {
          "mode": "sync",
          "request": {
            "url": "http://inventory/reserve",
            "verb": "POST",
            "body": {
              "orderId": "{{payload.orderId}}"
            }
          }
        },
        "compensation": {
          "url": "http://inventory/release",
          "verb": "POST",
          "body": {
            "orderId": "{{payload.orderId}}"
          }
        }
      }
    ]
  }'

Robust Definition Using All Main Fields

This sample covers: backoff, multi-node flow, headers, body templates, explicit successStatusCodes, node response references, and success/failure callbacks.

{
  "name": "order-fulfillment",
  "version": "v1",
  "failureHandling": {
    "type": "backoff",
    "maxAttempts": 5,
    "initialDelayMillis": 200,
    "maxDelayMillis": 5000,
    "multiplier": 2.0,
    "jitterRatio": 0.2
  },
  "entrypoint": "reserve-inventory",
  "nodes": [
    {
      "kind": "task",
      "id": "reserve-inventory",
      "action": {
        "mode": "sync",
        "request": {
          "url": "http://inventory/api/v1/reservations",
          "verb": "POST",
          "headers": {
            "X-Correlation-Id": "{{payload.correlationId}}",
            "Content-Type": "application/json"
          },
          "body": {
            "orderId": "{{payload.orderId}}",
            "items": "{{payload.items}}"
          },
          "successStatusCodes": [200, 201, 202]
        }
      },
      "compensation": {
        "url": "http://inventory/api/v1/reservations/{{nodes.reserve-inventory.response.body.reservationId}}/cancel",
        "verb": "POST",
        "headers": {
          "X-Correlation-Id": "{{payload.correlationId}}"
        },
        "body": {
          "reason": "compensation"
        },
        "successStatusCodes": [200, 204]
      },
      "next": "charge-payment"
    },
    {
      "kind": "task",
      "id": "charge-payment",
      "action": {
        "mode": "sync",
        "request": {
          "url": "http://payments/api/v1/charges",
          "verb": "POST",
          "headers": {
            "Authorization": "Bearer {{payload.paymentToken}}",
            "X-Correlation-Id": "{{payload.correlationId}}"
          },
          "body": {
            "orderId": "{{payload.orderId}}",
            "amount": "{{payload.amount}}",
            "reservationId": "{{nodes.reserve-inventory.response.body.reservationId}}"
          },
          "successStatusCodes": [200, 201]
        }
      },
      "compensation": {
        "url": "http://payments/api/v1/charges/{{nodes.charge-payment.response.body.chargeId}}/refund",
        "verb": "POST",
        "headers": {
          "Authorization": "Bearer {{payload.paymentToken}}",
          "X-Correlation-Id": "{{payload.correlationId}}"
        },
        "body": {
          "orderId": "{{payload.orderId}}",
          "reason": "compensation"
        },
        "successStatusCodes": [200, 202]
      }
    }
  ],
  "onSuccessCallback": {
    "url": "http://notifications/api/v1/order-success",
    "verb": "POST",
    "headers": {
      "Content-Type": "application/json"
    },
    "body": {
      "orderId": "{{payload.orderId}}",
      "chargeId": "{{nodes.charge-payment.response.body.chargeId}}"
    },
    "successStatusCodes": [200, 202, 204]
  },
  "onFailureCallback": {
    "url": "http://notifications/api/v1/order-failed",
    "verb": "POST",
    "headers": {
      "Content-Type": "application/json"
    },
    "body": {
      "orderId": "{{payload.orderId}}",
      "message": "saga failed or compensated"
    },
    "successStatusCodes": [200, 202, 204]
  }
}

Run Stored Definition

curl -X POST http://localhost:8080/sagas/definitions/order-saga/v1/run \
  -H 'Content-Type: application/json' \
  -d '{"payload": {"orderId": "ord-123", "amount": 99.5}}'

Run Inline Definition

curl -X POST http://localhost:8080/sagas/run \
  -H 'Content-Type: application/json' \
  -d '{
    "definition": {
      "name": "inline",
      "version": "v1",
      "failureHandling": {
        "type": "retry",
        "maxAttempts": 1,
        "delayMillis": 200
      },
      "entrypoint": "notify",
      "nodes": [
        {
          "kind": "task",
          "id": "notify",
          "action": {
            "mode": "sync",
            "request": {
              "url": "http://service/run",
              "verb": "POST"
            }
          }
        }
      ]
    },
    "payload": {}
  }'

Template Example

{
  "url": "http://service/charge?reservation={{nodes.reserve-inventory.response.body.id}}",
  "verb": "POST",
  "body": {
    "amount": "{{payload.amount}}"
  }
}

Switch Branching

Run an inline saga that routes to a different payment node based on paymentMethod. The choose-payment switch node evaluates a JSON Logic expression against the input payload and dispatches to pix-payment, card-payment, or a fallback.

curl -X POST http://localhost:8080/sagas/run \
  -H 'Content-Type: application/json' \
  -d '{
    "definition": {
      "name": "checkout-v2",
      "version": "v1",
      "failureHandling": { "type": "retry", "maxAttempts": 2, "delayMillis": 100 },
      "entrypoint": "choose-payment",
      "nodes": [
        {
          "kind": "switch",
          "id": "choose-payment",
          "cases": [
            {
              "name": "pix",
              "when": { "==": [{ "var": "input.paymentMethod" }, "pix"] },
              "target": "pix-payment"
            },
            {
              "name": "card",
              "when": { "==": [{ "var": "input.paymentMethod" }, "card"] },
              "target": "card-payment"
            }
          ],
          "default": "fallback-payment"
        },
        {
          "kind": "task",
          "id": "pix-payment",
          "action": {
            "mode": "sync",
            "request": {
              "url": "http://payments/pix",
              "verb": "POST",
              "body": { "orderId": "{{payload.orderId}}" }
            }
          },
          "compensation": { "url": "http://payments/pix/cancel", "verb": "POST" }
        },
        {
          "kind": "task",
          "id": "card-payment",
          "action": {
            "mode": "sync",
            "request": {
              "url": "http://payments/card",
              "verb": "POST",
              "body": { "orderId": "{{payload.orderId}}" }
            }
          },
          "compensation": { "url": "http://payments/card/cancel", "verb": "POST" }
        },
        {
          "kind": "task",
          "id": "fallback-payment",
          "action": {
            "mode": "sync",
            "request": { "url": "http://payments/fallback", "verb": "POST" }
          }
        }
      ]
    },
    "payload": { "orderId": "ord-456", "paymentMethod": "pix" }
  }'

Async Callback

An async task node fires its outbound request and pauses the workflow. Trama injects {{runtime.callback.url}} and {{runtime.callback.token}} into the request body. The external service must POST to the callback URL with the token header to resume execution.

curl -X POST http://localhost:8080/sagas/run \
  -H 'Content-Type: application/json' \
  -d '{
    "definition": {
      "name": "payment-async",
      "version": "v1",
      "failureHandling": { "type": "retry", "maxAttempts": 2, "delayMillis": 200 },
      "entrypoint": "authorize",
      "nodes": [
        {
          "kind": "task",
          "id": "authorize",
          "action": {
            "mode": "async",
            "request": {
              "url": "http://payments/authorize",
              "verb": "POST",
              "body": {
                "orderId": "{{payload.orderId}}",
                "callbackUrl": "{{runtime.callback.url}}",
                "callbackToken": "{{runtime.callback.token}}"
              }
            },
            "acceptedStatusCodes": [202],
            "callback": { "timeoutMillis": 30000 }
          },
          "compensation": { "url": "http://payments/authorize/cancel", "verb": "POST" },
          "next": "capture"
        },
        {
          "kind": "task",
          "id": "capture",
          "action": {
            "mode": "sync",
            "request": { "url": "http://payments/capture", "verb": "POST" }
          }
        }
      ]
    },
    "payload": { "orderId": "ord-789", "amount": 150.00 }
  }'

Once the external service is ready, it resumes the workflow by calling back:

curl -X POST http://localhost:8080/sagas/<executionId>/node/authorize/callback \
  -H 'Content-Type: application/json' \
  -H 'X-Callback-Token: <token>' \
  -d '{ "status": "authorized", "authCode": "AUTH-001" }'

Sleep Pause

A sleep node pauses the workflow for a configurable duration before advancing to the next node. Use it for time-based orchestration — e.g., wait 24 hours before sending a follow-up notification, or introduce a cooling-off period between steps.

curl -X POST http://localhost:8080/sagas/run \
  -H 'Content-Type: application/json' \
  -d '{
    "definition": {
      "name": "order-with-delay",
      "version": "v1",
      "failureHandling": { "type": "retry", "maxAttempts": 2, "delayMillis": 200 },
      "entrypoint": "confirm",
      "nodes": [
        {
          "kind": "task",
          "id": "confirm",
          "action": {
            "mode": "sync",
            "request": { "url": "http://service/confirm", "verb": "POST" }
          },
          "next": "wait-24h"
        },
        {
          "kind": "sleep",
          "id": "wait-24h",
          "durationMillis": 86400000,
          "next": "send-reminder"
        },
        {
          "kind": "task",
          "id": "send-reminder",
          "action": {
            "mode": "sync",
            "request": { "url": "http://service/remind", "verb": "POST" }
          }
        }
      ]
    },
    "payload": { "orderId": "ord-999" }
  }'

The execution status becomes SLEEPING while waiting. To skip the remaining wait and resume immediately:

curl -X POST http://localhost:8080/sagas/<executionId>/wake
# 202 Accepted — execution re-enqueued at the next node

Demo Scripts