Apex Class Tools
What is an Apex Class Tool?
An Apex Class tool is the third tool type you can build in the REDA AI Tool Builder, alongside SOQL Template and File Mapping. Reach for it when:
- The work needs conditional logic, multiple steps, or writes to more than one object
- You need to call out to an external system as part of the tool
- You want to reconcile against existing records, deduplicate, or apply business rules before writing
- You’re wiring a file-driven extraction where the structure of the data is too rich for declarative mapping
The platform handles the agent contract: the JSON Schema, input validation, error wrapping, action confirmation, audit logging and result formatting. Your Apex class only owns the work itself.
The Executor Interface
Every Apex Class tool implements a single global interface:
global interface REDAAI_Executable {
Object execute(String toolName, Map<String, Object> input);
}
Two parameters, one return value:
toolName— the developer name of the Tool Definition that triggered this call. Use it if a single class implements several tools and needs to switch on the caller.input— the parameters the agent provided, validated against your Input Schema JSON, plus the_channelkey described below.- Return value — depends on the tool’s category (see Return contract below).
Knowing which channel you are running in
The same tool can be called by an agent a colleague is chatting with in Salesforce, by a public website widget, or by an unattended API agent running overnight — and it is often right to behave differently in each case. REDA AI therefore adds a set of server-supplied keys to the input map before calling you — starting with the channel:
global class OrderLookupTool implements REDAAI_Executable {
global Object execute(String toolName, Map<String, Object> input) {
String channel = (String) input.get('_channel'); // 'Internal' | 'External_Widget' | 'Community' | 'API'
if (REDAAIConstants.CHANNEL_EXTERNAL_WIDGET.equals(channel)) {
return summaryForPublicVisitor(input); // no internal notes, no margins
}
return fullInternalDetail(input);
}
}
The value is one of Internal, External_Widget, Community or API — the same values the Channel field uses, so compare against the REDAAIConstants.CHANNEL_* constants rather than typing the literal. It is set by the platform — not by the agent. If the model tries to supply _channel itself, REDA AI overwrites it; where the channel genuinely cannot be resolved the key is removed altogether rather than left holding a value the model invented. So a present _channel is always trustworthy, and a missing one means “unknown”, never “Internal”.
_channel tells you where you are, not what you may do. It is context for shaping a response — how much detail to return, which wording to use — and it is not a permission signal. Never read _channel == 'API' as licence to skip a check your tool would otherwise make. Object and field access is decided by the platform and the running user, as described in Data Access & Permissions; a tool that grants itself extra powers based on this key is writing its own security model, and it will be wrong.
Two more keys arrive the same way, with the same overwrite-and-remove guarantee — the platform always supplies them, and a value the model tries to set is discarded:
| Key | What it holds | Use it for |
|---|---|---|
_conversation_id |
The Id of the conversation the tool is executing in. | Writing an audit trail back to the conversation, or resolving conversation-scoped configuration (this is how branded email finds the right branding). |
_agent_definition_id |
The Id of the agent that called the tool. | Behaving differently per agent, or recording which agent performed a write. |
Both are removed rather than left stale when there is no conversation or agent in scope — so, as with _channel, a present value is trustworthy and a missing one means “unknown”.
Existing tools need no change. A tool that never reads _channel simply ignores an extra map entry and behaves exactly as it did before.
Tool Definition Fields
| Field | Required | Purpose |
|---|---|---|
| Tool Type | Yes | Set to Apex_Class. |
| Executor Class | Yes | Fully-qualified Apex class name implementing REDAAI_Executable. Namespaced classes are supported (e.g. MyNs.OrderCreator). |
| Category | Yes | Tool for read-only retrieval, Action for anything that writes or has side-effects. Apex Class tools can be either — choose based on what your class actually does. |
| Description | Yes | The natural-language description sent to the agent. This is how the model decides when to call your tool. Be explicit about purpose, inputs, side-effects, and any constraints. |
| Input Schema | Yes | A JSON Schema (object type) describing the expected input Map. Used to validate the agent’s tool call before your class is invoked. |
| Is File Processing | Optional | Set to true when the tool consumes data extracted from an uploaded file. Enables the file-extraction context covered below. |
| Requires Confirmation | Optional | For Action tools, controls whether the agent shows an action confirmation card before executing. Defaults on for actions. |
| Tool Tier, Channels, Agent Name, Context Objects | Optional | Standard Tool Definition controls — tier escalation, channel scoping, per-agent assignment, and which objects the tool is relevant to. |
Building the Apex Class
A minimal Action-style executor that creates a single record:
global with sharing class MyToolExecutor implements redaai.REDAAI_Executable {
global Object execute(String toolName, Map<String, Object> input) {
try {
String name = (String) input.get('account_name');
Account a = new Account(Name = name);
insert as user a;
return new Map<String, Object>{
'success' => true,
'message' => 'Account created.',
'recordId' => a.Id
};
} catch (Exception e) {
return new Map<String, Object>{
'success' => false,
'message' => e.getMessage()
};
}
}
}
A few things to notice:
- The class is global. Public won’t do — the platform instantiates the class through the global interface so that managed-package consumers can register implementations from their own namespaces.
- The class declares
with sharing. The platform doesn’t enforce sharing for you; the executor is responsible for the data-access posture it wants. - The
insert as userclause runs the DML in user mode so field-level security applies. This is the recommended default unless you have a specific reason to escalate. - Errors are caught and returned as a structured response. Uncaught exceptions are still captured in the incident log, but a clean response gives the agent something useful to say back to the user.
Return Contract
The return shape depends on the tool’s Category:
| Category | Return type | Expected shape |
|---|---|---|
Tool (read-only) |
JSON String | Whatever the agent should see — typically an object with a records array, a totalSize, or a single result object. Errors are returned as { "error": "…" }. |
Action (write) |
Map<String, Object> |
At minimum a success boolean and a message string. Include recordId when you’ve created or updated a record so the agent can link to it. |
File-Driven Apex Tools
Setting Is File Processing to true changes what the agent passes you. Instead of free-form parameters, the platform first asks the model to extract structured data from the uploaded file using the schema you defined, then hands that extracted data to your executor as the input Map.
That is what makes Apex Class tools the right pattern for non-tabular file work — PDFs, images, mixed documents — where File Mapping’s flat column-to-field model isn’t enough.
Designing the input schema for extraction
The input schema describes the extracted shape, not the raw file. For a multi-row document the agent might produce something like:
{
"rows": [
{ "external_id": "INV-1001", "amount": 250.00, "due_date": "2026-05-01" },
{ "external_id": "INV-1002", "amount": 980.50, "due_date": "2026-05-15" }
]
}
Your executor then iterates that rows array, looks up matching records, applies whatever business rules you need, and writes back to Salesforce.
When you build the tool in the Tool Builder with Is File Processing turned on, the AI Generator can produce a sample of the extracted data shape from a representative file you upload. Use it as the contract you write your Apex against.
Naming, Packaging & Resolution
- Class visibility — the class and its
executemethod must both be declaredglobal(notpublic). - Implementation — must implement
redaai.REDAAI_Executable. - Class name length — up to 255 characters in the Executor Class field.
- One class, multiple tools — a single executor can back several Tool Definitions and switch behaviour on
toolName.
public class and public Object execute(...) to global, then redeploy.Security & Governance
- You own the security posture. The platform doesn’t enforce FLS or sharing inside your class. Use
with sharingand theUSER_MODE/as userpatterns unless you have an explicit reason to escalate. - Security acknowledgment on save. The first time you save a custom tool (Apex, SOQL Template or File Mapping) in the Tool Builder, you accept a shared-responsibility acknowledgment confirming you are responsible for its security, permissions (CRUD/FLS), data scoping, input validation and legal compliance — because REDA AI may invoke it autonomously, including on Community and External channels without a human confirmation step. Your acceptance (name + timestamp) is recorded on the tool and supplements the REDA AI Master Service Agreement. Built-in (ISV) tools are exempt; you accept once per tool and it’s preserved across edits.
- Input is validated. The platform validates
inputagainst your schema before invoking your class — you can trust the field names and types you declared, but always defensively cast values. - Action confirmation still applies. Setting Category to
Actionmeans the agent shows a confirmation card before calling your class, unless the agent is configured for auto-execution. - Errors land in incidents. Uncaught exceptions are captured as REDA AI incident records with the conversation, tool name and stack context. Returning a clean error response is friendlier; the incident log is the safety net.
- Synchronous execution. Apex Class tools run inline as part of the agent’s turn. Long-running work should be queued by your executor (Queueable, Schedulable, batch) and the immediate response should tell the user the work has been started.
When to Reach For an Apex Tool
| Need | Right tool type |
|---|---|
| Read records with a parameterised query | SOQL Template Tool |
| Bulk-load a CSV into one Salesforce object with a stable mapping | File Mapping Tool |
| Multi-object writes, conditional logic, deduplication, callouts, or orchestration | Apex Class Tool (this page) |
| Extracting structured data from PDFs, images or mixed documents and acting on it | Apex Class Tool with Is File Processing enabled |
Best Practices
Make the description ruthless. The agent’s decision to call your tool depends almost entirely on the tool description. Spell out what the tool does, what it does not do, and any preconditions.
Keep schemas tight. Use required, enum, maxLength and explicit types. The tighter the schema, the fewer surprises hit your Apex.
Always return a useful message. Even on failure, return success: false and a human-readable message. The agent reads the message back to the user; opaque errors lead to confused conversations.
Stay synchronous, or queue and report. Don’t block the agent turn on a slow callout. Either complete fast, or enqueue async work and return a response that tells the user it’s in progress.
Test with the Tool Builder preview panel. The live impact panel shows the input schema, action card and runtime contract the agent will see — catch mismatches there before the first real conversation.
Frequently Asked Questions
No. It can live in your own namespace, in a managed package you ship, or in the unmanaged code of a customer org. As long as it implements redaai.REDAAI_Executable and the Executor Class field on the Tool Definition can resolve to it, the platform will dispatch to it.
Yes. The first parameter of execute() is the tool’s developer name — switch on it to dispatch internally to the right method. This is a clean way to group related operations under one executor.
No. The platform enforces FLS for its own built-in tools, but inside your Apex you own the data-access posture. Declare the class with sharing and use as user / AccessLevel.USER_MODE on DML and queries unless you have a specific reason to escalate.
The platform catches it, logs an incident with the conversation context, and returns a generic error to the agent. The user sees an apology rather than a stack trace. Catching and returning a structured error response is friendlier — you control the message the user hears.
The execute() call itself runs inline in the agent’s turn. For long work, your executor should enqueue a Queueable or schedule a job and return immediately with a response that tells the user the work has been started.
Turn it on when the tool’s job is to act on data extracted from an uploaded file. The platform will route the file through the model first, materialise it as the structured shape your input schema describes, and pass that shape to your executor — instead of you having to parse the file yourself.
Up to 10,000 characters. That’s plenty for the schemas most tools need; if you find yourself bumping the limit, the tool is probably trying to do too much — consider splitting it.
Next Steps
Read SOQL Template Tools for the lighter-weight pattern when all you need is a parameterised query, or Document Processing for the bigger picture of how files flow through REDA AI.