In today's interconnected digital landscape, APIs (Application Programming Interfaces) are the glue that holds systems together. From sending emails and processing payments to fetching customer data, almost every meaningful automation involves calling a third-party service. But raw API calls can be messy, repetitive, and difficult to manage within larger processes.
This is where action.do shines. By encapsulating any API call into a simple, atomic, and powerful action, you can transform complex integrations into reusable building blocks for your agentic workflows.
This guide will walk you through exactly how to configure an API call as a .do action, turning fragile code into a robust, scalable asset.
Before we dive into the "how," let's understand the "why." Wrapping an API call in an action.do provides several key advantages:
Let's model an action that fetches a customer's details from a hypothetical CRM via its API. Here is the fundamental structure of what we're going to build:
import { action } from '@do-sdk/core';
import axios from 'axios'; // We'll use a popular HTTP client
export const fetchCrmUser = action({
// 1. Metadata: The action's identity
name: 'fetch-crm-user',
description: 'Fetches user details from the primary CRM API.',
// 2. Inputs: The data contract for the action
inputs: {
userId: { type: 'string', required: true }
},
// 3. Handler: The core logic where the API call happens
handler: async ({ inputs, context }) => {
// API call logic goes here...
const { userId } = inputs;
const apiKey = context.secrets.CRM_API_KEY; // Securely access credentials
const response = await axios.get(`https://api.mycrm.com/v1/users/${userId}`, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
// 4. Output: The data returned upon success
return response.data;
},
});
Let's break this down step-by-step.
First, give your action a unique name and a clear description. The name is the machine-readable identifier, while the description helps other developers (and your future self) understand its purpose.
Next, define the inputs. This is the "data contract" for your action. What information does it absolutely need to perform its task? For our example, we need a userId to know which customer to look up. By marking it as required: true, the .do platform ensures this action can't run without it.
export const fetchCrmUser = action({
name: 'fetch-crm-user',
description: 'Fetches user details from the primary CRM API.',
inputs: {
userId: {
type: 'string',
required: true,
description: 'The unique ID of the user in the CRM.'
}
},
// ... handler to come
});
The handler is where the magic happens. This async function is the heart of your action and contains the code that will be executed.
Securely Accessing Credentials:
NEVER hardcode API keys or secrets in your code. The .do platform provides a secure context object where you can store and access secrets. Here, we retrieve our CRM's API key using context.secrets.CRM_API_KEY.
Making the API Call:
Inside a try...catch block (for robust error handling), use a library like axios or the native fetch API to make the HTTP request.
// Inside the handler function...
handler: async ({ inputs, context }) => {
const { userId } = inputs;
const apiKey = context.secrets.CRM_API_KEY;
if (!apiKey) {
throw new Error('CRM API key is not configured in secrets.');
}
try {
const response = await axios.get(
`https://api.mycrm.com/v1/users/${userId}`,
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
}
);
// ... response handling to come
} catch (error) {
console.error('Error fetching user from CRM:', error);
throw new Error(`Could not fetch user ${userId} from CRM.`);
}
},
After a successful API call, you need to process the response.
Here's the complete, production-ready handler:
// The complete handler
handler: async ({ inputs, context }) => {
const { userId } = inputs;
const apiKey = context.secrets.CRM_API_KEY;
if (!apiKey) {
throw new Error('CRM API key is not configured in secrets.');
}
try {
const response = await axios.get(
`https://api.mycrm.com/v1/users/${userId}`,
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
}
);
// Ensure the API call was truly successful
if (response.status !== 200) {
throw new Error(`Failed to fetch user. API responded with status: ${response.status}`);
}
// Return the user data, which becomes the action's output
return response.data;
} catch (error) {
console.error(`Error fetching user ${userId} from CRM:`, error.message);
// Re-throw the error to ensure the action fails atomically
throw error;
}
},
With your fetch-crm-user action defined, you can now use it as a building block in any workflow.do. A workflow orchestrates multiple actions to achieve a larger business goal.
For example, a new user onboarding workflow might look like this:
Each step is a simple, self-contained, atomic unit. The workflow is just the blueprint that connects them, making the entire process easy to read, debug, and modify.
You've now seen how to take a standard API call and elevate it into a secure, reusable, and robust atomic action on the .do platform. This "business-as-code" approach is the key to building scalable and maintainable agentic workflows.
Ready to simplify your automations? Dive into the documentation and start building your first action.do!