HTTP Functions
HTTP functions create custom HTTP endpoints that execute your code on demand. Use them for custom APIs, webhook handlers, and complex operations.
How It Works
- Define a unique endpoint path (e.g.,
calculate-shipping) - Configure which HTTP methods are allowed (GET, POST)
- Set role-based access control per method
- Write JavaScript code to handle requests
- Call the endpoint via HTTP
URL Structure
/api/{function-name}
Example:
/api/calculate-shipping
External clients should replace /api with https://<region>.restapi.com/<api-name>. See Hosted Webapps for details.
Usage:
fetch("/api/calculate-shipping", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ weight: 2.5 }),
});
The path is /api/<function-name> — not /<function-name>. The /api prefix is required.
Configuration
Basic Settings
| Setting | Description |
|---|---|
| Name | Unique identifier for the function (used in URL) |
| Description | Optional documentation |
| Enabled | Toggle to enable/disable |
HTTP Methods
Configure which methods the endpoint accepts:
| Method | Typical Use |
|---|---|
GET | Retrieve data, calculations |
POST | Create resources, complex operations |
Access Control
Unlike trigger and timer functions, HTTP functions have role-based access control per HTTP method:
| Role | Description |
|---|---|
| Anonymous user | No authentication required |
| Authenticated user | Any logged-in user |
| Custom roles | Your defined roles |
Configure different permissions for each method. For example:
GET→ Anonymous (public)POST→ Authenticated users only
Execution Context
What Your Code Receives
| Object | Description |
|---|---|
req.bodyJson | Parsed JSON request body |
req.body | Raw request body string |
req.method | HTTP method (GET, POST) |
req.headers | Request headers (filtered for security) |
req.query | Query parameters |
me | Authenticated user information (if any) |
api | API information |
secrets | Configured secrets |
Returning Responses
Set the res object to return data:
// Return JSON response
res = {
status: 200,
bodyJson: {
success: true,
result: calculatedValue,
},
};
// Or just set bodyJson (status defaults to 200)
res = {
bodyJson: { message: "Done" },
};
The examples above use the script form (bare global res, no export default). You cannot mix this with the default-export form — pick one per file.
If your file has export default (ctx) => {...}, set the response via ctx.res. A bare global res = {...} is silently discarded the moment an export default exists — the response is lost and the function appears to do nothing. See Two Function Forms.
Typed Parameters
An HTTP function is untyped by default — /api/_openapi and the generated types.d.ts describe it as accepting and returning an opaque object. You can optionally declare the query parameters, request-body fields, and response fields it works with. Declared parameters flow into the OpenAPI document and types.d.ts, so typed clients (typescript-fetch, openapi-generator) — and code-generating LLMs — call the function with the right shapes and get a typed result.
Set them on the function via the API, the CLI, or a schema import:
{
"name": "calculate-shipping",
"functionTypeId": 2,
"endpoint": "/calculate-shipping",
"queryParams": [
{ "name": "currency", "type": "string", "spec": { "enum": ["USD", "EUR", "NOK"] } }
],
"requestBody": [
{ "name": "weight", "type": "decimal", "isRequired": true },
{ "name": "destination", "type": "string", "isRequired": true }
],
"responseFields": [
{ "name": "cost", "type": "decimal" },
{ "name": "estimatedDays", "type": "integer" },
{
"name": "breakdown",
"type": "object",
"spec": { "properties": { "base": { "type": "number" }, "fuel": { "type": "number" } } }
}
]
}
Each entry is a typed field: a name, a type, an optional isRequired, and an optional field spec (enum, description, a nested object shape, …).
Input is enforced, output is advisory
The three sides are not symmetric:
| Side | Types | At runtime |
|---|---|---|
queryParams, requestBody | scalar only¹ | Enforced — a missing required or wrong-typed value is rejected with 400 before your code runs. |
responseFields | any, including object | Advisory — never validated; it only types the response for clients. |
¹ string, integer, decimal, guid, boolean, date, date-time, text. Objects and blobs can only be response fields — the runtime validator can't check them, so they're rejected as input. A typical function is therefore scalar in, rich object out.
Because inputs are enforced, adding a required queryParams or requestBody field makes every existing caller that omits it start receiving 400s. Declare required inputs when you first create the function; add later ones as optional.
Regenerate your client
Declared parameters flow into /api/_openapi and types.d.ts — regenerate your client (e.g. gg rat api -a <api> types) to pick them up. The function becomes a typed method whose body and return type match your declaration:
// types.d.ts (generated)
post(path: `/calculate-shipping${string}`, body: { weight: number; destination: string }):
Promise<{ cost?: number; estimatedDays?: number; breakdown?: { base?: number; fuel?: number } }>;
Typed parameters are set through the API, CLI, or schema import — the developer portal doesn't have an editor for them yet. Functions authored without typed parameters keep working exactly as before (opaque object in, object out).
Use Cases
Custom Calculations
Perform calculations that require server-side logic:
const { weight, destination, method } = req.bodyJson;
let baseCost = weight * 0.5;
if (destination === "international") {
baseCost *= 2.5;
}
if (method === "express") {
baseCost *= 1.5;
}
res = {
bodyJson: {
shippingCost: Math.round(baseCost * 100) / 100,
estimatedDays: method === "express" ? 2 : 7,
},
};
Webhook Handlers
Receive webhooks from external services:
// Handle Stripe webhook
const event = req.bodyJson;
switch (event.type) {
case "payment_intent.succeeded":
const paymentIntent = event.data.object;
// Update order status using built-in method
await patch(`orders/${paymentIntent.metadata.orderId}`, {
paymentStatus: "paid",
stripePaymentId: paymentIntent.id,
});
break;
case "payment_intent.payment_failed":
// Handle failure
break;
}
res = { bodyJson: { received: true } };
Aggregation Endpoints
Create endpoints that aggregate data:
// Dashboard statistics - use built-in methods
const [ordersRes, customersRes, productsRes] = await Promise.all([
get("orders?count=true"),
get("customers?count=true"),
get("products?count=true"),
]);
res = {
bodyJson: {
totalOrders: ordersRes.meta?.count || 0,
totalCustomers: customersRes.meta?.count || 0,
totalProducts: productsRes.meta?.count || 0,
recentOrders: ordersRes.data.slice(0, 5),
},
};
Multi-Step Operations
Orchestrate complex operations:
// Create order with inventory check
const { customerId, items } = req.bodyJson;
// Check inventory using built-in method
for (const item of items) {
const productRes = await get(`products/${item.productId}`);
const product = productRes.data[0];
if (product.stockLevel < item.quantity) {
res = {
status: 400,
bodyJson: {
error: `Insufficient stock for ${product.name}`,
available: product.stockLevel,
},
};
return;
}
}
// Create order
const orderRes = await post("orders", {
customerId,
items,
status: "pending",
total: items.reduce((sum, i) => sum + i.price * i.quantity, 0),
});
const orderId = orderRes.data[0];
// Reserve inventory
for (const item of items) {
const productRes = await get(`products/${item.productId}`);
const product = productRes.data[0];
await patch(`products/${item.productId}`, {
stockLevel: product.stockLevel - item.quantity,
});
}
res = { bodyJson: { orderId, status: "created" } };
Validation with Errors
Use validationErrors for user-facing validation:
const { email, name, age } = req.bodyJson;
if (!email) {
validationErrors.push("Email is required");
}
if (!name || name.length < 2) {
validationErrors.push("Name must be at least 2 characters");
}
if (age && age < 18) {
validationErrors.push("Must be 18 or older");
}
// If validationErrors has items, returns 400 automatically
if (validationErrors.length > 0) {
return; // Stop execution
}
// Proceed with valid data
const userRes = await post("users", { email, name, age });
res = { bodyJson: { userId: userRes.data[0], created: true } };
External API Integration
Call external APIs (requires paid tier):
// Verify address with external service
const { address } = req.bodyJson;
const verification = await fetch("https://api.addressvalidator.com/verify", {
method: "POST",
headers: {
Authorization: `Bearer ${secrets.ADDRESS_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ address }),
});
const result = await verification.json();
res = {
bodyJson: {
valid: result.valid,
standardized: result.standardizedAddress,
},
};
Calling HTTP Functions
HTTP functions are entry points for external clients — web apps, integrations, cURL. Never call one from another function: function-to-function calls can loop forever. Import a shared module instead.
From a Hosted Web App
If your frontend is hosted on RestAPI.com, use the /api virtual path — no CORS or auth headers needed:
const response = await fetch("/api/calculate-shipping", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
weight: 2.5,
destination: "domestic",
method: "standard",
}),
});
const result = await response.json();
console.log(result.shippingCost);
From External Clients
Use the full region-specific URL with an auth token:
const response = await fetch(
"https://<region>.restapi.com/<api-name>/calculate-shipping",
{
method: "POST",
headers: {
Authorization: "Bearer <token>",
"Content-Type": "application/json",
},
body: JSON.stringify({
weight: 2.5,
destination: "domestic",
method: "standard",
}),
},
);
const result = await response.json();
console.log(result.shippingCost);
From cURL
curl -X POST https://<region>.restapi.com/<api-name>/calculate-shipping \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"weight": 2.5, "destination": "domestic", "method": "standard"}'
Best Practices
- Use meaningful function names —
calculate-shippingis better thanfunc1 - Validate input — Check
req.bodyJsonfields and usevalidationErrors - Use built-in methods —
get(),post(),patch()for reading and writing collections - Use
fetchonly for external APIs — External calls require paid tier - Set appropriate access control — Don't expose sensitive operations publicly
- Return consistent responses — Use a standard response structure