Apex API

Developer

ctxl.Entity is the package’s Apex entry point: the agent actions and the scoped tools you generate both call it. Its members don’t change shape once released — new options arrive as new fields.


Entity.load — one record

Returns the assembled payload as a String.

// Default entity for the object, markdown
String context = ctxl.Entity.load(recordId);

The object comes from the record ID. The level is the one the entity pins, or the org’s Default detail level when it pins none (shipped: detailed). Every load serves the entity’s active version; an entity never 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.domainApiName = 'sales_desk';
req.entityApiName = 'account_360';    // optional: narrows within the domain
req.level         = 'standard';
req.format        = 'json';
req.toolName      = 'QuarterlyDigest';
String payload = ctxl.Entity.load(req);
FieldTypeNotes
recordIdIdRequired. The record to assemble
recordIdsList<Id>For loadMany. Objects may differ unless entityApiName or objectApiName pins one
entityApiNameStringA specific entity. Alongside domainApiName it narrows within that domain, which has to carry the entity
objectApiNameStringOptional. Comes from the record ID when omitted, and has to match it
levelStringlist · standard · detailed. Default: the entity’s pinned level, then the org’s
formatStringmarkdown (default) · json · both. both is the JSON payload with the markdown rendering under markdown: one assembly, one metered load
domainApiNameStringThe domain to read through. It picks the entity for the object, and adds the domain’s briefs and Info to the payload
toolNameStringYour caller’s name, recorded as the Surface in Usage. Without it, loads land under apex
includeBriefsBooleanDomain calls only. false leaves the domain’s briefs out of the payload; on by default

New call-time options arrive as new EntityRequest fields, never as 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);
FieldTypeNotes
objectApiNameStringThe collection to query. Required unless entityApiName names it
entityApiNameStringA specific entity, which supplies its own object. Alongside domainApiName it narrows within that domain, which has to carry the entity
domainApiNameStringThe domain to read through. It picks the entity for the object, so it needs objectApiName unless an entity is named
filtersJsonStringStructured filter list. → Filters
filterNamesStringComma-separated named filters from the catalog (open,committed). They combine by AND, with each other and with filtersJson
sortByStringField to sort by, ascending unless followed by a direction: Amount DESC. Filterable fields only. Default: last modified, newest first
levelStringlist (default) · standard — how much of each row. detailed is refused: pass the IDs to loadMany
maxRowsIntegerDefault and cap are org settings (shipped: 20/50). A capped response says so
formatStringmarkdown (default) · json · both. both is the JSON payload with the markdown rendering under markdown: one query, one metered load
scopeStringWhose records: all (default) · me · user:<id|name> · team:<id|name> · rollup:<id|name>. → Find Records
groupByStringOne groupable field — one aggregate row per distinct value instead of records
aggregateStringcount, or sum(F) · avg(F) · min(F) · max(F). With groupBy, one value per group; alone, one summary
maxGroupsIntegerGroups returned, largest first. Org-capped; truncation is stated
sampleSizeIntegerRecords per group. Requires groupBy
sampleLevelStringlist (default) · standard — depth of those sample rows
maxSampledGroupsIntegerHow many top groups get samples. Org-capped
toolNameStringRecorded as the Surface in Usage
includeBriefsBooleanDomain calls only. false leaves the domain’s briefs out

Rows come back in the entity’s curated list columns, or its standard columns when you ask for them. Totals cover the full matching set: a query capped at 10 rows still reports the true count and sums. → JSON schema for the result and grouped-result shapes.

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.

BehaviorDetail
Mixed objectsEach record resolves independently: object from the ID, then that object’s default entity — or the domain’s entity for it when domainApiName is set. An entityApiName alongside a domain narrows within it and therefore names one object
Pinned addressentityApiName or objectApiName alongside mixed IDs throws. Either one names a single object
Cap5 records at detailed, 10 otherwise, both org settings. Past the cap it assembles what fits and appends a visible note
Per-record failureA record that can’t load is named in a trailing note; the rest come back
MarkdownThe record payloads joined by ---, with briefs, Info and the computed line once at the top
JSONOne envelope — requested, returned, truncated, records, errors — not a joined string. → JSON schema

