Agentforce

Unlocking Machine-Readable Agentforce Responses with Custom Connections

In the world of generative AI, we often focus on the “chat”—the natural, conversational flow between a human and an agent. But for developers, the real magic happens when we can integrate those AI insights into existing applications. If you’re building a custom mobile app or a middleware service that consumes Agentforce via the Agent API, you don’t just need a friendly chat; you need structured, machine-readable data.

Today, we’re diving into Custom Connections, a powerful feature that allows you to bypass conversational filler and guides Agentforce Agents to return responses in a strictly defined JSON format.

The Challenge: Conversational vs. Structured

By default, AI agents are designed to be helpful and verbose. While a response like “I found two cases for you! Case 001 is high priority…” looks great in a chat window, it’s a nightmare for an external app (mobile, React) to parse.

To build reliable integrations, we need the agent to follow a contract. We want to move from the standard Salesforce UI response to a predefined JSON schema that our external applications can instantly ingest.

The Metadata Foundation

Custom Connections rely on two specific metadata types to bridge the gap between LLM reasoning and structured output:

1. AiResponseFormat

This defines the JSON schema. It uses an input attribute to specify exactly which fields the agent must return. It acts as the “template” for the agent’s response.

  • Key Attribute: input (The JSON structure).
  • Role: Provides instructions on how the agent should populate specific fields (e.g., “The caseNumber should be the 8-digit identifier”).

2. AiSurface

This is the interface configuration. While the LLM does the thinking, the AiSurface defines how it communicates that thought.

  • Instructions: You can provide high-level directives like “Respond only in valid JSON” or “Do not include conversational filler.”
  • Mapping: It links the agent to the specific AiResponseFormat records it is allowed to use.

Implementation Walkthrough: The Employee Agent

Let’s look at a case study: An Employee Agent designed to return support cases to an external app.

Step 1: Define the Schema (AiResponseFormat)

We define a format called CaseRecordJsonFormat. Our schema requires a summary, the criteria used for searching, a total count, and an array of the case records themselves.

<?xml version="1.0" encoding="UTF-8"?>
<AiResponseFormat xmlns="http://soap.sforce.com/2006/04/metadata">
    <description>JSON format for returning Case records with structured data
    including case number, subject, status, priority, and created date</description>
    <input>{
  "type": "object",
  "properties": {
    "summary": { "type": "string" },
    "searchCriteria": { "type": "string" },
    "cases": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "caseNumber": { "type": "string" },
          "subject": { "type": "string" },
          "status": { "type": "string" },
          "priority": { "type": "string" },
          "createdDate": { "type": "string", "format": "date-time" }
        },
        "required": ["caseNumber", "subject"]
      }
    },
    "totalCount": { "type": "integer" }
  },
  "required": ["summary", "searchCriteria", "cases", "totalCount"]
}</input>
    <instructions>
        <instruction>Use this format when returning Case records via Custom Connection API</instruction>
        <sortOrder>1</sortOrder>
    </instructions>
    <masterLabel>Case Record JSON Format</masterLabel>
</AiResponseFormat>

Step 2: Configure the Surface (AiSurface)

Next, we create the CaseRecordApiSurface. This metadata tells the agent that when this surface is active, it must use our CaseRecordJsonFormat and avoid any “chatty” behavior.

<?xml version="1.0" encoding="UTF-8"?>
<AiSurface xmlns="http://soap.sforce.com/2006/04/metadata">
    <description>Custom connection surface for returning Case records via Agent API</description>
    <instructions>
        <instruction>When users request Case records, use the QueryRecords action
        to retrieve data, then transform the results into the
        CaseRecordJsonFormat structure</instruction>
        <sortOrder>1</sortOrder>
    </instructions>
    <instructions>
        <instruction>ALWAYS respond in valid JSON format only - do not include
        any conversational text before or after the JSON object</instruction>
        <sortOrder>2</sortOrder>
    </instructions>
    <masterLabel>Case Record API Surface</masterLabel>
    <responseFormats>
        <enabled>true</enabled>
        <responseFormat>CaseRecordJsonFormat</responseFormat>
    </responseFormats>
    <surfaceType>Custom</surfaceType>
</AiSurface>

Step 3: Link Custom Connection to Agent

Once your metadata is deployed, link the custom connection to your agent in Agentforce Builder:

  1. Navigate to Agentforce Studio and open your agent
  2. Go to the Connections tab in the Explorer panel
  3. Click Add and select Custom Connection
  4. Choose your CaseRecordApiSurface from the available surfaces
  5. Save the connection configuration

Step 4: Activate the Agent

After adding the custom connection, activate your agent to generate the runtime metadata:

  1. Click Activate in Agentforce Builder
  2. Confirm the activation when prompted
  3. The system generates the GenAiPlannerBundle with the custom surface configuration

This activation creates the following entry in your agent’s GenAiPlannerBundle metadata:

<plannerSurfaces>
    <adaptiveResponseAllowed>true</adaptiveResponseAllowed>
    <callRecordingAllowed>false</callRecordingAllowed>
    <surface>CaseRecordApiSurface</surface>
    <surfaceType>Custom</surfaceType>
</plannerSurfaces>
<plannerType>Atlas__ConcurrentMultiAgentOrchestration</plannerType>

The plannerSurfaces configuration tells the runtime engine that this agent supports custom surface responses and should use your CaseRecordApiSurface when the Agent API specifies "surfaceType": "Custom" in the session configuration.

Connecting via the Agent API

To trigger this structured response, your external application must specify the custom surface when creating a session via the Agent API. We are utilizing the Client Credentials flow for authentication. The secret sauce is in the surfaceConfig:

1. Get Access Token (OAuth Client Credentials)

curl -X POST 'https://[org-domain]/services/oauth2/token' 
  -H 'Content-Type: application/x-www-form-urlencoded' 
  --data-urlencode 'grant_type=client_credentials' 
  --data-urlencode 'client_id=YOUR_CLIENT_ID' 
  --data-urlencode 'client_secret=YOUR_CLIENT_SECRET'

2. Create Agent Session (with surfaceType=Custom)

{
  "externalSessionKey": "unique-session-id",
  "instanceConfig": {
    "endpoint": "https://your-org.my.salesforce.com"
  },
  "streamingCapabilities": {
    "chunkTypes": ["Text"]
  },
  "bypassUser": false,
  "surfaceConfig": {
    "surfaceType": "Custom"
  }
}

3. Send Messages

{
  "message": {
    "sequenceId": 1,
    "type": "Text",
    "text": "Return cases with subject containing Test Account and Priority is Low"
  }
}

Validating the Output

Once the session is established, any query sent will return a structured results block in the JSON response. The agent returns a fully formatted JSON response matching your defined schema—no conversational filler, just clean data your app can parse instantly.

Now we can easily power highly customized external UIs and seamlessly connect Agentforce (via the Agent API) to any frontend without having to build complex logic to parse the Agent’s responses.

Conclusion

Custom Connections turn Agentforce from a chatbot into a powerful data engine. By defining strict metadata contracts via AiSurface and AiResponseFormat, you ensure your external apps get the precise data they need to function, every single time.