Skip to main content

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.

Input validation

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

index.ts
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:
index.ts
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.

Validation error format

Each error in the details array contains:
FieldTypeDescription
pathstring[]Path to the field with the error, e.g.: ["email"] or ["address", "city"]
messagestringHuman-readable error message
codestringZod 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"
    }
  ]
}