Shares API
Mint shareable links to your characters. Two flavors:
- Expiring shares -- time-limited, optionally exchange-capped. Good for product trials, link sharing, embedded widgets.
- Permanent shares -- never expire. Designed for physical carriers (NFC tags, QR codes, printed cards). Defended by per-link rate limits and dashboard revocation.
Base path: /api/v1/share
Authentication
The Shares API supports two auth modes depending on the endpoint:
| Auth | How to send | Notes |
|---|---|---|
| API key | Authorization: Bearer est_... | Used for create. Stamps created_via_api_key_id for forensics. |
| Firebase ID token | Authorization: Bearer <firebase-id-token> | Used for list, revoke, and bulk-revoke. |
The POST /api/v1/share/{id}/open endpoint is unauthenticated -- the share_id itself is the credential, protected by rate limits.
Create a Share
POST /api/v1/share
Mint a new share, either expiring (default) or permanent.
Request Body
{
"characterId": "550e8400-e29b-41d4-a716-446655440000",
"permanent": false,
"ttl": 3600,
"memorySharing": "isolated",
"maxExchanges": 100,
"maxInteractions": 50
}
| Field | Type | Required | Description |
|---|---|---|---|
characterId | string | Yes | UUID of the character to share. Must be owned by the caller. |
permanent | boolean | No | When true, mints a permanent share. ttl and maxExchanges are ignored. Default: false. |
ttl | integer | No | Lifetime in seconds for an expiring share. |
memorySharing | string | No | "isolated" (default) or "shared". Controls whether shared sessions write to the owner's memory. |
maxExchanges | integer | No | Cap on total redeemers (expiring shares only). |
maxInteractions | integer | No | Cap on total interactions (chat turns) across all redeemers. |
Response: Permanent
{
"token": "<share id>",
"shareUrl": "https://share.estuary-ai.com/s/<share id>",
"anchorUrl": "https://share.estuary-ai.com/sa/<share id>",
"expiresAt": null
}
The anchorUrl uses the short sa/ path -- this minimizes NDEF bytes when encoded onto NTAG213 NFC tags (144 byte user memory).
Response: Expiring
{
"token": "<share token>",
"shareUrl": "https://share.estuary-ai.com/s/<share token>",
"expiresAt": "2026-05-09T18:00:00Z"
}
Errors
| Status | Cause |
|---|---|
404 | Character does not exist or is not owned by the caller |
429 | Permanent share limit reached (MAX_ACTIVE_PERMANENT_PER_USER = 200) |
Examples
# Permanent share (NFC / QR / print)
curl -X POST https://api.estuary-ai.com/api/v1/share \
-H "Authorization: Bearer est_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"characterId": "550e8400-e29b-41d4-a716-446655440000",
"permanent": true
}'
# Expiring share (1-hour link)
curl -X POST https://api.estuary-ai.com/api/v1/share \
-H "Authorization: Bearer est_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"characterId": "550e8400-e29b-41d4-a716-446655440000",
"ttl": 3600,
"maxInteractions": 50
}'
Open a Permanent Share
POST /api/v1/share/{share_id}/open
Mint a fresh, short-lived session token for a permanent share. This endpoint is unauthenticated -- the share_id is the credential. Each call increments the share's open_count and updates last_opened_at.
:::caution Rate limits
This endpoint is rate-limited per share_id to prevent quota drain:
- 10 requests per minute
- 100 requests per hour
- 500 requests per day
Excess requests return 429 Too Many Requests with the offending bucket in the detail field (e.g., "Rate limit (min)").
:::
Response
{
"sessionToken": "sst_a1b2c3d4...",
"characterId": "550e8400-e29b-41d4-a716-446655440000",
"playerId": "player_abc123",
"serverUrl": "https://api.estuary-ai.com",
"character": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Sage",
"tagline": "A wise mentor",
"avatar": "https://...",
"modelUrl": "https://.../final.glb",
"modelPreviewUrl": "https://.../preview.glb",
"modelStatus": "ready",
"sourceImageUrl": "https://.../source.jpg"
}
}
| Field | Type | Description |
|---|---|---|
sessionToken | string | Opaque sst_<32-char> token. Use as the api_key field in the SDK auth payload. 1-hour TTL. |
characterId | string | Character to connect to. Pass as character_id to the SDK. |
playerId | string | Player ID to use for this session. |
serverUrl | string | Estuary server URL the SDK should connect to. |
character | object | Public character metadata (name, avatar, 3D model URLs). |
Errors
| Status | Cause |
|---|---|
404 | Share not found, revoked, or not a permanent share |
429 | Rate limit exceeded |
:::info Compatible with SDK auth
sessionToken (the sst_<32-char> string) is interchangeable with an API key in the SDK auth payload. SDKs do not need any special code path to consume share-issued tokens.
:::
Example
curl -X POST https://api.estuary-ai.com/api/v1/share/abc123/open
List Shares for a Character
GET /api/v1/share/character/{character_id}
List all non-revoked shares (permanent and expiring) for a character. Firebase ID token only -- API keys cannot list shares.
Response
{
"shares": [
{
"id": "abc123",
"characterId": "550e8400-e29b-41d4-a716-446655440000",
"createdAt": "2026-05-01T10:00:00Z",
"expiresAt": null,
"memorySharing": "isolated",
"maxExchanges": null,
"maxInteractions": null,
"openCount": 12,
"lastOpenedAt": "2026-05-08T12:34:00Z",
"shareUrl": "https://share.estuary-ai.com/s/abc123",
"anchorUrl": "https://share.estuary-ai.com/sa/abc123"
}
]
}
Revoke a Share
DELETE /api/v1/share/{share_id}
Revoke a share (permanent or expiring). Soft-deletes the row, deletes Redis forward maps, and disconnects active sessions across all server pods. Firebase ID token only.
Response
{
"revoked": true,
"killedSessions": 2,
"removedSessionBlobs": 2
}
| Field | Type | Description |
|---|---|---|
revoked | boolean | Always true on success |
killedSessions | number | Number of active SDK sessions that were disconnected |
removedSessionBlobs | number | Number of session token blobs invalidated in cache |
Bulk Revoke by API Key
DELETE /api/v1/share/by-api-key/{api_key_id}
Revoke every share that was created via a specific API key. Useful for rotating credentials or recovering from a compromised device. Firebase ID token only.
The endpoint revokes shares matching:
created_via_api_key_id = api_key_idowner_user_id = callerrevoked_at IS NULL
Each share is run through the full kill-switch (DB soft-delete, Redis cleanup, cross-pod session kill).
Response
{
"revoked": 7,
"killedSessions": 12,
"removedSessionBlobs": 12
}
Notes
- Permanent shares have
expires_at = NULLandmax_exchanges = NULL. - Permanent share IDs are either 32-char base64url strings (legacy) or 36-char UUIDs (new).
- The Estuary backend caps each user at 200 active permanent shares.
- Session tokens issued by
/openlast 1 hour. After expiry, the SDK must reconnect by calling/openagain.