API Overview
j17's API is HTTP-first and minimal. POST events, GET aggregates. That's the core. Everything else -- batching, admin, implications -- is built on these primitives.
Base URLs
Production: https://{instance}.j17.dev
Staging: https://{instance}-staging.j17.dev
Test: https://{instance}-test.j17.dev
All requests require HTTPS.
Authentication
Two methods depending on what you're doing:
| Method | Header | Use for |
|---|---|---|
| API Key | Authorization: Bearer {key} |
Instance data (events, aggregates) |
| JWT | Authorization: Bearer {token} |
Admin operations, dashboards |
See Authentication for details.
Core operations
Write an event
POST /{aggregate_type}/{aggregate_id}/{event_type}
curl -X POST https://myapp.j17.dev/order/abc123/was_placed \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": { "items": [...], "total": 59.99 },
"metadata": {
"actor": { "type": "user", "id": "550e8400-e29b-41d4-a716-446655440000" }
}
}'
Returns the stream ID of the written event.
Read an aggregate
GET /{aggregate_type}/{aggregate_id}
curl https://myapp.j17.dev/order/abc123 \
-H "Authorization: Bearer $API_KEY"
Returns current state derived from all events.
Batch operations
POST /{aggregate_type}/{aggregate_id}
Atomic multi-event writes to a single aggregate. See Batch operations.
Request format
Event POST body
{
"data": { ... }, // Required. Validated against event schema
"metadata": { // Required
"actor": { // Required. Who performed the action
"type": "user", // Must be in spec's agent_types
"id": "550e8400-e29b-41d4-a716-446655440000"
},
"target": { // Optional. What was affected
"type": "order",
"id": "target-id"
},
"previous_length": 5 // Optional. For OCC
}
}
Idempotency is handled via the X-Idempotency-Key request header, not in metadata. See Writing events for details.
Content-Type
Always application/json. j17 rejects other content types with 400.
Response format
Success (201 Created) -- single event
{
"ok": true,
"stream_id": "1234567890123-0",
"implied_count": 2
}
| Field | Type | Description |
|---|---|---|
ok |
boolean | Always true on success |
stream_id |
string | Redis stream ID of the written event |
implied_count |
integer | Number of implied events (omitted if 0) |
Success (201 Created) -- batch
{
"ok": true,
"stream_ids": ["1234567890123-0", "1234567890123-1"],
"count": 2,
"implied_count": 3
}
Error (4xx/5xx)
{
"ok": false,
"error": "Event data failed schema validation",
"path": "data.email"
}
Common error codes:
| HTTP | Meaning |
|---|---|
| 400 | Validation error (bad schema, missing fields) |
| 401 | Invalid or missing API key |
| 403 | Valid auth, but not allowed (wrong environment, read key on write) |
| 404 | Aggregate type or event type not in spec |
| 409 | OCC conflict (stale previous_length) |
| 413 | Event data too large |
| 422 | Idempotency key reused with different body |
| 429 | Rate limited |
| 500 | Internal error |
Query parameters
GET /{type}/{id}
| Parameter | Type | Description |
|---|---|---|
synchronous |
boolean | Bypass cache, compute fresh (?synchronous=true) |
GET /{type}/{id}/events
| Parameter | Type | Description |
|---|---|---|
start |
string | Start from this stream ID (exclusive) |
count |
integer | Max events to return (default 100) |
Rate limits
| Scope | Limit |
|---|---|
| Per API key | 500 requests/minute |
| Per IP | 2,000 requests/minute |
Rate limit headers on every response:
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 499
X-RateLimit-Scope: api-key
Exceeding limits returns 429.
Idempotency
POST events support idempotency via the X-Idempotency-Key request header:
curl -X POST https://myapp.j17.dev/order/abc123/was_placed \
-H "X-Idempotency-Key: order-123-was_placed-20240101" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {...}, "metadata": {"actor": {...}}}'
Same key + same body = cached response replayed (with X-Idempotency-Replayed: true header).
Same key + different body = 422 error.
Keys expire after 24 hours.
CORS
j17 supports cross-origin requests from browser clients:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Webhooks (Listeners)
j17 delivers events to your HTTP endpoints via listeners defined in your spec. When a matching event is written, j17 POSTs a signed JSON payload to your URL.
Delivery is guaranteed with retry: failed deliveries are retried with exponential backoff (5s, 25s, 125s) up to 3 attempts. Payloads are signed with HMAC-SHA256 using your listener secret, delivered in the X-J17-Signature header.
Admin API
Separate endpoints for instance management, accessible via the headnode:
POST /api/instances/:id/spec # Deploy spec
GET /api/instances/:id/keys # List API keys
POST /api/instances/:id/keys # Create API key
See Admin API for details.
Testing
Use staging or test environments:
# Staging
curl https://myapp-staging.j17.dev/order/abc123/was_placed ...
# Production
curl https://myapp.j17.dev/order/abc123/was_placed ...
Different API keys, isolated data. Test freely in staging.
See also
- Writing events - Deep dive on POST
- Reading aggregates - Deep dive on GET
- Authentication - Keys, JWT, environments
- Batch operations - Atomic multi-event writes
- Admin API - Instance management