Salesforce Apex REST APIs have existed for years, but with the rise of:
- Agentforce
- MCP (Model Context Protocol) servers
- AI agents
- OpenAPI-based tooling
- External Services
- LangChain integrations
the implementation approach for Apex REST classes has changed significantly.
A class that works perfectly in Postman may still fail when:
- generating an OpenAPI specification,
- importing into MCP clients,
- registering as an External Service,
- or exposing as an Agentforce action.
This article explains:
- how Salesforce generates OpenAPI specifications from Apex REST classes,
- the exact qualification rules,
- what patterns fail,
- and the safest architecture for universal compatibility.
Why This Matters
Modern AI tooling relies heavily on OpenAPI contracts.
When Salesforce generates an OpenAPI specification from an Apex REST class, tools such as:
- MCP clients,
- Agentforce,
- Cursor,
- Claude Desktop,
- LangChain,
- and External Services
use that specification to automatically create callable tools.
However, Apex REST has several Salesforce-specific serialization behaviors that are not obvious initially.
The biggest issue:
A REST API can work in Salesforce but still fail for OpenAPI/MCP interoperability.
How Salesforce Generates OpenAPI from Apex REST
Salesforce analyzes:
- the Apex REST annotations,
- method signatures,
- request/response types,
- wrapper classes,
- and field definitions
to infer an OpenAPI schema automatically.
For example:
@RestResource(urlMapping=’/accounts’)
global with sharing class AccountApi {
global class CreateAccountRequest { public String name; public String phone;}global class CreateAccountResponse { public Boolean success; public String id;}@HttpPostglobal static CreateAccountResponse createAccount() { return new CreateAccountResponse();} }
Salesforce can generate:
- request schema,
- response schema,
- operationId,
- endpoint definitions,
- and response contracts.
This is then used by:
- External Services,
- Agentforce,
- MCP servers,
- OpenAPI tools,
- and AI agents.
The Most Important Salesforce REST Behavior
This is the single most important thing to understand.
Apex REST Parameter Binding
If your REST method has parameters:
@HttpPost
global static Response create(Request req)
Salesforce expects JSON like:
{
“req”: {
“name”: “Acme”
}
}
NOT:
{
“name”: “Acme”
}
This causes most MCP/OpenAPI interoperability failures.
Typical errors:
Unexpected parameter encountered during deserialization
or:
JSON_PARSER_ERROR
The Correct Pattern for OpenAPI Compatibility
The safest and most compatible approach is:
- zero method parameters,
- manual JSON deserialization,
- flat wrapper DTOs,
- concrete response classes.
Example:
@RestResource(urlMapping=’/accounts’)
global with sharing class AccountApi {
global class CreateAccountRequest { public String name; public String phone; public String website;}global class CreateAccountResponse { public Boolean success; public String id; public String message;}@HttpPostglobal static CreateAccountResponse createAccount() { String body = RestContext.request.requestBody.toString(); CreateAccountRequest req = (CreateAccountRequest) JSON.deserialize( body, CreateAccountRequest.class ); CreateAccountResponse res = new CreateAccountResponse(); if (req == null || String.isBlank(req.name)) { RestContext.response.statusCode = 400; res.success = false; res.message = 'Account name is required'; return res; } Account acc = new Account( Name = req.name, Phone = req.phone, Website = req.website ); insert acc; RestContext.response.statusCode = 201; res.success = true; res.id = acc.Id; res.message = 'Account created successfully'; return res;}}
This pattern works consistently across:
- Salesforce OpenAPI generation,
- MCP clients,
- Agentforce,
- External Services,
- Postman,
- AI agents,
- and REST integrations.
OpenAPI-Compatible JSON Payload
With manual deserialization, clients can send standard flat JSON:
{
“name”: “Acme Corp”,
“phone”: “+1-555-123-4567”,
“website”: “https://acme.com”
}
This is critical for:
- MCP servers,
- OpenAPI tools,
- LangChain agents,
- and AI integrations.
Requirements for Apex REST OpenAPI Eligibility
1. Use @RestResource
@RestResource(urlMapping=’/accounts’)
2. Class Must Be global
global with sharing class AccountApi
public classes are not sufficient for OpenAPI exposure.
3. REST Methods Must Be global static
Correct:
@HttpPost
global static Response create()
Invalid:
- instance methods,
- private methods,
- protected methods.
4. Use Supported HTTP Annotations
Supported:
@HttpGet@HttpPost@HttpPut@HttpPatch@HttpDelete
5. Wrapper Classes Must Be global
Correct:
global class Request
Otherwise schema generation may fail.
6. Use Serializable Primitive Fields
Good:
public String name;
public Boolean active;
Avoid:
- transient fields,
- unsupported Apex types,
- interfaces.
7. Use Concrete Response Types
Correct:
global static CreateResponse create()
Avoid:
Map<String,Object> or Object
because OpenAPI generators struggle to infer schemas.
What You Should Avoid
1. Avoid REST Method Parameters
Problematic:
create(Request req)
This introduces Salesforce-specific JSON wrapping behavior.
2. Avoid Map<String,Object>
Although Apex supports it, OpenAPI generation often rejects it.
Bad: global static Map<String,Object> create()
3. Avoid Generic Object
Bad: public Object payload;
OpenAPI schema generation becomes ambiguous.
4. Avoid Circular References
Bad:
class A {
B b;
}
class B {
A a;
}
5. Avoid Complex Inheritance
Bad: class Child extends Parent
Flatten DTOs instead.
6. Avoid Overloaded REST Methods
Bad: create()
create(String name)
OpenAPI generation may fail or generate incorrect operationIds.
OpenAPI Generation Lifecycle in Salesforce
When changing Apex REST method signatures:
- Deploy Apex class
- Regenerate OpenAPI specification
- Recreate External Service registration if needed
- Re-import into MCP client/tool
Salesforce may cache older schema metadata.
MCP and AI Agent Compatibility
For MCP/OpenAPI interoperability:
- use flat JSON,
- avoid Apex parameter binding,
- use deterministic schemas,
- avoid ambiguous response types.
This enables compatibility with:
- Claude MCP,
- Cursor MCP,
- Agentforce,
- OpenAI tool calling,
- LangChain,
- External Services,
- and future AI tooling.
Leave a comment