Share Your Experience With Others

Building OpenAPI-Compatible Apex REST APIs in Salesforce for MCP, Agentforce, and External Clients

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;
}
@HttpPost
global 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;
}
@HttpPost
global 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:

  1. Deploy Apex class
  2. Regenerate OpenAPI specification
  3. Recreate External Service registration if needed
  4. 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