Apex API
Developer
ctxl.Entity is the engine’s public entry point. The agent actions, generated scoped tools, and the Studio preview all go through it.
Entity.load — one record
Returns the assembled payload as a String.
// Default entity for the object, detailed level, markdown
String context = ctxl.Entity.load(recordId);
The object is resolved from the record ID; the level defaults to detailed. Every load serves the entity’s active version; an entity that has never been activated serves its draft.
There are no positional overloads. Everything beyond the one-argument form goes through EntityRequest:
ctxl.EntityRequest req = new ctxl.EntityRequest();
req.recordId = recordId;
req.entityApiName = 'finance_view'; // or domainApiName — not both
req.level = 'standard';
req.format = 'json';
req.toolName = 'QuarterlyDigest';
String payload = ctxl.Entity.load(req);
| Field | Type | Notes |
|---|---|---|
recordId | Id | The record to assemble |
recordIds | List<Id> | For loadMany. Objects may differ unless an address is pinned |
entityApiName | String | A specific entity. Mutually exclusive with domainApiName |
objectApiName | String | Optional — resolved from the record ID when omitted |
level | String | list · standard · detailed (default) |
format | String | markdown (default) · json · both — both is the JSON payload with the markdown rendering under markdown: one assembly, one metered load |
domainApiName | String | Route through a domain. Mutually exclusive with entityApiName |
toolName | String | Your caller’s name, stamped as the telemetry surface |
New options land as new EntityRequest fields, not new overloads — build against this form.
Entity.find — a set of records
ctxl.EntityFindRequest req = new ctxl.EntityFindRequest();
req.objectApiName = 'Opportunity';
req.filtersJson = '[{"fieldName":"IsClosed","operator":"=","values":["false"]},' +
' {"fieldName":"CloseDate","operator":"<=","values":["THIS_QUARTER"]}]';
req.sortBy = 'Amount DESC'; // ascending without a direction
req.maxRows = 10;
req.format = 'markdown';
req.toolName = 'PipelineReview';
String results = ctxl.Entity.find(req);
| Field | Type | Notes |
|---|---|---|
objectApiName | String | The collection to query. Required unless entityApiName names it |
entityApiName | String | Explicit entity. Mutually exclusive with domainApiName |
domainApiName | String | Route through a domain. Requires objectApiName |
filtersJson | String | Structured filter list. → Filters |
sortBy | String | Field to sort by, ascending unless followed by a direction: Amount DESC. Filterable fields only. Default: last modified, newest first |
maxRows | Integer | Default and cap are org settings (shipped: 20/50); capped requests are noted in the payload |
format | String | markdown (default) · json · both — both is the JSON payload with the markdown rendering under markdown: one query, one metered load |
toolName | String | Telemetry surface |
Rows use the entity’s curated list columns. Totals cover the full matching set, not the returned page — a query capped at 10 rows still reports the true count and sums.
Calling out after a load
Assembly leaves no uncommitted DML, so a caller can load context and then make a callout — an LLM, a Models API request, any HTTP call — in the same transaction. Usage telemetry publishes as a Publish Immediately platform event, which the platform’s callout rule ignores.
This matters for embedded orchestrators, which assemble context and call a model in one transaction by design and have no place to commit in between. It is a property of the package, not of your code: nothing you do at the call site restores it if it is lost.
Entity.write — a curated update
ctxl.EntityWriteRequest req = new ctxl.EntityWriteRequest();
req.recordId = oppId;
req.changesJson = '{"StageName": "Negotiation/Review", "CloseDate": "2026-09-30"}';
req.confirm = false; // preview first — changes nothing
String diff = ctxl.Entity.write(req);
// show the diff, get approval, then the same request with confirm = true
| Field | Type | Notes |
|---|---|---|
recordId | Id | Required. The record to update |
changesJson | String | JSON object of field API name → new value. Picklists take the API value; dates are yyyy-MM-dd |
confirm | Boolean | false/omitted returns the diff and changes nothing; true commits |
objectApiName | String | Optional; validated against the ID’s type |
entityApiName | String | The write contract to apply. Default: the object’s default entity. Mutually exclusive with domainApiName |
domainApiName | String | Route through a domain — the domain must allow writes |
changeDigest | String | The digest the preview returned, echoed back with confirm = true. Required where confirmation is required |
toolName | String | Telemetry surface, recorded in the write log |
Effective writability is a strict AND — the org write switch, the domain (when routed through one), the entity’s toggle, the field’s declaration, then the platform’s own FLS, sharing, and validation rules; the DML runs in user mode. Confirmation resolves separately: the (domain, entity) assignment decides when domainApiName is set, the org default when it is not. Where it is required, a commit whose changeDigest is absent or no longer matches the record’s current values is refused. Refusals and validation failures throw ctxl.WriteService.EntityWriteException with a message written for an agent, after logging: every commit, failure, and refused attempt writes durable per-field write-log rows. → Write access
Entity.loadMany — several records
ctxl.EntityRequest req = new ctxl.EntityRequest();
req.recordIds = recordIds;
req.level = 'list';
String context = ctxl.Entity.loadMany(req);
Assembles each record through the same path as load and joins the payloads with --- separators.
| Behavior | Detail |
|---|---|
| Mixed objects | Each record resolves independently: object from the ID, then that object’s default entity — or the domain’s entity for it when domainApiName is set |
| Pinned address | entityApiName or objectApiName alongside mixed IDs throws — either names one object |
| Cap | 5 records at detailed, 10 otherwise. Past the cap it assembles what fits and appends a visible note |
| Per-record failure | A record that can’t load is named in a trailing note; the rest come back |
Use a lean level — detailed multiplies a full 360 by N. For sets, prefer Entity.find: it returns totals.
Error handling
Failures throw ctxl.Entity.EntityException:
try {
String context = ctxl.Entity.load(recordId);
} catch (ctxl.Entity.EntityException e) {
// unknown entity, unconfigured object, invalid request shape
}
Two outcomes are not exceptions:
- A record the caller can’t see returns a not-found payload, not a throw.
- A domain coverage miss returns a structured answer naming current coverage, so an agent can re-plan.
Element failures never surface here — they degrade to issues.
toolName
Optional. Set it on everything you build — it’s stamped as the telemetry surface, so Usage attributes loads to your caller instead of lumping them under apex.
Governor limits
Assembly is query-bound: one query for the record, one batched query for all metrics, one per related list and reference, plus whatever each enrichment source runs. Calling load in a loop will hit limits — use Entity.find for sets.
Sharing
Every query runs as the calling user, in user mode; calling Entity.load from a without sharing context does not widen what comes back. See Security.