STATEWAY

Documentation

Getting started

Point /v1 at https://api.stateway.dev with a Bearer token. Dynamo and Cosmos are optional bridges over the same partitions — use them only when an existing SDK already owns the client.

Access

Hosted Stateway is invite-only. There is no public signup. You should receive credentials from an operator:

export STATEWAY_ENDPOINT=https://api.stateway.dev
export STATEWAY_AKID=…
export STATEWAY_SECRET=…
export STATEWAY_TOKEN=$STATEWAY_SECRET   # Bearer for /v1
export COSMOS_MASTER_KEY=…   # if using Cosmos

Confirm with curl -sS https://api.stateway.dev/health — expect authentication: "required" and an apis.native entry.

Clients

Zero-dependency helpers for invitees who do not have the private repo. Same API as the in-repo packages.

JavaScript (ESM, Node 18+ or browsers with fetch):

import { Stateway } from "https://stateway.dev/client.js";

const sw = new Stateway({
  endpoint: "https://api.stateway.dev",
  token: process.env.STATEWAY_TOKEN,
});

await sw.createCollection({ name: "orders", hash: "pk", range: "sk" });
await sw.put("orders", { pk: "acme", sk: "o#1", total: 42.5 });
const { item, version } = await sw.get("orders", { pk: "acme", sk: "o#1" });

Python (stdlib only):

curl -fsS https://stateway.dev/client/stateway.py -o stateway.py

import os
from stateway import Stateway

sw = Stateway("https://api.stateway.dev", os.environ["STATEWAY_TOKEN"])
sw.create_collection("orders", hash="pk", range="sk")
sw.put("orders", {"pk": "acme", "sk": "o#1", "total": 42.5})
item = sw.get("orders", {"pk": "acme", "sk": "o#1"})["item"]

Helpers: waitForIndex / wait_for_index, queryAll / query_all, scanAll / scan_all, and stream fan-out.

Native API (/v1)

Preferred surface: plain JSON, no AWS or Azure SDK required. Same stored documents as the compatibility façades. Full endpoint table: repo docs/native-api.md.

curl -sS -X POST "$STATEWAY_ENDPOINT/v1/collections" \
  -H "Authorization: Bearer $STATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"orders","hash":"pk","range":"sk"}'

curl -sS -X PUT "$STATEWAY_ENDPOINT/v1/collections/orders/items" \
  -H "Authorization: Bearer $STATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pk":"acme","sk":"o#1","total":42.5}'

curl -sS -X POST "$STATEWAY_ENDPOINT/v1/collections/orders/get" \
  -H "Authorization: Bearer $STATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key":{"pk":"acme","sk":"o#1"}}'

curl -sS -X POST "$STATEWAY_ENDPOINT/v1/collections/orders/query" \
  -H "Authorization: Bearer $STATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key_condition":"pk = :pk AND begins_with(sk, :p)","values":{":pk":"acme",":p":"o"}}'

Also: TTL (attribute + default_ttl), streams, indexes (CREATINGACTIVE), if_version, projection / return values, parallel scan, batch, and single- or multi-collection transact.

Recipes

Optimistic concurrency. Get returns version. Pass it back as if_version on put / update / delete. Mismatch → 409 ConditionFailed.

const { item, version } = await sw.get("orders", { pk: "acme", sk: "o#1" });
await sw.update("orders", {
  key: { pk: "acme", sk: "o#1" },
  update: "SET total = :t",
  values: { ":t": item.total + 1 },
  if_version: version,
});

Index wait. Create returns status: "CREATING". Wait for ACTIVE before relying on a full backfill (queries during create can be incomplete).

await sw.createIndex("orders", { name: "byTag", hash: "tag", range: "sk" });
await sw.waitForIndex("orders", "byTag");
const page = await sw.queryIndex("orders", "byTag", {
  key_condition: "tag = :t",
  values: { ":t": "rush" },
});

Pagination. Query and scan return next. Pass it as after on the next call, or use queryAll / scanAll.

let after;
do {
  const page = await sw.query("orders", {
    key_condition: "pk = :pk",
    values: { ":pk": "acme" },
    limit: 50,
    after,
  });
  // …page.items
  after = page.next;
} while (after);

Expressions. key_condition, update, condition, and filter use DynamoDB expression syntax with plain JSON values (not AttributeValue maps). Alias attributes with names ({"#t":"total"}).

Streams. Enable with setStream, list partition ids via getStream, then poll streamRecords per partition (after is exclusive). Or use streamRecordsAll for one page across every partition.

Errors

Failures are JSON {"error":{"code":"…","message":"…"}}. Common codes:

Values on /v1 are plain JSON. Binary and typed Dynamo sets do not round-trip through native the way AttributeValue maps do — prefer strings, numbers, booleans, lists, and maps.

DynamoDB compatibility

Optional: use any AWS SDK against the same collections. Region in the SigV4 scope can be us-east-1; data is not regional.

import boto3
import os

ddb = boto3.client(
    "dynamodb",
    endpoint_url=os.environ["STATEWAY_ENDPOINT"],
    region_name="us-east-1",
    aws_access_key_id=os.environ["STATEWAY_AKID"],
    aws_secret_access_key=os.environ["STATEWAY_SECRET"],
)

ddb.create_table(
    TableName="Orders",
    AttributeDefinitions=[
        {"AttributeName": "customerId", "AttributeType": "S"},
        {"AttributeName": "orderId", "AttributeType": "S"},
    ],
    KeySchema=[
        {"AttributeName": "customerId", "KeyType": "HASH"},
        {"AttributeName": "orderId", "KeyType": "RANGE"},
    ],
    BillingMode="PAY_PER_REQUEST",
)
ddb.put_item(
    TableName="Orders",
    Item={
        "customerId": {"S": "acme"},
        "orderId": {"S": "o#1"},
        "total": {"N": "42.5"},
    },
)

Cosmos DB compatibility

import { CosmosClient } from "@azure/cosmos";

const client = new CosmosClient({
  endpoint: process.env.STATEWAY_ENDPOINT,
  key: process.env.COSMOS_MASTER_KEY, // base64 master key
});

const { database } = await client.databases.createIfNotExists({ id: "shop" });
const { container } = await database.containers.createIfNotExists({
  id: "orders",
  partitionKey: { paths: ["/customerId"] },
});
await container.items.create({
  id: "ord-1",
  customerId: "acme",
  total: 42.5,
});

The same document is readable via Dynamo as table shop/orders with keys customerId + id.

Health and latency

curl -sS https://api.stateway.dev/health | jq .

Expect status: "ok". The JSON includes latency_slo budgets (ms) for PutItem, GetItem, Query, and cross-partition TransactWriteItems.

Consistency (said plainly)

What is not implemented

Use AWS DynamoDB and Azure Cosmos documentation as the API reference for the façades. These are deliberately absent:

Item size follows Dynamo’s ~400 KB limit. A single partition has a stored-bytes ceiling (default 8 GiB) so a Durable Object stays under the platform limit — oversized writes fail with Dynamo ItemCollectionSizeLimitExceededException / Cosmos 413.

Need access?

Hosted Stateway is invite-only — there is no public signup. If you already have keys, start from Connect or the native examples above. For self-hosting, fork the engine and run your own Worker; the credential shape is the same.

Product overview and consistency model: stateway.dev.