It tells you what you may put in
variables without reading anyone’s prompt or calling GET /v1/copilots/{id} yourself.
Context vs. variables
Both feed data from your app into the agent’s system prompt, but they arrive there in completely different ways — and they are not interchangeable.…on the pro plan with 12 seats. Address them as "Maya" and reply in FR. — the placeholders are gone by the time the model sees the prompt. context, by contrast, is never templated: whatever strings you pass show up as their own section, unchanged.
Nested objects and dot paths
A prompt block can read a dot path —{{user.language}}, {{account.plan.tier}} — and you can fill it either way. Pass the whole object as it already exists in your app, or a dotted key; the API expands a dotted key into the nested form, so both spellings land in the same place:
Rules for variables (enforced by the API)
- Every dot-separated segment must be a plain identifier — letters, digits, underscores, starting with a letter or underscore.
- Values are a string, number, boolean,
null, an array of those, or an object of the same. - Paths go up to 3 segments deep —
{{a.b.c}}yes,{{a.b.c.d}}no — however you spell them. - An array is kept or dropped whole and renders as JSON if interpolated directly; its real use is
contains/indisplay conditions. - Up to 64 values (each leaf counts, however deeply nested), 2 KB per value, 16 KB total. A
nullleaf is “no value” — it’s not stored and doesn’t count, so an unfilled leaf costs you nothing. - Anything breaking those rules is dropped, not rejected — a display-only field must never fail a chat turn. So a typo’d key fails silently; check what your agent actually renders.
- A placeholder with no value and no default is left visible (
{{userName}}) rather than blanked, which makes a missing value obvious in the answer. - The
now.*namespace ({{now.datetime}},{{now.date}},{{now.iso}},{{now.timezone}}) is filled in server-side on every turn and cannot be overridden — a key whose first segment isnowis dropped either way it’s spelled, so don’t send your own clock.
plan in ['pro','enterprise'], user.language exists) that decide whether the block is included at all, evaluated against the same values.
Discovering which variables to send
You don’t have to read the prompt to know what an agent expects —getSidenetVariables() returns the {{placeholders}} the copilot’s agents actually declare, in the same nested shape you send back:
{{plan || 'free'}} → 'free'), or null when it has none — in which case a literal {{plan}} ends up in the prompt. The nulls are exactly what you have to fill in. Keys come back alphabetical at every level.
Because it’s the same shape, the object round-trips: fill in the leaves you have and send the whole thing back, no reshaping and no need to strip what you couldn’t fill.
flattenSidenetVariables():
path is itself a valid key to send ({ 'instance.currency': 'EUR' }), per the dot-path section above.
Return types
getSidenetVariables() returns SidenetDeclaredVariables — an object whose leaves are string | null and whose branches are more of the same:
flattenSidenetVariables(declared) returns SidenetVariablePath[]:
Notes:
- Served from the object cached during
initSidenet(), so it’s free after boot. It fetches once only if that init fetch failed. CallrefreshSidenetConfig()to pick up prompt edits made since. - Keys are alphabetical at every level.
- Throws if the SDK isn’t initialized, or if you initialized with only an
agentId/agentVersionId— with nocopilotIdthere’s no network to enumerate. - An empty object is a real answer: the agents use no placeholders, or they still run the legacy single prompt string, which isn’t templated.
- Sending a
nullleaf back is a no-op — not stored, not counted against the 64-value limit, not reported as dropped. A namespace whose leaves are allnullis dropped whole, so a wholesale{{instance}}stays visible rather than rendering{}. - A path that a prompt reads both wholesale (
{{user}}) and by sub-path ({{user.name}}) comes back only as the namespace — that combination isn’t fillable both ways, since{ user: 'x', 'user.name': 'y' }is rejected as a conflict. - Server builtins (
now.*) are excluded — you can’t supply those.
Both are updatable at runtime
Neithercontext nor variables is init-only. updateSidenetConfig() applies both to the next send — a response already streaming keeps the values its turn started with, and the conversation is not reset:
{} (or []) to clear.