REST, rich results, and moderation
Use EstuaryHttpClient for character management, conversation history, memories,
shares, and text-only turns over HTTP. It uses the same EstuaryConfig as your
live character connection. Run its methods with StartCoroutine.
Authentication and errors
using Estuary;
using Estuary.Models;
using UnityEngine;
public class CharacterHistory : MonoBehaviour
{
[SerializeField] private EstuaryConfig config;
[SerializeField] private string characterId;
[SerializeField] private string playerId;
void Start()
{
var http = new EstuaryHttpClient(config) { PlayerId = playerId };
StartCoroutine(http.GetMessages(characterId, playerId,
page => Debug.Log($"Loaded {page.Messages.Length} messages"),
error => Debug.LogError(error), limit: 50, page: 1));
}
}
Configure an API key or config.TokenProvider for Firebase authentication. If a
token provider fails, the request fails without falling back to the API key.
http.PlayerId sends X-Player-Id; http.OrgId sends X-Org-Id. The SDK also sends
its name and version on API requests.
The error callback includes the HTTP status and server response body. Missing API-key scopes remain visible in the response detail. The SDK does not retry rejected mutations or rate-limited turns automatically.
Available methods
Each method takes success and error callbacks; optional filters follow them.
Response models live in Estuary.Models. Character writes take a JObject with
the character API schema, including nested persona,
llm, tts, and stt sections. A character's display name is persona.name on
writes and AgentResponse.Name on reads.
| Area | Methods |
|---|---|
| Characters | ListCharacters, GetCharacter, CreateCharacter, UpdateCharacter, DeleteAgent, TransferCharacter |
| Private motives | GetCharacterMotive, SetCharacterMotive, ResetCharacterMotive |
| Tool configuration | TestApiEndpoint |
| Images/models | UploadImageToCharacter, GenerateModel, GetModelStatus, PollModelStatus, UploadAvatar, UploadModel, DownloadGlb |
| Conversations | ListConversations, GetConversationStats, GetConversation, GetMessages, DeleteConversation |
| Memories | GetMemories, GetMemoryTimeline, GetMemoryStats, GetCoreFacts, GetMemoryGraph, SearchMemories, CreateMemory, UpdateMemory, DeleteMemory, DeleteAllMemories |
| Shares | CreateShare, OpenShare, ExchangeShare, ListShares, RevokeShare, RevokeSharesByApiKey |
| HTTP turns | Turn, StreamTurn |
ListCharacters is paginated; GetAgents preserves its older unpaginated list
behavior. DeleteConversation removes messages and memories for the pair.
DeleteAllMemories requires an explicit confirm: true. Memory search uses POST
but only requires memories:read.
CreateShare supports a permanent share or a TTL of 24h, 7d, 30d, or 90d.
OpenShare and ExchangeShare redeem links anonymously, without sending your
API key, Firebase token, player header, or organization header. For isolated
shares, pass a stable recipientId to ExchangeShare. Use the returned
ShareSession.SessionToken as the API key with its CharacterId, PlayerId, and
ServerUrl. Listing and revoking shares requires a Firebase TokenProvider.
Streaming HTTP turns
HTTP turns return text and actions without starting a voice connection. Reuse a
playerId to preserve that player's relationship and history.
var http = new EstuaryHttpClient(config);
StartCoroutine(http.StreamTurn(characterId,
new CharacterTurnRequest { Message = "Hello", PlayerId = playerId },
evt =>
{
if (evt.Type == "text_delta") Debug.Log((string)evt.Data["text"]);
if (evt.Type == "api_endpoint_result")
{
var result = evt.Data.ToObject<ApiEndpointResult>();
Debug.Log($"Received {result.Media.Length} media items");
}
},
reply => Debug.Log(reply.Text),
error => Debug.LogError(error)));
CharacterTurnEvent preserves the event type and JSON data. Events arrive in
order; onCompleted follows turn_completed. An incomplete or malformed stream
calls onError. Turn uses the same request model for a single complete response.
Set Remember = false to skip long-term memory extraction for a turn.
Rich results and moderation
These events are available on EstuaryClient, EstuaryManager, and
EstuaryCharacter, and run on Unity's main thread:
| Event | Payload and behavior |
|---|---|
OnDelegationUpdate | DelegationUpdate: task progress, invocation/message IDs, results, errors, optional AuthorizationUrl |
OnApiEndpointResult | ApiEndpointResult: tool call ID, operation, status, message ID, Media and Citations |
OnModerationWarning | ModerationWarning: Level, Message, and IsTerminated |
OnModerationFlag | ModerationFlag: MessageId, replacement Message, Action, and Categories |
OnServerError | ServerError: machine-readable Code and human-readable Message; OnError remains available |
Rich media and citations are separate from character prose. The SDK passes URLs as data. Your app decides whether to show an authorization link; validate remote media URLs and fetch them without attaching Estuary credentials.
On termination, the SDK stops local audio and microphone capture and suppresses
automatic reconnection. Resume with an explicit Connect after user intent.
For a redaction, it stops matching audio, rejects late content for that message,
and updates the current partial response only if the ID matches. Your UI must
replace historical text and remove media associated with the flagged message ID.
The SDK's BasicChatDemo includes an example of text replacement by ID.
Rigged models and imported clips
Call GenerateModel(characterId, onSuccess, onError, rigged: true) to request a
rigged humanoid. Polling exposes the posing, rig checking, rigging and animation
stages. ModelStatusResponse.Rigged and Animations describe the result.
rig_failed or animation_failed with a usable ModelUrl is a partial success:
you can still display the static model.
With glTFast installed, add EstuaryClipPlayer beside EstuaryCharacter and
EstuaryModelLoader. It binds the loaded model's skeletal clips to typed actions:
an exact name wins, otherwise a unique colon-delimited suffix is accepted
(wave matches preset:biped:wave). Ambiguous or absent clips emit
OnClipNotFound. Idle, walk and run loop; other actions return to idle.
Use SetModel(GameObject) for a custom importer and PlayAction(string) for
explicit playback. Audio2Face is not implemented by this adapter.
Audio completion and LiveKit state
For WebSocket voice, BotVoice.IsFinal marks the end of server audio, including an
empty terminator. EstuaryAudioSource waits until those samples have rendered
locally, then emits OnMessagePlaybackComplete(messageId) and acknowledges the
same ID to the server. Buffer underruns do not count as completion. If you feed
raw PCM with EnqueueAudio(byte[], sampleRate, messageId), call
MarkAudioComplete(messageId) after the final chunk.
OnBotSpeakingStateChanged on the manager and character exposes BotSpeakingState
with State, IsSpeaking, MessageId, and ParticipantIdentity. Advanced
integrations can read CurrentBotSpeakingState through the optional
ILiveKitBotStateSource interface. Older servers may supply an empty message ID.
These attributes describe server speaking state; use your local audio playback
clock for precise animation timing.