JSON payload schema
Developer
The structure returned when format is json. Same assembly as markdown, encoded for code that parses it.
NOTE
Every record and set payload carries schemaVersion, currently 0.1-beta. Read it when you parse payloads in production. A change to the shape changes the value.
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, such as "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. Absent when the org switch is off |
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 |
instruction | Present only when one is set |
| absent | A field the caller can’t read is absent from the array, 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 rows are a sample of the 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 the lookup is empty. There is no parent record to fetch, 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 is also the data as-of time |
timeZone | The caller’s time zone ID |
dayOfWeek | Today’s day name, such as "Tuesday" |
fiscalPeriod | The current fiscal period’s label, such as "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, over 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. This shape carries 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. A changed shape changes the value. - 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. From outside, a record that doesn’t exist and a record the caller can’t see look the same.