Documentation Index
Fetch the complete documentation index at: https://docs.jelou.ai/llms.txt
Use this file to discover all available pages before exploring further.
When you define an input schema, each request is validated before executing the handler. If validation fails, the handler does not execute and a 400 is returned:
{
"error": "Validation failed",
"details": [
{
"path": ["email"],
"message": "Invalid email",
"code": "invalid_string"
}
]
}
Example with full schema
import { define, z } from "@jelou/functions";
export default define({
name: "create-ticket",
description: "Creates a support ticket from WhatsApp",
input: z.object({
name: z.string().min(1).describe("Customer name"),
email: z.string().email().describe("Contact email"),
subject: z.string().max(200).describe("Ticket subject"),
priority: z.enum(["low", "medium", "high"]).default("medium"),
}),
output: z.object({
ticketId: z.string(),
status: z.string(),
}),
handler: async (input, ctx) => {
ctx.log("Creating ticket", { customer: input.name, priority: input.priority });
return { ticketId: "TKT-2024-0042", status: "open" };
},
});
Supported types
You can use any Zod type inside z.object():
input: z.object({
name: z.string().min(1),
age: z.number().int().positive(),
email: z.string().email(),
active: z.boolean().default(true),
role: z.enum(["admin", "user", "guest"]),
tags: z.array(z.string()).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
})
Coercion in GET requests
For GET requests, query parameters are strings. Use z.coerce to convert types automatically:
import { define, z } from "@jelou/functions";
export default define({
name: "search-orders",
description: "Search orders by status",
input: z.object({
q: z.string(),
limit: z.coerce.number().default(10),
page: z.coerce.number().default(1),
active: z.coerce.boolean().default(true),
}),
handler: async (input, ctx) => {
ctx.log("Searching", { q: input.q, limit: input.limit });
return { results: [], total: 0 };
},
});
curl "https://search-orders.fn.jelou.ai/?q=pending&limit=5&page=2"
.describe() annotations
Use .describe() on each field to document the parameters. These descriptions appear automatically in the MCP schema, which helps AI agents understand how to use your function:
input: z.object({
phone: z.string().min(10).describe("Phone number with country code, e.g.: 593987654321"),
includeHistory: z.boolean().default(false).describe("Whether to include the conversation history"),
})
Output validation
When you define an output schema, the value returned by the handler is validated after execution. If it does not match:
- A warning is logged
- The response is sent normally with status
200
Output validation never blocks the response. It is a development tool to detect inconsistencies.
output: z.object({
name: z.string(),
balance: z.number(),
})
If the handler returns { name: "Maria", balance: "150" } (balance as string), you will see a warning in the logs but the client receives the response unchanged.
Each error in the details array contains:
| Field | Type | Description |
|---|
path | string[] | Path to the field with the error, e.g.: ["email"] or ["address", "city"] |
message | string | Human-readable error message |
code | string | Zod error code (e.g.: invalid_string, too_small, invalid_enum_value) |
{
"error": "Validation failed",
"details": [
{
"path": ["name"],
"message": "String must contain at least 1 character(s)",
"code": "too_small"
},
{
"path": ["priority"],
"message": "Invalid enum value. Expected 'low' | 'medium' | 'high', received 'urgent'",
"code": "invalid_enum_value"
}
]
}