Quickstart

From a running server to your first authenticated call.

1. Run the server

The server needs Redis (with the JSON and Search modules), an RSA keypair for signing tokens, and a public base URL:

export REDIS_URL=redis://localhost:6379
export PUBLIC_API_URL=http://localhost:4000
# Generate a signing keypair once and keep jwt_private.pem secret
snug jwt keygen --out-dir .
export JWT_PRIVATE_KEY="$(cat jwt_private.pem)"
export JWT_PUBLIC_KEY="$(cat jwt_public.pem)"

cargo run --bin snug-api --features full

The server listens on port 4000 by default. Verify it is up:

curl http://localhost:4000/health

2. Create an account

Signing up does two things: it creates your user, and it registers your personal tenant. Tenancy is a closed world - every token names a tenant in its azp claim, and a token for an unregistered tenant is rejected with 401 tenant is not registered before any handler runs. Signup, organization creation, and directory provisioning are what register tenants.

snug auth signup --email you@example.com --password '...' --username you
{
  "email": "you@example.com",
  "email_verified": false,
  "status": "account created. logged in"
}

The CLI stores your session in ~/.snug/credentials.json and refreshes it automatically. Your user ID doubles as your personal tenant ID:

snug --output json auth me
{
  "id": "q7Fk2PmXbTn4RsWvZc8L",
  "email": "you@example.com",
  "email_verified": false,
  "role": "user"
}

One gate remains: service APIs require a verified email, and an unverified session gets 403 EMAIL_NOT_VERIFIED. On a deployment with real email configured, click the link in the verification message - though note that a real provider may reject placeholder recipients like example.com outright, in which case no message is sent at all. On a local server with the default log email provider, nothing is delivered either. In both cases, skip the session and mint tokens directly, which is the natural local-dev path anyway:

3. Or mint tokens directly (local development)

When you run the server yourself you hold its signing key, so you can mint tokens for any subject without the signup ceremony - as long as the token's tenant is registered. Read your personal tenant ID from auth me and mint against it:

TENANT=$(snug --output json auth me | jq -r .id)
export SNUG_API_TOKEN=$(snug jwt generate user -u dev -t "$TENANT" --format compact)

Tokens minted this way have full access by default; add --scope 'kv:read ...' to restrict them. See Authentication for the whole model.

4. First calls

Store and read a value in the KV service, through the CLI:

snug kv set docs/hello '{"greeting": "hello world"}'
snug --output json kv get docs/hello
{
  "key": "docs/hello",
  "value": { "greeting": "hello world" },
  "content_type": "application/json",
  "updated_at": "2026-08-28T03:41:02.485104Z"
}

The same operation over raw HTTP - service endpoints live under /api/v1, and every response is wrapped in the standard envelope:

curl -H "Authorization: Bearer $SNUG_API_TOKEN" \
  http://localhost:4000/api/v1/kv/docs/hello
{
  "status": 200,
  "msg": "OK",
  "data": {
    "key": "docs/hello",
    "value": { "greeting": "hello world" },
    "content_type": "application/json",
    "updated_at": "2026-08-28T03:41:02.485104Z"
  }
}

The CLI unwraps that envelope for you; over HTTP you read data. Errors use the same shape with an error code and no data - the details are in Responses and errors.

Where to go next

On this page