Recipe: Proxy Third-Party APIs¶
Call any external API from a Centrali function with server-side secret handling. This is the foundational pattern — every external integration (Stripe, OpenAI, Twilio, etc.) follows this structure.
Problem¶
Your frontend app needs to call an external API that requires a secret key. You can't expose the key in client-side code. You need a server-side proxy that holds the secret and forwards requests.
Solution¶
Create a Centrali function with an endpoint trigger. The frontend calls the endpoint, the function adds the secret and calls the external API, then returns the result.
Step 1: Create the function¶
async function run() {
const { path, body } = executionParams.payload || {};
const apiKey = triggerParams.apiKey;
if (!apiKey) {
return { success: false, error: 'API key not configured' };
}
try {
const response = await api.httpPost(
`https://api.example.com${path || '/v1/data'}`,
body || {},
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
}
);
return { success: true, data: response };
} catch (error) {
api.log({ error: error.message, path });
return { success: false, error: error.message };
}
}
Step 2: Create an endpoint trigger¶
- In the Console, go to Functions → Triggers → New Trigger
- Select Endpoint as the trigger type
- Assign it to your function
- Set the static params — add your API key:
Secrets in orchestrations
For stronger secret protection, wrap this function in an orchestration and use encrypted params instead of static trigger params. Encrypted params are stored with AES-256-GCM at rest and decrypted only at execution time.
Step 3: Call from your frontend¶
const result = await centrali.triggers.invokeEndpoint('my-proxy-trigger', {
payload: {
path: '/v1/chat/completions',
body: { model: 'gpt-4', messages: [{ role: 'user', content: 'Hello' }] },
},
});
Step 4: Add the external domain¶
In the Console, go to Logic → Domains and add api.example.com to the allowed domains list. Compute functions can only call domains that are explicitly allowed.
Adapting for specific providers¶
OpenAI¶
async function run() {
const apiKey = triggerParams.openaiKey;
const { messages, model } = executionParams.payload || {};
const response = await api.httpPost(
'https://api.openai.com/v1/chat/completions',
{ model: model || 'gpt-4', messages },
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
);
return { success: true, data: response };
}
Stripe (create checkout session)¶
async function run() {
const secretKey = triggerParams.stripeSecretKey;
const { priceId, successUrl, cancelUrl } = executionParams.payload || {};
const response = await api.httpPost(
'https://api.stripe.com/v1/checkout/sessions',
new URLSearchParams({
'line_items[0][price]': priceId,
'line_items[0][quantity]': '1',
'mode': 'payment',
'success_url': successUrl,
'cancel_url': cancelUrl,
}).toString(),
{
headers: {
'Authorization': `Basic ${btoa(secretKey + ':')}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);
return { success: true, data: { url: response.url, id: response.id } };
}
Common gotchas¶
- Allowed domains: You must add the external API domain to your allowed domains list, otherwise the request will be blocked silently.
- Timeouts: Compute functions have a default timeout. If the external API is slow, increase the trigger timeout or handle timeouts gracefully.
- Error responses: External APIs return errors as HTTP responses, not exceptions. Check the response status before returning
success: true. - Secret rotation: If you store secrets in trigger params, you update them in the Console. For orchestration encrypted params, update the orchestration step config.