Getting Started

Get up and running with j17 in under five minutes.

Prerequisites

You'll need: - A j17 account (sign up at j17.dev) - An API key for your instance - curl or any HTTP client

1. Create your spec

Define your aggregate types and events in JSON:

{
  "aggregate_types": {
    "user": {
      "events": {
        "was_created": {
          "schema": {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "email": { "type": "string", "format": "email" }
            },
            "required": ["name", "email"]
          },
          "handler": [
            { "set": { "target": "", "value": "$.data" } },
            { "set": { "target": "created_at", "value": "$.metadata.timestamp" } }
          ]
        },
        "had_email_updated": {
          "schema": {
            "type": "object",
            "properties": {
              "email": { "type": "string", "format": "email" }
            },
            "required": ["email"]
          },
          "handler": [
            { "merge": { "target": "", "value": "$.data" } }
          ]
        }
      }
    }
  },
  "agent_types": ["user", "admin"]
}

The spec defines: - aggregate_types: Kinds of entities in your system - events: What can happen to those entities - handlers: How events transform state (using the Tick language) - agent_types: Who can trigger events

Upload via the dashboard or Admin API.

2. Post an event

curl -X POST https://myapp.j17.dev/user/550e8400-e29b-41d4-a716-446655440000/was_created \
  -H "Authorization: Bearer $J17_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": { "name": "Alice", "email": "alice@example.com" },
    "metadata": {
      "actor": { "type": "admin", "id": "550e8400-e29b-41d4-a716-446655440001" }
    }
  }'

Every event needs: - URL path: /{aggregate_type}/{aggregate_id}/{event_type} - data: The event payload (validated against your schema) - metadata.actor: Who performed the action

3. Query the aggregate

curl https://myapp.j17.dev/user/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $J17_API_KEY"

Response:

{
  "ok": true,
  "state": {
    "name": "Alice",
    "email": "alice@example.com",
    "created_at": 1705312800
  },
  "length": 1
}

That's it. You're event sourcing.

What just happened?

  1. You defined the shape of your data (the spec)
  2. You recorded a fact (the event)
  3. j17 computed current state by applying the handler

The event is stored forever. The aggregate is derived on demand. This is the core trade-off: you give up simple SQL queries, you get immutable history.

Next steps

Common gotchas

Events are immutable. There's no DELETE. To "undo," write a compensating event.

IDs matter. Use UUIDv4 for most things. For human-readable codes (booking references, promo codes), use 9-character humane codes.

Actors are required. Every event must include metadata.actor with type and id. This isn't bureaucracy -- it's your audit trail.

Can't find what you need? support@j17.app