# RiLiGar Functions — Master Implementation Guide (LLM-Ready) > Funções serverless com agendamento cron. O cliente publica JavaScript e ganha > um endpoint HTTP; cada invocação roda numa sandbox bubblewrap isolada. > > Este arquivo é a documentação inteira do produto — toda a API cabe em treze > rotas. Cole a URL no seu agente (Claude Code, Cursor, Windsurf, Codex, > Copilot) e peça a integração: não é preciso mais nada. > A seção 8 delimita o escopo com precisão — leia antes de inventar API. Base da API: https://functions.manager.myinfrastructure.click Endpoint de uma função: https://functions.manager.myinfrastructure.click/fn/ Painel: https://functions.dashboard.myinfrastructure.click Servidor MCP: https://functions.manager.myinfrastructure.click/mcp (seção 7) Doc do MCP: https://myinfrastructure.click/products/functions/llms-mcp.txt ## 0. Credenciais — leia antes de escrever código O Functions tem uma credencial: a chave `fk_…`, enviada no header `x-api-key`. **Como pedir a chave ao usuário** — use este texto: > Preciso de uma chave de API do Functions. Ela está no seu **painel** > (https://functions.dashboard.myinfrastructure.click), no cartão de acesso da tela > inicial, aba **Chave de API**. Ela começa com `fk_`. Sem `x-api-key`, toda rota de dados responde `401 {"error":"Faltando x-api-key."}`. Com uma chave que não existe, `403 {"error":"API key inválida."}`. **Se o usuário ainda não tem conta, você se provisiona sozinho.** A `fk_` nasce de uma sessão autenticada — mas você consegue essa sessão sem navegador, pelo device flow do RiLiGar Auth. A pessoa lê um código de 8 caracteres no e-mail, informa a você, e o resto é seu: ```bash # 1. sessão (uma interação humana: o código do e-mail) POST https://auth.worker.myinfrastructure.click/auth/code/start {"email":"..."} POST https://auth.worker.myinfrastructure.click/auth/code/verify {"email":"...","code":"ABCD-EFGH"} → JWT # 2. a conta e a chave NASCEM SOZINHAS — não há "projeto" aqui GET https://functions.manager.myinfrastructure.click/me -H "authorization: Bearer " # → {"user":{...},"apiKey":"fk_...","mode":"auth"} (resposta na raiz, sem envelope) ``` A partir daí a `fk_` **substitui o JWT**: as rotas de função aceitam `x-api-key: fk_…`, não o token do Auth. A chave é recuperável — `GET /me` de novo devolve a mesma. O percurso completo dos cinco produtos, e o que a RiLiGar é: https://myinfrastructure.click/llms.txt Se o usuário já tem a chave, peça — é mais rápido. Se não tem conta, use o caminho acima. O que você não pode é gerar código com `fk_xxx` de placeholder e dizer que terminou: sem credencial de verdade, **pare e peça**. O dono de uma função é sempre derivado da credencial, nunca de um campo do corpo ou da URL — um campo seria falsificável. ## 1. Modelo mental Uma FUNÇÃO é um nome estável mais um histórico de versões. - `POST /functions` com um nome novo → cria a função e a versão 1. - `POST /functions` com o mesmo nome → cria a versão 2 e **a ativa**. O endereço `/fn/` não muda. - `POST /functions//rollback` → move o ponteiro da versão ativa de volta. Não reenvia código; só troca qual versão responde. A unicidade é do par **(dono, nome)**: dois clientes podem ter uma função `hello` cada um, sem colidir. Nome válido: `^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$`. Nomes **reservados** (recusados, porque colidiriam com as rotas do manager): `functions`, `schedules`, `me`, `admin`, `status`, `health`, `mcp`. Nome inválido é **recusado**, não corrigido em silêncio. ## 2. A função Um módulo ESM com default export: ```js export default (req, ctx) => { // req → Request padrão (method, headers, url) // ctx → { query, body, env } return Response.json({ metodo: req.method, query: ctx.query }) } ``` O retorno pode ser uma `Response`, ou um valor serializável (vira JSON). **Runtime: Bun, JavaScript apenas.** Não há Python, Go, imagem customizada, Dockerfile nem binário próprio. Não há `npm install` nem build step: o que a função tem é o próprio arquivo — escreva-a sem dependências externas, mesmo com a rede ligada. **Estado: nenhum.** Cada invocação é um processo novo e descartável. Nada escrito em disco sobrevive, nenhuma variável global persiste, não há cache quente entre chamadas. ## 3. A sandbox — leia antes de desenhar a função Cada invocação é um processo [bubblewrap](https://github.com/containers/bubblewrap) descartável: namespaces próprios (user, PID, IPC, UTS e **rede**), ambiente limpo e rootfs mínimo montado read-only. ⚠️ **Toda função nasce sem acesso à internet.** O padrão é a jaula fechada: a função não chama API externa, não lê banco remoto, não envia e-mail. Liberar é **opt-in, por função** — uma decisão explícita do dono, tomada uma vez, e que não muda o código publicado (seção 3.1). **Onde este produto é a ferramenta certa:** todo trabalho que acontece **dentro da requisição** — transformar payload, validar entrada, calcular, assinar, responder. É aí que ele entrega endpoint, jaula e cron sem nenhuma infraestrutura da sua parte. Com a rede ligada, entram também o webhook que avisa um terceiro e a função que consulta uma API pública. **Onde não é:** o que precisa alcançar a rede privada de alguém. Uma função com rede fala com a internet **pública**, nunca com um banco atrás de VPC nem com `localhost` do manager. Medido na mesma Fly Machine, com a mesma função hostil: | O que o código do cliente consegue | Sem sandbox | Com sandbox | | --- | --- | --- | | Variáveis de ambiente do manager | 20 | 1 | | Secrets (`PAYMENTS_SECRET_KEY` etc.) | visíveis | 0 | | Ler `/app/data` (bancos de todos) | sim | bloqueado | | Ver processos do host | 79 | 2 | | Sair para a internet | sim | bloqueado, até ser liberado por função | | Alcançar a rede privada da plataforma | sim | bloqueado, mesmo com rede ligada | | Consumir 400 MB de RAM | sim | morto pelo cgroup | **O que isto NÃO cobre:** o kernel é compartilhado entre todos os tenants da máquina, então um exploit de escalação de privilégio no kernel atravessa a jaula. Não há filtro seccomp ainda. Também não cobre side channels de temporização nem bugs de JIT do Bun. É contenção de dano forte, não isolamento absoluto. **Modo degradado:** sem bubblewrap (macOS, por exemplo) o serviço cai para um processo filho com ambiente limpo, que **não é** fronteira de segurança. Ele **diz isso** em `GET /status` (`"sandbox":"process"`) e na página inicial do manager. Em produção hoje, `/status` responde `"sandbox":"bwrap"`. ### 3.1 Acesso à internet, por função A rede é uma propriedade **da função**, não do serviço. Toda função nasce com ela desligada; ligar é um `PUT` de um booleano: ```bash # ver o estado atual curl https://functions.manager.myinfrastructure.click/functions/hello/network \ -H "x-api-key: fk_…" # ligar curl -X PUT https://functions.manager.myinfrastructure.click/functions/hello/network \ -H "x-api-key: fk_…" \ -H "content-type: application/json" \ -d '{"enabled": true}' ``` ```json {"ok":true,"allowNetwork":true,"effective":true} ``` `enabled` é obrigatório e precisa ser booleano — qualquer outra coisa responde `400`. Uma função que não é da sua conta responde `404`, a mesma resposta de uma que não existe. `allowNetwork` também vem em `GET /functions/:name` e na listagem, para você não precisar de uma chamada por função só para saber quem fala com a internet. **Ligar a rede não republica nem cria versão.** É por isso que ela mora numa rota separada da `POST /functions`: publicar código e conceder rede são autorizações diferentes, e juntá-las faria toda republicação reafirmar — ou apagar em silêncio — uma decisão de segurança que ninguém pediu para revisar naquele momento. Um `rollback` preserva a escolha. **O que uma função com rede alcança:** a internet **pública**, por `http` e `https`. **O que ela nunca alcança, mesmo com a rede ligada:** endereços internos — `10/8`, `127/8`, `169.254/16`, `172.16/12`, `192.168/16`, `100.64/10`, `fdaa::/8`, `fc00::/7`, `fe80::/10` e `::1`. A tentativa falha com um `TypeError` cuja mensagem começa em **"Destino bloqueado"** — política, não rede caída, para você não depurar o código errado por horas. A checagem resolve o domínio antes de deixar a conexão sair, então vale inclusive quando um domínio público resolve para um IP interno (DNS rebinding). Não é uma lista de "IPs feios": do outro lado dessas faixas estão o próprio manager em `127.0.0.1`, a rede privada da Fly onde vivem Auth, Storage e Payments, e o metadata da plataforma em `169.254.169.254`. **Como funciona por baixo:** o namespace de rede é fechado **sempre** (`--unshare-net`). Quando a função tem rede, o `slirp4netns` liga esse namespace à internet por uma stack TCP/IP em espaço de usuário. A função nunca herda a rede da máquina — ela ganha a internet, não "a rede do manager". **Se o manager não consegue entregar a saída**, a invocação de uma função com rede marcada falha com `network_unavailable` e uma mensagem dizendo o motivo. Nunca roda sem rede em silêncio, nunca cai na rede do host. `GET /status` e a ferramenta `functions_get_account` trazem `enforced.egress` justamente para isso: ele diz se este manager consegue de fato entregar saída para a internet. **O filtro cobre `fetch` E o socket.** Embrulhar só o `fetch` não bastava: `import("node:net").connect(8080, "127.0.0.1")` alcançava o manager com o fetch devidamente bloqueado — foi verificado explorável e corrigido. Hoje o guard também embrulha `Socket.prototype.connect`, que é o ponto por onde `fetch`, `node:http`, `node:net` e qualquer biblioteca sobre eles acabam passando. **Limitação conhecida:** a checagem no socket é síncrona sobre o host pedido, e por isso não resolve DNS — um NOME que aponte para IP interno passa por ela. Quem cobre esse caso é o wrapper de `fetch` (que resolve o host antes de conectar) e o `--disable-host-loopback` do slirp. Para um IP interno literal, por qualquer caminho, o bloqueio é direto. ### Headers que a função NÃO controla Removidos da resposta antes de ela sair, sempre: `Set-Cookie`, `Set-Cookie2`, `Access-Control-*`, `Content-Length`, `Transfer-Encoding`, `Content-Encoding`, `Connection`, `Keep-Alive`, `Upgrade`, `Trailer`, `X-Riligar-*`. Motivo: todos os produtos RiLiGar compartilham o apex `myinfrastructure.click`. Uma função emitindo `Set-Cookie: ...; Domain=.myinfrastructure.click` a partir de `/fn/*` seria session fixation contra o Auth e os painéis dos outros produtos. CORS permissivo com credenciais viraria ponte para ler dados autenticados de outra origem. Mentir sobre tamanho ou codificação é request smuggling. ## 4. Limites | Limite | Padrão | Variável | | --- | --- | --- | | Tempo por invocação | 10 s | `FN_TIMEOUT_MS` | | Memória por invocação | 128 MB | `FN_MEMORY_MB` | | Tamanho do código | 512 KB | `FN_MAX_CODE` | | Saída capturada | 1 MB | `FN_MAX_OUTPUT` | | Log por invocação | 200 linhas / 2000 chars | `FN_LOG_MAX_LINES` / `FN_LOG_MAX_CHARS` | | Valor de variável de ambiente | 8 KB | — | | Rede na função | desligada | por função, em `PUT /functions/:name/network` | As variáveis são do serviço, ajustadas por quem opera o manager — a rede é a exceção: ela é de cada função e você mesmo liga (seção 3.1). Estourar a memória é morte por OOM do cgroup, não erro tratado: a invocação volta com falha. ## 5. Rotas Todas exigem `x-api-key: fk_…`, exceto `GET /status` e `ANY /fn/:name`. | Método | Rota | O quê | | --- | --- | --- | | POST | `/functions` | publica (cria ou nova versão) | | GET | `/functions` | lista | | GET | `/functions/:name` | detalhe | | DELETE | `/functions/:name` | remove | | GET | `/functions/:name/versions` | histórico de versões | | GET | `/functions/:name/versions/:id` | uma versão | | POST | `/functions/:name/rollback` | ativa uma versão anterior | | GET | `/functions/:name/network` | diz se a função tem internet | | PUT | `/functions/:name/network` | liga ou desliga a internet da função | | GET | `/functions/:name/logs` | invocações recentes (`?limit=`) | | GET | `/functions/:name/metrics` | métricas (`?horas=`, 1–168) | | GET | `/functions/:name/env` | variáveis da função | | PUT | `/functions/:name/env` | grava uma variável | | DELETE | `/functions/:name/env/:chave` | remove uma variável | | ANY | `/fn/:name` | **executa** (público, sem chave) | | POST | `/schedules` | agenda | | GET | `/schedules` | lista | | PATCH | `/schedules/:id` | altera | | POST | `/schedules/:id/run` | dispara agora | | DELETE | `/schedules/:id` | remove | | GET | `/me` | troca o JWT do painel pela `fk_` da conta | | GET | `/status` | saúde (público, sem chave) | ### Publicar ```bash curl -X POST https://functions.manager.myinfrastructure.click/functions \ -H "x-api-key: fk_…" \ -H "content-type: application/json" \ -d '{"name":"hello","code":"export default (req, ctx) => Response.json({ ok: true })"}' ``` O código vai como **string** no JSON. Não há upload de arquivo, não há zip, não há build step. ### Executar ```bash curl https://functions.manager.myinfrastructure.click/fn/hello ``` `/fn/:name` é **público**: quem tem a URL invoca. Não há autenticação por função — se a sua função precisa de segredo, verifique-o dentro dela, lendo de `ctx.env`. ### Agendar ```bash curl -X POST https://functions.manager.myinfrastructure.click/schedules \ -H "x-api-key: fk_…" \ -d '{"function":"hello","cron":"0 3 * * *"}' ``` O relógio vive só no processo primário do cluster e arma os workers por IPC: o agendamento dispara **uma vez**, não uma vez por worker. ### Status (sem chave) ```bash curl https://functions.manager.myinfrastructure.click/status ``` ```json {"service":"riligar-functions","status":"ok","uptime_s":9185, "sandbox":"bwrap", "enforced":{"sandbox":"bwrap","timeout":true,"outputSize":true, "memory":true,"network":true,"egress":true, "filesystem":true,"processes":true}, "auth":"riligar-auth","scheduler":"primary"} ``` Confira `sandbox` antes de confiar no isolamento: `"bwrap"` é a jaula real, `"process"` é o modo de desenvolvimento sem fronteira de segurança. Os dois campos de rede dizem coisas diferentes. `network` é a **política**: se a jaula de fato separa uma função com rede de uma sem. `egress` é a **entrega**: se este manager consegue levar uma função com rede até a internet. Com `egress: false`, ligar a rede de uma função continua sendo salvo, mas as invocações dela falham com `network_unavailable` — a resposta do `PUT` avisa isso num campo `warning`. ## 6. Erros | Código | Quando | | --- | --- | | 400 | corpo inválido, nome inválido, cron inválido | | 401 | falta `x-api-key` | | 402 | limite de plano atingido (`reason: "plan_limit"`) | | 403 | `x-api-key` não corresponde a nenhuma conta | | 404 | função, versão ou agendamento inexistente | | 405 | método não suportado na rota | | 413 | código acima de 512 KB, ou valor de env acima de 8 KB | ## 7. Servidor MCP O Functions tem servidor MCP, na revisão `2026-07-28` — a mesma que Auth, Storage, Payments e Hoster falam. ``` https://functions.manager.myinfrastructure.click/mcp ``` **O endereço é o da CONTA inteira: não há id no caminho.** Isso difere de Auth, Storage e Payments, onde a URL termina no id de uma aplicação, instância ou projeto. Aqui a `fk_` já alcança todas as suas funções, e um recorte só para o MCP criaria uma segunda noção de posse divergente desta API. É o mesmo desenho do Hoster. São **17 ferramentas**: 9 leem (listar, ver o código, logs, métricas, agendamentos) e 8 escrevem (publicar, executar, rollback, variáveis, agendar, liberar a internet). **Nenhuma apaga** — remover função, agendamento ou variável continua nas rotas da seção 5, onde há uma pessoa confirmando. Autentica de duas formas: a `fk_` desta seção 0, ou OAuth 2.1 pelo RiLiGar Auth. Com um token `auth:read` o catálogo encolhe para as 9 de leitura — as de escrita não são registradas, então não aparecem em `tools/list`. ```bash claude mcp add --transport http riligar-functions \ https://functions.manager.myinfrastructure.click/mcp \ --header "Authorization: Bearer fk_sua_chave" ``` **A documentação completa do MCP** — catálogo com parâmetros, fluxo OAuth, códigos de erro e limites — está em `/llms-mcp.txt`. Ela é separada deste arquivo de propósito: o protocolo afogaria a documentação da API, que é o que a maioria procura primeiro. ## 8. Escopo — o que este produto não faz O recorte é deliberado, e está aqui para o seu código acertar de primeira. - **Rede dentro da função** — desligada por padrão, liberada por função (seção 3.1). Mesmo ligada, ela alcança só a internet pública: nada de `10/8`, `127/8`, `169.254/16` ou `fdaa::/8`. - **Firewall de saída por domínio** — não existe. A escolha é binária: sem rede, ou a internet pública inteira. Não há allowlist de destino. - **Apagar pelo MCP** — o servidor MCP existe (seção 7), mas nenhuma das 17 ferramentas apaga função, agendamento ou variável. Remover é pelas rotas HTTP desta seção 5, onde há uma pessoa confirmando. - **SDK em npm** — não há `@riligar/functions-*`. A integração é HTTP puro. Não sugira `bun add`; não existe pacote para instalar. - **Outras linguagens** — só JavaScript sobre Bun. Sem Python, Go, Dockerfile ou imagem customizada. - **Dependências de terceiros** — sem `npm install` e sem build step, a função roda com o que está no próprio arquivo. A rede ligada não muda isso: ela serve à execução, não à instalação. - **Estado entre invocações** — nada persiste: nem disco, nem memória, nem cache. - **Autenticação por função** — `/fn/:name` é público para quem tem a URL. - **Streaming / WebSocket / processo longo** — o teto é 10 s por invocação. - **Multi-região, réplica e failover** — uma Fly Machine em `iad`. Se ela cai, as funções param e o cron não dispara. - **Filtro seccomp** — na jaula ainda não há. - **Cota além de funções** — o plano limita só quantas funções a conta mantém publicadas (1 sem plano ou Starter, 5 no Pro, sem limite no Scale). Versões, invocações, agendamentos e variáveis não têm teto de plano. ## 8.1 Planos e o 402 O catálogo é o mesmo dos outros produtos da RiLiGar, lido ao vivo em `https://myinfrastructure.click/products/functions#pricing`: | Plano | Mensal | Anual | Funções publicadas | | --- | --- | --- | --- | | sem plano | R$ 0 | — | 1 | | Starter | R$ 29,99 | R$ 299,99 | 1 | | Pro | R$ 49,99 | R$ 499,99 | 5 | | Scale | R$ 99,99 | R$ 999,99 | sem limite | Todo o resto (sandbox, versões, rollback, cron, logs, métricas, variáveis, MCP) está em todos os planos. O gate roda **só na criação** de função: a `POST /functions` de um nome novo acima do limite responde `402` com `{ reason: "plan_limit", resource: "functions", current, limit, plan }`. Publicar nova versão de função existente nunca esbarra nele. Diga ao usuário qual plano libera (a assinatura fica no painel, menu **Assinatura**) e **não tente contornar** — apagar uma função para abrir vaga é decisão dele. ## 9. Checklist para agentes de IA 1. **A função nasce sem internet.** Se ela precisa chamar uma API externa, ligue a rede com `PUT /functions/:name/network` (ou `functions_set_network`) e diga ao usuário que fez isso — é uma concessão de segurança, não um detalhe de configuração. Se o destino é uma rede privada, não encaixa: só a internet pública sai daqui. 2. **Peça a chave `fk_`**; nunca invente um placeholder e siga adiante. 3. **Escreva a função sem dependências externas.** Não há `npm install`, com ou sem rede. 4. **Respeite os tetos**: 10 s, 128 MB, 512 KB de código. 5. **Não prometa headers que serão removidos** (`Set-Cookie`, `Access-Control-*`). 6. **Para conectar um agente**, use o MCP da seção 7 — ou as rotas da seção 5, se preferir HTTP puro. Os dois caminhos chegam na mesma conta. 7. **Não anuncie SDK**: a integração é `curl`/`fetch` contra as rotas da seção 5. 8. **Confira `GET /status`** e avise se `sandbox` vier `"process"`: nesse ambiente não há fronteira de segurança.