Sharing Characters
Share your characters with other users via long-lived anchors (for physical carriers like NFC tags, QR codes, or print) or short-lived tokens (for one-shot web links). The SDK provides a SharesClient accessible via client.shares.
Overview
Estuary supports two sharing models:
- Share anchors — Long-lived, owner-scoped redirectors that mint a fresh short-lived session token on every redemption. Intended for physical carriers (NFC tags, QR codes, print). Unlimited redemptions by default; protected by per-anchor rate limits and dashboard revocation.
- Share tokens — Short-lived one-shot links with built-in redemption caps. Intended for web links.
Both models issue a short-lived session token (sst_...) that a recipient can use to connect to the character through any Estuary SDK.
Authentication
The SharesClient is available immediately on EstuaryClient (no connect() required):
from estuary_sdk import EstuaryClient, EstuaryConfig
config = EstuaryConfig(api_key="est_...")
client = EstuaryClient(config)
# client.shares is ready to use
result = await client.shares.create_anchor(character_id="char_abc")
Most SharesClient methods require a Firebase ID token on the backend. Only the three methods below are reachable with a standard est_ API key:
create_anchor— create a long-lived anchor for a character you ownopen_anchor— redeem an anchor to get a session token (unauthenticated)exchange_share_token— redeem a share token to get a session token (unauthenticated)
All other methods (list, revoke, create share token) will raise EstuaryError with HTTP 401 if called with API-key-only auth.
Creating an Anchor
Create a long-lived anchor for a character you own. The returned anchorUrl is stable and safe to burn onto an NFC tag, encode in a QR code, or print.
result = await client.shares.create_anchor(
character_id="char_abc",
memory_sharing="isolated", # "isolated" or "shared"
max_interactions=None, # None = no cap
)
print(result["id"]) # 32-char url-safe anchor id
print(result["anchorUrl"]) # https://share.estuary-ai.com/sa/{id}
| Parameter | Type | Default | Description |
|---|---|---|---|
character_id | str | required | The character being shared |
memory_sharing | str | "isolated" | "isolated" (each visitor has private memory) or "shared" (all visitors share the owner's memory) |
max_interactions | int | None | None | Optional per-session interaction cap |
Creating an anchor counts against a per-user cap. If you exceed the cap, the call raises EstuaryError with HTTP 429.
Redeeming an Anchor
When a visitor scans or taps an anchor URL, the client resolves it into a fresh session token by calling open_anchor. This endpoint is unauthenticated and rate limited per anchor.
redeemed = await client.shares.open_anchor(anchor_id="abc123...")
session_token = redeemed["sessionToken"] # sst_...
character_id = redeemed["characterId"]
player_id = redeemed["playerId"]
character = redeemed["character"] # id, name, tagline, avatar, modelUrl, ...
The returned sessionToken is a short-lived (~1 hour) token you can pass to EstuaryConfig to connect as the visitor:
visitor_config = EstuaryConfig(session_token=redeemed["sessionToken"])
async with EstuaryClient(visitor_config) as visitor:
await visitor.connect()
await visitor.send_text("Hello!")
Exchanging a Share Token
For short-lived one-shot web links, use exchange_share_token instead. This endpoint is also unauthenticated.
exchanged = await client.shares.exchange_share_token(
token="shr_...",
recipient_id="visitor-local-id-123", # required for isolated shares
)
session_token = exchanged["sessionToken"]
| Parameter | Type | Description |
|---|---|---|
token | str | The share token string |
recipient_id | str | None | Client-provided recipient identifier. Required for isolated shares; may be omitted for shared mode. |
Unlike open_anchor, the exchange_share_token response does not include a top-level memorySharing field.
Share Management (Firebase Auth)
The following methods require a Firebase ID token on the backend. They exist for API completeness but will raise EstuaryError(401) with API-key-only auth.
| Method | Description |
|---|---|
list_anchors(character_id=None) | List non-revoked anchors owned by the caller, optionally filtered by character |
revoke_anchor(anchor_id) | Soft-delete an anchor and kill all active sessions that depended on it |
bulk_revoke_anchors_by_api_key(api_key_id) | Revoke every anchor minted by a given API key. Primary use case: recovering from a compromised device |
create_share_token(character_id, ttl, memory_sharing, max_exchanges) | Create a short-lived share token (ttl one of "24h" | "7d" | "30d" | "90d") |
list_character_shares(character_id) | List active share tokens for a character |
revoke_share_token(share_token_id) | Revoke a share token by its database row id |
Anchors vs Tokens
| Property | Share anchor | Share token |
|---|---|---|
| Intended use | NFC tags, QR codes, print | One-shot web links |
| Lifetime | Long-lived | Short-lived (TTL) |
| Redemptions | Unlimited (rate limited) | Capped by max_exchanges |
| Revocable from dashboard | Yes | Yes |
| Reachable with API key | create_anchor, open_anchor | exchange_share_token only |
For long-lived, transport-neutral redirectors, use share anchors. For short-lived one-shot links, use share tokens.