Sharing Characters
Share your characters with other users via short-lived tokens (great for one-shot web links) or permanent links (great for physical carriers like NFC tags, QR codes, or print). Both kinds are created through the same unified API. The SDK exposes a SharesClient accessible via client.shares.
Overview
Every share is a row in a single shares table, distinguished only by the permanent flag at creation time:
- Temporary shares (
permanent=False) — Time-limited links with a redemption cap. Best for one-shot web links shared with a small group of recipients. - Permanent shares (
permanent=True) — Never-expiring links with no redemption cap. Best for physical carriers (NFC tags, QR codes, print). Protected by per-link rate limits and dashboard revocation.
Both kinds issue a short-lived session token (sst_...) on redemption.
Earlier versions of this API exposed two separate endpoint families ("share anchors" and "share tokens"). They were merged into a single unified API. If you have older integrations that called create_anchor, open_anchor, or create_share_token, switch to the methods documented below.
Authentication
client.shares is available immediately on EstuaryClient — no connect() required:
from estuary_sdk import EstuaryClient, EstuaryConfig
config = EstuaryConfig(
server_url="https://api.estuary-ai.com",
api_key="est_your_api_key",
character_id="char_abc",
player_id="user-123",
)
client = EstuaryClient(config)
# client.shares is ready to use without connecting
result = await client.shares.create_share(character_id="char_abc")
:::info Authentication requirements
Methods on SharesClient use one of three auth modes:
- Mixed auth (
est_API key OR Firebase ID token):create_share,revoke_share_token - Unauthenticated:
open_share,exchange_share_token - Firebase ID token only:
list_character_shares,bulk_revoke_by_api_key
Methods that require Firebase will raise EstuaryError (HTTP 401) when called with an est_ API key.
When using mixed-auth methods with an API key, the SDK injects an Authorization: Bearer {api_key} header automatically — you do not need to do anything special.
:::
Creating a Temporary Share
Create a time-limited share with a redemption cap:
result = await client.shares.create_share(
character_id="char_abc",
permanent=False, # default
ttl="7d", # one of "24h", "7d", "30d", "90d"
memory_sharing="isolated", # "isolated" or "shared"
max_exchanges=5, # 1-100
max_interactions=None, # optional per-session interaction cap
)
print(result["token"]) # the share token string
print(result["shareUrl"]) # web link recipients can open
print(result["expiresAt"]) # ISO timestamp when the share expires
| Parameter | Type | Default | Description |
|---|---|---|---|
character_id | str | required | The character being shared |
permanent | bool | False | When True, creates a never-expiring permanent share (see below) |
ttl | str | "7d" | Lifetime: "24h", "7d", "30d", or "90d". Ignored when permanent=True. |
memory_sharing | str | "isolated" | "isolated" (each recipient has private memory) or "shared" (all recipients share the owner's memory) |
max_exchanges | int | 5 | Max number of recipients (1-100). Ignored when permanent=True. |
max_interactions | int | None | None | Optional per-session interaction cap |
Creating a Permanent Share
Permanent shares are designed for physical carriers. Create one by passing permanent=True:
result = await client.shares.create_share(
character_id="char_abc",
permanent=True,
memory_sharing="isolated",
max_interactions=None, # optional per-session interaction cap
)
print(result["token"]) # the share token string
print(result["shareUrl"]) # standard web link
print(result["anchorUrl"]) # short URL for NFC/QR (https://share.estuary-ai.com/sa/{id})
print(result["expiresAt"]) # None — permanent shares never expire
The returned anchorUrl is short and stable, so it's safe to burn onto an NFC tag, encode in a QR code, or print.
Permanent shares count against a per-user cap. Exceeding the cap raises EstuaryError with HTTP 429.
Redeeming a Share
When a recipient opens a share, your application redeems it for a short-lived session token. The SDK provides two redemption methods that mirror the two kinds of share:
open_share(share_id)— for permanent shares; takes the share row id.exchange_share_token(token, recipient_id=...)— for temporary shares; takes the token string and a recipient identifier.
Both endpoints are unauthenticated and return the same shape.
Opening a Permanent Share
redeemed = await client.shares.open_share(share_id="abc123...")
session_token = redeemed["sessionToken"] # sst_...
character_id = redeemed["characterId"]
player_id = redeemed["playerId"]
server_url = redeemed["serverUrl"]
character = redeemed["character"] # id, name, tagline, avatar, modelUrl, ...
open_share is rate limited per share id. Exceeding the rate limit raises EstuaryError with HTTP 429.
Exchanging a Temporary Share Token
exchanged = await client.shares.exchange_share_token(
token="...", # the token string returned by create_share
recipient_id="visitor-local-id-123", # required for isolated shares
)
session_token = exchanged["sessionToken"]
character_id = exchanged["characterId"]
player_id = exchanged["playerId"]
server_url = exchanged["serverUrl"]
character = exchanged["character"]
| Parameter | Type | Description |
|---|---|---|
token | str | The share token string returned by create_share |
recipient_id | str | None | Client-provided recipient identifier. Required for isolated shares; optional for shared mode. |
The redeemed sessionToken is currently consumed by Estuary's web client and dashboard, where it is exchanged for an authenticated session against the gateway. The Python SDK's EstuaryConfig accepts an api_key (your est_... developer key) — it does not yet take a sessionToken directly. If you need to drive a redeemed share session from Python, treat the redeemed payload as opaque metadata for your app's own routing, and have the recipient connect via the standard SDK flow under your own API key.
Listing Shares for a Character
List all active (non-revoked, non-expired) shares for a character. Both permanent and temporary shares are returned:
result = await client.shares.list_character_shares(character_id="char_abc")
for share in result["shares"]:
print(share["id"], share["permanent"], share["expiresAt"])
:::caution Firebase only
This endpoint requires a Firebase ID token. Calling it with only an est_ API key raises EstuaryError (HTTP 401).
:::
Revoking a Share
Revoke a single share by its database row id (not the token string). This works for both permanent and temporary shares: the row is soft-deleted and any sessions it minted are killed.
result = await client.shares.revoke_share_token(share_token_id="...")
print(result["revoked"]) # True
print(result["killedSessions"]) # number of live sessions terminated
print(result["removedSessionBlobs"]) # number of Redis session blobs cleaned up
| Auth mode | Scope |
|---|---|
est_ API key | May only revoke shares created via the same API key |
| Firebase ID token | May revoke any share owned by the caller |
Bulk Revoke by API Key
Revoke every share that was created via a particular API key. The primary use case is recovering from a compromised device — revoke all shares minted by that device's key in one call.
result = await client.shares.bulk_revoke_by_api_key(api_key_id="...")
print(result["revoked"]) # number of shares revoked
:::caution Firebase only
This endpoint requires a Firebase ID token. Calling it with only an est_ API key raises EstuaryError (HTTP 401).
:::
Temporary vs Permanent
| Property | Temporary share | Permanent share |
|---|---|---|
permanent flag | False (default) | True |
| Intended use | One-shot web links | NFC tags, QR codes, print |
| Lifetime | Bounded by ttl | Never expires |
| Redemptions | Capped by max_exchanges | Unlimited (rate limited) |
| Redemption method | exchange_share_token(token, recipient_id) | open_share(share_id) |
| Reachable with API key | create_share, exchange_share_token, revoke_share_token (own shares only) | create_share, open_share, revoke_share_token (own shares only) |