JSON payload schema
Developer
The structure returned when format is json. Same assembly as markdown, encoded for programmatic consumers.
NOTE
The schema is versioned 0.1-beta. Read schemaVersion if you parse it in production — it becomes additive-only once stable.
Top level
One assembled record, from Entity.load or Get Records.
| Key | Type | Always? | What it is |
|---|---|---|---|
schemaVersion | string | yes | "0.1-beta" |
entity | string | yes | The entity’s API name |
entityLabel | string | yes | The entity’s label — "Account 360". null when the object has no entity and the payload came from fallbacks |
object | string | yes | The object API name |
objectLabel | string | yes | The object’s label — "Account", or "Widget" where Widget__c is named differently |
recordId | string | yes | The record assembled |
level | string | yes | list · standard · detailed |
instruction | string | if set | The entity-level instruction |
found | boolean | only when false | Present and false when the record wasn’t found or wasn’t visible. A payload carrying it has no record sections |
fields | array | when found | Curated fields. Present whenever the record is, even as [] |
metrics | array | if any | Metric and formula values |
enrichment | array | if any | Enrichment output, grouped by element |
relatedLists | array | if any | Child record tables |
references | array | if any | Embedded parent records |
notes | array of string | if any | Assembly warnings |
computedContext | object | if on | The standing context — see below. Off when the org switch is |
briefs | array | if any | The briefs the domain injects. Only on a call that names a domain |
info | array of string | if any | The Info lines the domain carries, one summary each. Only on a call that names a domain. In the catalog this is an array of objects instead, one per Info, each carrying its summary |
markdown | string | when format is both | The same assembly rendered as markdown, inside the same envelope |
An empty section is left out: an entity with no metrics carries no metrics key. A key can also be present and null, so test for a value rather than for the key.
computedContext, briefs and info describe the caller’s situation rather than the record. A record set carries them once for the whole set.
fields
{ "path": "Industry", "label": "Industry", "type": "picklist", "value": "Media",
"instruction": "Verticals, not SIC codes." }
| Key | Notes |
|---|---|
path | Field API name. Cross-object fields use dot notation — Owner.Name |
label | What the agent should call the field — the entity’s own label where one is declared, otherwise the Salesforce label. Absent for cross-object paths. path always carries the API name, which is what filters and writes accept |
type | Salesforce field type, lowercased. Absent for cross-object paths |
value | Typed — numbers as numbers, booleans as booleans, null when empty. Never null for a field the caller can’t read |
instruction | Present only when one is set |
| absent | A field the caller can’t read is absent from the array entirely, with no marker. So is a field the entity doesn’t curate at this level — the payload doesn’t say which → Security |
Order is the curated picker order — the same order markdown renders in. → Order
metrics
{ "label": "Open Pipeline", "value": 40000,
"instruction": "Open, unweighted. Excludes closed-lost." }
Metrics and formulas both land here, each a labeled computed value. A failed element drops out of the array; the failure appears on Issues, and in notes.
enrichment
Grouped by element, because one source can return several items:
[{ "group": "Engagement Signals",
"items": [
{ "label": "Last Activity", "value": "2026-07-14" },
{ "label": "Open Tasks", "value": 0 },
{ "label": "Stage History", "markdown": "- Prospecting → Discovery (12 days)" },
{ "label": "Stalled Deals", "object": "Opportunity", "objectLabel": "Opportunity",
"columns": ["Name", "Amount", "StageName"], "truncated": false,
"records": [{ "Id": "006...", "Name": "Renewal", "Amount": 25000, "StageName": "Proposal" }] }
]}]
Three item shapes, distinguished by which key is present:
| Shape | Key |
|---|---|
| Value | value |
| Block | markdown — pre-rendered |
| Records | records, with object, objectLabel, columns, truncated |
relatedLists
{ "label": "Open Opportunities", "object": "Opportunity", "objectLabel": "Opportunity",
"columns": ["Name", "Amount", "StageName", "CloseDate"],
"truncated": false,
"records": [
{ "Id": "006...", "Name": "Q3 Expansion", "Amount": 25000,
"StageName": "Proposal", "CloseDate": "2026-09-30" }
]}
| Key | Notes |
|---|---|
label | The list’s label on the entity |
object / objectLabel | The child object’s API name and label |
columns | The child entity’s curated list fields |
records | Every row carries Id plus those columns |
truncated | true when more rows matched than the list’s max-rows cap — the list is a sample, not the whole set |
references
An embedded parent record, using the same shape as the top level:
{ "label": "Account", "viaField": "AccountId",
"object": "Account", "objectLabel": "Account", "recordId": "001...", "level": "standard",
"fields": [ ... ], "metrics": [ ... ] }
viaField names the lookup ContextWorks followed to reach it. References nest one level only: an embedded record never carries its own references.
A curated reference with an empty lookup still appears:
{ "label": "Parent Account", "viaField": "ParentId", "missing": true }
missing: true means there is no parent record — the lookup is empty, not unfetched — so don’t query again for one.
notes
One string per warning: an element used where it doesn’t apply, a relationship that couldn’t resolve. A payload that carries notes every time usually has a configuration issue, and Issues aggregates them.
computedContext
The standing context, as an object: the same facts the markdown header line carries. Its org switch sits on the Behavior settings page. → Settings
| Key | What it is |
|---|---|
today | Today’s date, in the caller’s time zone |
now | Assembly time, which doubles as the data as-of time |
timeZone | The caller’s time zone ID |
dayOfWeek | Today’s day name — "Tuesday" |
fiscalPeriod | The current fiscal period’s label — "FY 2027 Q2" |
fiscalPeriodStart · fiscalPeriodEnd | The period’s first and last dates |
fiscalYearStart | The fiscal year’s first date |
daysRemaining | Days from today to fiscalPeriodEnd |
customFiscal | true when the org runs a custom fiscal calendar |
Find results
Entity.find and Find Records return a set shape instead:
| Key | Always? | What it is |
|---|---|---|
schemaVersion | yes | "0.1-beta" |
entity / object | yes | The entity and object API names, as at top level |
level | yes | Always "query" — the set shape has no field levels |
totalCount | yes | Rows matching the filters — the full set, not the page |
returned | yes | Rows in this response |
columns | yes | The entity’s curated fields at the level asked for, list by default |
records | yes | The rows, each carrying Id plus those columns |
totals | if any | Sums over the full matching set: field label → formatted total, as a string |
computedContext | if on | The standing context, the same object as above |
availableNamedFilters | if any | Named filters this entity defines that the call didn’t use |
requestedRows / notes | only when capped | Present when maxRows exceeded the org’s page-size cap: what was asked for, and a note naming the cap |
briefs / info | if any | The frame, on a call that names a domain |
markdown | when format is both | The same set rendered as markdown |
totalCount and totals cover the full matching set, so a capped query still answers “how many” and “how much” correctly.
Grouped results
groupBy or aggregate returns groups instead of rows, in a shape of its own — and with no schemaVersion.
| Key | Always? | What it is |
|---|---|---|
object | yes | The object API name |
entity | yes | The entity’s label, not its API name |
aggregate | yes | The measure, echoed back — "sum(Amount)", or "count" |
total | yes | { "count": …, "measure": … } over the full matching set |
computedContext | if on | The standing context as a rendered line, not an object |
currency | when converted | The currency the measures were converted to |
groupBy | with groupBy | The field grouped on |
totalGroups / groupsTruncated | with groupBy | Distinct values matched, and whether the group cap cut the list |
groups | with groupBy | One entry per group: value, count, measure, plus records when you asked for samples |
sampledGroups | when samples were cut | How many groups got sample records |
markdown | when format is both | The same summary rendered as markdown |
Record sets
Entity.loadMany wraps the record payloads in one envelope, so the response parses as a single document:
| Key | Always? | What it is |
|---|---|---|
schemaVersion | yes | "0.1-beta" |
requested / returned | yes | IDs you passed, and records in records |
truncated | yes | true when the request ran past the set cap |
records | yes | One top-level payload per record |
errors | yes | One line per ID that couldn’t load — the ID, then the reason |
computedContext / briefs / info | if any | The frame, once for the set |
markdown | when format is both | Every record’s markdown under one frame |
Best practices
- Check
schemaVersion. It’s beta. - Check for key presence, not length. Empty sections are left out.
- Look values up by
path, not position.fieldsorder is the curated order, and an admin can change it. - Treat
found: falseas one outcome. Not found and not visible are deliberately indistinguishable from outside.