Skip to main content
Version: 3.1

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

PatternWhen to Use
Direct HTTP callPublic APIs with no credentials (weather, geocoding, open data)
Connection TypeAPIs requiring stored credentials (API keys, tokens, passwords)
aktorSettingPer-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}&current_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'),
});
});
tip

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

  1. Wrap HTTP calls in createAktorFunction so they appear as named steps in Reaktor diagrams and logs, and can be tested in isolation.

  2. Use Connection Types for credentials — never hard-code API keys or tokens. Connection Types let administrators rotate credentials without redeploying code.

  3. Wrap connection names in aktorSetting so operators can switch the connection per deployment.

  4. Validate responses with Zod to catch upstream API changes early. A ZodError with a clear message is easier to debug than silent data corruption downstream.

  5. Handle errors explicitly using axios.isAxiosError(). Rethrow with context (URL, status code) so the Reaktor log shows what went wrong.