Integrating REST APIs
Reaktors can call any external REST API. The standard pattern wraps an axios call inside createAktorFunction so that the HTTP call appears as a named step in diagrams, logs, and tests. For APIs that require credentials, register a Connection Type so administrators can manage secrets in the UI — see Connection Management for the admin perspective.
Overview
| Pattern | When to Use |
|---|---|
| Direct HTTP call | Public APIs with no credentials (weather, geocoding, open data) |
| Connection Type | APIs requiring stored credentials (API keys, tokens, passwords) |
aktorSetting | Per-deployment runtime config (base URLs, simple toggles) |
All patterns use the same core building block: createAktorFunction from @operaide/aktor.
Making HTTP Calls
GET Request
Wrap a plain axios.get call in createAktorFunction. The first argument is a name that shows up in diagrams and logs.
import { createAktorFunction } from '@operaide/aktor';
import axios from 'axios';
import { z } from 'zod';
const WeatherZ = z.object({
latitude: z.number(),
longitude: z.number(),
current_weather: z.object({
temperature: z.number(),
windspeed: z.number(),
}),
});
const getWeather = async ({ lat, lon }: { lat: number; lon: number }) => {
const response = await axios.get(
`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t_weather=true`
);
return WeatherZ.partial().parse(response.data);
};
export const aktorGetWeather = createAktorFunction('aktorGetWeather', getWeather);
POST Request
const createLead = async ({ body }: { body: Record<string, unknown> }) => {
const response = await axios.request({
url: 'https://api.example.com/leads',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: body,
});
return response.data;
};
export const aktorCreateLead = createAktorFunction('aktorCreateLead', createLead);
Using the Aktor in a Reaktor
import { createAktorComposition, registerReaktorDefinition } from '@operaide/aktor';
import { z } from 'zod';
import { aktorGetWeather } from './aktorGetWeather';
const aktorWeatherReport = createAktorComposition('aktorWeatherReport', ({ lat, lon }) => {
return aktorGetWeather({ lat, lon });
});
registerReaktorDefinition({
reaktorDefinitionId: 'weather-report',
label: 'Weather Report',
description: 'Returns current weather for a location',
inputSchema: z.object({
lat: z.number(),
lon: z.number(),
}),
outputSchema: z.string(),
aktor: aktorWeatherReport,
});
Authenticated APIs with Connection Types
For APIs that require credentials, register a Connection Type so that administrators can store secrets in the platform UI. Your code retrieves them by name at runtime via getConnection().
Registering a Connection Type
import { registerConnectionType } from '@operaide/aktor';
import { z } from 'zod';
export const crmConnectionType = registerConnectionType({
type: 'example-crm',
label: 'Example CRM',
configSchema: z.object({
apiKey: z.string().min(1, 'API Key is required'),
baseUrl: z.string().url().default('https://crm.example.com'),
}),
credentialPortalUrl: 'https://crm.example.com/settings/api',
descriptionMarkdown: 'Enter your CRM API key. Find it under **Settings > API Keys**.',
});
Once registered, the connection type appears in the admin UI under Connections > + Add Connection. See Connection Management for how administrators create and manage connections.
Using the Connection in an Aktor
Call getConnection(name) to retrieve the validated, typed credentials at runtime.
import { createAktorFunction } from '@operaide/aktor';
import axios from 'axios';
async function createContact({
name,
email,
connectionName,
}: {
name: string;
email: string;
connectionName: string;
}) {
const connection = await crmConnectionType.getConnection(connectionName);
const response = await axios.post(
`${connection.baseUrl}/api/contacts`,
{ name, email },
{
headers: {
Authorization: `Token ${connection.apiKey}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
export const aktorCreateContact = createAktorFunction('aktorCreateContact', createContact);
In the Reaktor, wrap the connection name in aktorSetting so operators can switch connections per deployment:
const aktorCreateCrmContact = createAktorComposition('aktorCreateCrmContact', ({ name, email }) => {
return aktorCreateContact({
name,
email,
connectionName: aktorSetting(z.string(), 'example-crm', 'CRM connection'),
});
});
Wrap the connection name in aktorSetting so operators can change which connection is used at deployment time — no code change required.
Error Handling
Use axios.isAxiosError() to distinguish HTTP errors from unexpected failures and rethrow with a meaningful message.
async function callExternalApi({ url }: { url: string }) {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
const status = error.response?.status;
const statusText = error.response?.statusText ?? '';
const message = status ? `${status} ${statusText}`.trim() : error.message;
throw new Error(`External API call failed: ${message}`);
}
throw error;
}
}
Response Validation
Parse API responses with a Zod schema so your Reaktor fails fast when the external API changes its contract unexpectedly.
import { z } from 'zod';
const LocationZ = z.object({
status: z.string(),
countryCode: z.string(),
city: z.string(),
});
const getLocation = async () => {
const response = await axios.get('http://ip-api.com/json/?fields=status,countryCode,city');
return LocationZ.parse(response.data); // throws ZodError if shape doesn't match
};
Best Practices
-
Wrap HTTP calls in
createAktorFunctionso they appear as named steps in Reaktor diagrams and logs, and can be tested in isolation. -
Use Connection Types for credentials — never hard-code API keys or tokens. Connection Types let administrators rotate credentials without redeploying code.
-
Wrap connection names in
aktorSettingso operators can switch the connection per deployment. -
Validate responses with Zod to catch upstream API changes early. A
ZodErrorwith a clear message is easier to debug than silent data corruption downstream. -
Handle errors explicitly using
axios.isAxiosError(). Rethrow with context (URL, status code) so the Reaktor log shows what went wrong.