Use a lean level: detailed multiplies a full 360 by N. For sets, prefer Entity.find — it returns totals.

Entity.write — a curated update

ctxl.EntityWriteRequest req = new ctxl.EntityWriteRequest();
req.recordId    = oppId;
req.domainApiName = 'sales_desk';
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
FieldTypeNotes
recordIdIdRequired. The record to update
changesJsonStringJSON object of field API name → new value. Picklists take the API value; dates are yyyy-MM-dd
confirmBooleanfalse/omitted returns the diff and changes nothing; true commits
objectApiNameStringOptional; has to match the ID’s type
entityApiNameStringThe entity whose write declarations apply. Default: the object’s default entity, or the domain’s entity for it when domainApiName is set. Alongside domainApiName it narrows within that domain, which has to carry the entity
domainApiNameStringThe domain to write through. That domain has to allow writes
changeDigestStringThe digest the preview returned, passed back with confirm = true. Required wherever confirmation applies
toolNameStringRecorded as the Surface, in Usage and on every write-log row

A write has to clear every level in turn, and any one of them can refuse it:

LevelWhat allows it
OrgAllow writes in Settings
DomainAllow writes on the domain, when the call names one
EntityWrite Access on the entity the call resolves to
FieldThe field picker’s Write column
PlatformFLS, sharing and validation rules — the DML runs in user mode

Confirmation is a separate question, and the domain answers it:

The callConfirmation
Names a domainAllow unattended writes on that domain decides
Names no domainAlways confirms

Where confirmation applies, a commit needs the preview’s changeDigest, and ContextWorks refuses one that is missing or no longer matches the record’s current values. Every commit, failure and refused attempt writes per-field rows to the write log before the call throws. → Write access

Entity.create — a new record

ctxl.EntityCreateRequest req = new ctxl.EntityCreateRequest();
req.objectApiName = 'Task';
req.domainApiName = 'sales_desk';
req.valuesJson    = '{"Subject": "Follow up", "WhatId": "' + oppId + '"}';
req.confirm       = false;   // preview the proposed record
String preview = ctxl.Entity.create(req);
FieldTypeNotes
objectApiNameStringRequired. The object to create
valuesJsonStringJSON object of field API name → value
confirmBooleanfalse/omitted returns the proposed record and creates nothing; true commits
entityApiNameStringThe entity whose create declarations apply. Alongside domainApiName it narrows within that domain, which has to carry the entity
domainApiNameStringThe domain to create through
changeDigestStringThe preview’s digest, passed back with confirm = true
toolNameStringRecorded as the Surface

Creating clears its own chain: Allow record creation at the org, then the domain, the entity, and the field picker’s Create column. So an org can allow creating while refusing editing.

The digest doubles as an idempotency key. Repeat a confirmed create with the same digest inside five minutes and you get back the record already made, not a second one.

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. A detailed assembly runs 6–8 queries, which is why loadMany caps at 5 of them. Calling load in a loop will hit limits; Entity.find is one query at any level it serves.

Error handling

ctxl.Entity.EntityException is the only exception type the package exposes globally. An unknown entity, an object with no entity and a malformed request throw it. Everything else — an object the domain doesn’t serve, a filter on a field the entity doesn’t offer, a refused write — throws a type internal to the package. Catch Exception:

try {
    String context = ctxl.Entity.load(req);
} catch (Exception e) {
    // e.getMessage() names what was wrong, and often what to call instead
}

Two outcomes are not exceptions. A record the caller can’t see comes back as a not-found payload. An element that fails degrades to an issue, and the rest of the payload arrives.

Calling out after a load

Assembly leaves no uncommitted DML, so a caller can load context and then make a callout in the same transaction — an LLM, a Models API request, any HTTP call. Usage telemetry publishes as a Publish Immediately platform event, which the platform’s callout rule ignores. An embedded orchestrator can assemble context and call a model in one go.

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.

What’s next