> ## 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.

# HTTP Responses

> Control your response status codes and headers with the response builder: custom statuses, custom headers, and MCP behavior.

## Default behavior

When your handler returns a plain object, the platform responds with `200 OK` and `Content-Type: application/json`:

```typescript theme={null}
handler: async (input) => {
  return { name: "Maria", balance: 150.00 };
}
// → 200 OK
// → { "name": "Maria", "balance": 150.00 }
```

When you need a different status code or custom headers, use the **response builder**.

## Response builder

Import `response` from `@jelou/functions`:

```typescript theme={null}
import { define, response, z } from "@jelou/functions";
```

### Custom status code

```typescript theme={null}
export default define({
  description: "Create a user",
  input: z.object({ name: z.string(), email: z.string().email() }),
  output: z.object({ id: z.string(), name: z.string() }),
  handler: async (input) => {
    const user = await createUser(input);

    return response
      .status(201)
      .json({ id: user.id, name: user.name });
  },
});
```

### Custom headers

```typescript theme={null}
handler: async (input) => {
  return response
    .header("X-Request-Id", crypto.randomUUID())
    .header("Cache-Control", "max-age=300")
    .json({ data: [] });
}
```

### Multiple headers at once

```typescript theme={null}
handler: async (input) => {
  return response
    .headers({
      "X-Request-Id": crypto.randomUUID(),
      "Cache-Control": "no-store",
    })
    .json({ ok: true });
}
```

### Full chaining

The builder is **immutable** — each method returns a new instance:

```typescript theme={null}
handler: async (input) => {
  return response
    .status(201)
    .header("Location", `/users/${input.id}`)
    .header("X-Created-By", "api")
    .json({ id: input.id, name: input.name });
}
// → 201 Created
// → Location: /users/abc123
// → { "id": "abc123", "name": "Maria" }
```

## API

| Method                         | Description                                |
| ------------------------------ | ------------------------------------------ |
| `response.status(code)`        | Set HTTP status (default: 200)             |
| `response.header(name, value)` | Add a header                               |
| `response.headers(init)`       | Add multiple headers                       |
| `response.json(body)`          | Finalize with a JSON body                  |
| `response.noContent()`         | Finalize with `204 No Content` and no body |

<Note>
  `.json()` and `.noContent()` are the terminal methods — after calling them you get a final response, not a builder.
</Note>

## Practical examples

<Tabs>
  <Tab title="201 Created">
    ```typescript theme={null}
    handler: async (input) => {
      const ticket = await createTicket(input);
      return response
        .status(201)
        .header("Location", `/tickets/${ticket.id}`)
        .json({ ticketId: ticket.id, status: "open" });
    }
    ```
  </Tab>

  <Tab title="404 Not Found">
    ```typescript theme={null}
    handler: async (input) => {
      const customer = await findCustomer(input.phone);
      if (!customer) {
        return response
          .status(404)
          .json({ error: "not_found", message: "Customer not found" });
      }
      return { name: customer.name, balance: customer.balance };
    }
    ```
  </Tab>

  <Tab title="Cache headers">
    ```typescript theme={null}
    handler: async (input) => {
      const products = await listProducts(input.category);
      return response
        .header("Cache-Control", "public, max-age=300")
        .json({ products, total: products.length });
    }
    ```
  </Tab>

  <Tab title="202 Accepted">
    ```typescript theme={null}
    handler: async (input, ctx) => {
      ctx.log("Webhook received", { event: input.event });
      procesarEvento(input).catch((err) =>
        ctx.log("Error", { error: err.message })
      );
      return response.status(202).json({ acknowledged: true });
    }
    ```
  </Tab>

  <Tab title="204 No Content">
    ```typescript theme={null}
    handler: async (input, ctx) => {
      await deleteResource(input.id);
      return response.noContent();
    }
    // → 204 No Content (no body)
    ```
  </Tab>
</Tabs>

## Output validation

When using `response.json(body)` with an `output` schema, validation applies to the **body** — same as with plain objects. Validation never blocks the response.

## MCP behavior

When invoked via MCP (Brain Studio AI agents):

* The **body** is emitted as `structuredContent`
* **Status code** and **headers** are **ignored** — MCP has no HTTP status concept
* The body is also sent as JSON text for compatibility

<Tip>
  No need to condition your code for HTTP vs MCP — use the response builder normally. The platform extracts the body and discards HTTP metadata for MCP invocations.
</Tip>

## Limitations

| Limitation             | Detail                                                          |
| ---------------------- | --------------------------------------------------------------- |
| JSON or empty          | Only `.json()` and `.noContent()` — no `.text()`, `.html()`     |
| No native Web Response | Returning `new Response()` from `define()`/`app()` doesn't work |
| Fixed Content-Type     | Always `application/json` for `.json()`, cannot be overridden   |
| OpenAPI                | Spec at `/openapi.json` only documents `200` responses for now  |

For advanced needs (streaming, binary, dynamic content-type), use raw mode.
