Skip to main content
Meridian uses custom ed25519-signed tokens — smaller than JWT, constant-time verification.

Token format

base64url(msgpack(claims)) + "." + base64url(ed25519_signature)
Claims:
FieldTypeDescription
namespacestringThe namespace this token grants access to
client_idnumberUnique numeric ID for this client
expires_atnumberExpiry timestamp in milliseconds
permissionsobject{ read: string[], write: string[] } — glob patterns

Issue a token via HTTP

curl -X POST http://localhost:3000/v1/namespaces/my-room/tokens \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "client_id": 42, "ttl_ms": 3600000, "permissions": { "read": ["*"], "write": ["*"] } }'
Response:
{ "token": "eyJ..." }

Issue a token from the SDK

import { Effect } from "effect";
import { MeridianClient } from "meridian-sdk";

const client = await Effect.runPromise(MeridianClient.create({ ... }));

const result = await Effect.runPromise(
  client.http.issueToken("my-room", {
    client_id: 42,
    ttl_ms: 3_600_000,
    permissions: { read: ["*"], write: ["*"] },
  })
);

console.log(result.token);

Scoped permissions

Permissions use glob patterns matched against the CRDT key (crdt_id). This lets you issue tokens that only access specific keys — critical for multi-tenant namespaces where multiple users share the same namespace.
PatternMatches
"*"All keys (full access)
"gc:*"All GCounter keys
"or:cart-42"Only or:cart-42
"or:cart-*"All cart keys for any user
"lw:title"Only the lw:title key

Example: per-user scoped token

# User 42 can read everything but only write to their own cart
curl -X POST http://localhost:3000/v1/namespaces/shop/tokens \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": 42,
    "permissions": {
      "read": ["*"],
      "write": ["or:cart-42", "pr:room-*"]
    }
  }'

Example: read-only observer token

curl -X POST http://localhost:3000/v1/namespaces/shop/tokens \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": 99,
    "permissions": {
      "read": ["gc:views", "pn:votes"],
      "write": []
    }
  }'
A token with write: ["*"] can write to any key in the namespace. In multi-user namespaces, always scope write permissions to the keys the client actually owns.

Verify a token (SDK)

MeridianClient.create() automatically parses and validates the token before connecting:
import { Effect } from "effect";
import { MeridianClient, TokenExpiredError, TokenParseError } from "meridian-sdk";

await Effect.runPromise(
  MeridianClient.create(config).pipe(
    Effect.catchTag("TokenExpiredError", () => Effect.die("renew your token")),
    Effect.catchTag("TokenParseError", () => Effect.die("invalid token")),
  )
);