zerolith.ioVérification…
Documentation

Docs

Tout ce qu'il faut pour déployer et appeler une fonction : le contrat du handler, l'invocation HTTP, les variables d'environnement, les quotas, les métriques et le pilotage par agent. Chaque exemple correspond au comportement réel de la plateforme.

Démarrage rapide

  1. Créez un compte et vérifiez votre adresse email.
  2. Déployez une fonction depuis le tableau de bord. Un exemple qui marche est prérempli ; choisissez Python ou Node.js.
  3. Testez-la depuis sa page avec le bouton « Tester la fonction ».
  4. Créez une clé API pour l'appeler depuis l'extérieur (les fonctions publiques s'appellent sans clé).

Le crédit offert à l'inscription couvre largement ces premiers pas : une fonction en veille ne coûte rien.

Le contrat du handler

Une fonction est un fichier unique qui expose un handler. Il reçoit un objet requête et retourne la réponse : une chaîne, un objet JSON, ou un tuple (statut, corps, en-têtes).

python
# main.py — handler: main.handler
def handler(request):
    # request.method   -> "GET", "POST", ...
    # request.path     -> str
    # request.headers  -> dict[str, str]
    # request.query    -> dict[str, str]
    # request.body     -> bytes
    # request.json()   -> parsed JSON body
    name = request.query.get("name", "world")
    return {"message": f"hello, {name}"}

    # other return shapes:
    #   "text"                          -> 200 text/plain
    #   {"a": 1} | [1, 2]               -> 200 application/json
    #   (404, "not found")              -> (status, body)
    #   (201, body, {"X-My": "hdr"})    -> (status, body, headers)
node.js
// main.js — handler: main.handler
exports.handler = (request) => {
  // request.method   -> "GET", "POST", ...
  // request.path     -> string
  // request.headers  -> object (lower-cased names)
  // request.query    -> object (first value per key)
  // request.body     -> Buffer
  // request.json()   -> parsed JSON body
  const name = request.query.name || 'world';
  return { message: `hello, ${name}` };

  // other return shapes:
  //   'text'                          -> 200 text/plain
  //   { a: 1 }                        -> 200 application/json
  //   [404, 'not found']              -> [status, body]
  //   [201, body, { 'X-My': 'hdr' }]  -> [status, body, headers]
};

Le champ « Handler » du formulaire désigne module.fonction (par défaut main.handler). Les runtimes embarquent déjà les bibliothèques courantes : requests, httpx, pydantic, PyYAML côté Python ; axios, lodash, zod, dayjs côté Node.

Invoquer une fonction

Une fonction privée s'appelle avec une clé API (créée dans l'app, affichée une seule fois) passée en en-tête Authorization :

curl
curl -H "Authorization: Bearer $ZEROLITH_KEY" \
     "https://<function-url>?name=neo"

Une fonction publique s'appelle sans authentification, ce qui est pratique pour un webhook :

curl
# public function — no key needed
curl "https://<function-url>?name=neo"

Une URL présignée appelle une seule fonction privée sans clé côté appelant, jusqu'à la date que vous fixez. Créez-la depuis la page de la fonction ; elle n'est affichée qu'une fois, et vous pouvez la révoquer avant son expiration :

curl
# presigned URL — one function, until its expiry date
curl "https://<function-url>?faas_token=faast_..."

Le jeton n'autorise que cette fonction, ni votre compte ni vos autres fonctions. Les appels passés par cette URL vous sont facturés comme n'importe quelle invocation, et vos garde-fous s'appliquent toujours (fonction suspendue ou solde épuisé : l'appel est refusé).

L'authentification est vérifiée en périphérie, avant même que votre fonction ne se réveille, donc un appel non autorisé ne vous coûte rien. Toutes les méthodes HTTP (GET, POST, PUT, PATCH, DELETE) et tous les chemins sont transmis au handler.

Exemples en direct

Quatre fonctions réellement déployées sur la plateforme, publiques et en scale-to-zero. Le code affiché est le vrai handler, et le bouton « Appeler en direct » interroge vraiment l'URL publique : ce que vous voyez est la réponse de la fonction, rien n'est simulé.

Rien n'est appelé (ni facturé) tant que vous ne cliquez pas : c'est aussi ce qui permet à ces fonctions de rester à zéro instance. Le premier appel provoque un démarrage à froid de quelques instants, les suivants sont servis à chaud.

Le dépôt d'exemples rassemble ces handlers et d'autres, prêts à déployer, avec les commandes qui les mettent en ligne : github.com/zerolith-faas/zerolith-examples

Variables d'environnement

Définissez des variables réutilisables (config ou secret) au niveau du compte, puis attachez-les fonction par fonction. Elles sont injectées comme variables d'environnement classiques :

env
# Python                      # Node.js
import os                      process.env.MY_VAR
os.environ["MY_VAR"]

Tout se pilote aussi par l'API : créez, listez, modifiez et supprimez vos variables, puis attachez-les à une fonction via son champ env_var_ids :

curl
# create a secret — its value is write-only, it can never be read back
curl -X POST -H "Authorization: Bearer $JWT" \
     -d '{"name": "API_TOKEN", "kind": "secret", "value": "s3cr3t"}' \
     https://zerolith.io/api/env-vars

# attach it to a function (rolls a new revision)
curl -X PATCH -H "Authorization: Bearer $JWT" \
     -d '{"env_var_ids": ["<env-var-id>"]}' \
     https://zerolith.io/api/functions/<id>

Modifier la valeur d'une variable déploie automatiquement une nouvelle révision de chaque fonction qui l'utilise. Supprimer une variable encore attachée à une fonction est refusé (409) tant qu'elle n'est pas détachée.

Les valeurs des secrets sont en écriture seule : stockées uniquement dans le cluster, jamais relisibles via l'API. Le préfixe FAAS_ est réservé à la plateforme.

Bases de données

Chaque compte peut provisionner une base de données privée : du SQLite accessible en réseau, répliqué en continu vers un stockage objet. Elle vit dans votre espace isolé, n'est jamais exposée publiquement, et n'est joignable que par vos propres fonctions.

Elle se met en veille comme une fonction. Sans requête pendant la durée d'inactivité configurée (10 minutes par défaut, réglable de 6 secondes à 1 heure), l'instance s'arrête : vous ne payez plus aucun temps de calcul, seul le palier de stockage réservé reste facturé, puisque vos données sont dans le stockage objet et n'en bougent pas. La requête suivante réveille la base automatiquement et paie un démarrage à froid de quelques secondes, restauration comprise. Allonger la durée d'inactivité évite de repayer ce réveil entre deux requêtes espacées. Si vous préférez une latence toujours constante, le mode « toujours active » maintient une instance en permanence, au prix du calcul correspondant. Les deux réglages se changent à tout moment, sans déplacer les données.

Attachez la base à une fonction : la plateforme injecte alors ces deux variables dans le pod. Le jeton n'est jamais affiché, il est monté depuis un secret en écriture seule.

env
# Python                                  # Node.js
import os                                  const url = process.env.DATABASE_URL;
url = os.environ["DATABASE_URL"]           const tok = process.env.DATABASE_AUTH_TOKEN;
token = os.environ["DATABASE_AUTH_TOKEN"]
python
# main.py — the libSQL client ships with the runtime
import os, libsql_client

def handler(request):
    db = libsql_client.create_client_sync(
        url=os.environ["DATABASE_URL"],
        auth_token=os.environ["DATABASE_AUTH_TOKEN"],
    )
    db.execute("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)")
    db.execute("INSERT INTO notes (body) VALUES (?)", ["hello"])
    rows = db.execute("SELECT body FROM notes").rows
    db.close()
    return {"notes": [r[0] for r in rows]}

Une base attachée à une fonction ne peut pas être supprimée (409) : détachez-la d'abord. Le palier de stockage est un plafond ferme ; les écritures échouent au-delà.

Le serveur de base ne peut pas appeler vos fonctions : les migrations sont explicites. Déployez le migrateur avec le type « migration » — sans service, sans route ni nom d'hôte, donc privé par construction — désignez-le, puis déclenchez-le à la demande depuis l'interface, l'API ou un agent MCP.

Facturation : l'instance de la base coûte le même prix par Go-seconde qu'une fonction de même taille (et rien pendant son sommeil), plus le palier de stockage détenu, au prorata du mois. Une augmentation de palier prend effet immédiatement ; aucune réduction n'est possible.

Sauvegarde et rétention : générez à tout moment un export SQL portable de votre base, téléchargeable pendant 24 h et restaurable partout (gunzip -c export.sql.gz | sqlite3 base.db). Le service est en bêta, gardez vos propres sauvegardes. Si votre solde reste à zéro, les données sont conservées 7 jours puis SUPPRIMÉES définitivement ; recharger avant l'échéance annule la suppression. Supprimer une base efface aussi ses données du stockage objet, mais vos exports, eux, sont conservés.

Page dédiée : fonctionnement, sécurité d'accès, sauvegarde et coût des bases de données

Domaines personnalisés

Servez une fonction sur un nom de domaine qui vous appartient : api.exemple.com au lieu de l'adresse de la plateforme. La plateforme y termine le TLS et obtient elle-même le certificat, vous n'en téléversez aucun.

Ajoutez le domaine depuis la page de la fonction, puis publiez les deux enregistrements qu'elle vous indique :

  • un TXT sous _zerolith-challenge.votre-domaine, portant un jeton qui prouve que la zone est à vous ;
  • un CNAME de votre nom d'hôte vers l'adresse plateforme de la fonction ; aucun nouvel enregistrement de notre part, et la cible suit la plateforme si cette adresse change un jour.

Rien n'est routé sur une revendication non prouvée. La plateforme revérifie régulièrement ; dès que le TXT est vu, elle met la route en place et commande le certificat, prêt en général en moins d'une minute. Ce n'est qu'ensuite que le domaine se déclare en service.

Une fois vérifié, le TXT a fait son travail et peut être supprimé, il n'est lu qu'une seule fois. Le CNAME, lui, porte le trafic et doit rester.

Jusqu'à 10 domaines par compte. Les jokers ne sont pas pris en charge : le certificat est obtenu via HTTP-01, qui ne sait pas valider un nom en `*`. Les noms d'hôtes déjà servis par la plateforme ne peuvent pas être revendiqués.

Une revendication jamais vérifiée est libérée au bout de 14 jours : un nom d'hôte dont vous ne voulez plus ne reste pas réservé indéfiniment.

Libérer un domaine l'arrête immédiatement. Comme le rajouter implique de commander un nouveau certificat, le nom d'hôte ne peut pas être repris avant 60 minutes.

Tailles et limites

Les tailles sont des presets de ressources (mémoire × CPU). Le prix suit la mémoire et le processeur réservés, multipliés par le temps d'exécution :

Timeout d'exécution configurable de 1 à 600 s. Échelle de 0 (scale-to-zero) à l'échelle max de votre fonction ; min ≥ 1 garde des instances chaudes, facturées en continu.

Quotas par compte :

Fonctions déployées50
Taille du code d'une fonction768 KiB
Variables d'environnement100
… dont secrets50
Valeur d'une variable / budget total4 KiB / 512 KiB
Corps d'une requête API2 MiB
Invocations par compte (en périphérie)600 / min

Le débit est contrôlé en périphérie, avant que votre fonction ne se réveille. Les fonctions publiques appelées sans clé ont leur propre compteur, par fonction : un pic sur un webhook public n'épuise pas le budget du reste du compte.

Cycle de vie & mise à l'échelle

Une fonction monte et descend en charge automatiquement (Knative). Au repos, elle tourne à zéro instance et ne coûte rien ; la première requête provoque un démarrage à froid de quelques instants, puis les appels suivants sont servis à chaud.

Cycle de vie d'une invocation
1 instance0 instancetimeoutfenêtre stablerequêteréponsescale → 0

timeout = durée max d'une seule exécution ; fenêtre stable = durée d'inactivité avant le retour à zéro instance.

Chaque déploiement (nouveau code, changement de config ou de variable d'environnement) crée une nouvelle révision immuable de la fonction. Le trafic ne bascule vers elle que lorsqu'elle est prête à servir, donc une mise à jour ne provoque aucune interruption.

Réglages depuis le formulaire de la fonction : le timeout borne la durée d'une seule exécution (1–600 s) ; la fenêtre stable est le temps d'inactivité avant le retour à zéro instance ; l'échelle min à 0 active le scale-to-zero (à la demande), ≥ 1 garde des instances chaudes (facturées en continu) ; l'échelle max plafonne la concurrence. La facturation ne court que pendant l'exécution (mémoire réservée × temps), plus les invocations.

Observabilité & métriques

Chaque fonction expose ses métriques en direct : requêtes par seconde, latence (p50 / p95 / p99), instances actives, démarrages à froid, CPU et mémoire. La page « Statut » de l'app les affiche, et la même API est ouverte à vos scripts :

curl
# live snapshot for all your functions (rolling window, default 5 min)
curl -H "Authorization: Bearer $JWT" \
     "https://zerolith.io/api/functions/metrics?window_seconds=3600"

# time series for one function — one point per step (feeds the Status graphs)
curl -H "Authorization: Bearer $JWT" \
     "https://zerolith.io/api/functions/<id>/series?window_seconds=3600&step_seconds=60"

Champs retournés : rps, latency_p50_ms / p95 / p99, instances, cold_starts, cpu_millicores, memory_mb. Fenêtre glissante réglable de 1 minute à 7 jours (5 min par défaut). Ces métriques sont mesurées en direct, rien n'est stocké.

Côté facturation, l'API d'usage donne les totaux du compte (requêtes, GB-secondes, coût) et le détail de chaque fenêtre facturée :

curl
# billed requests, GB-seconds and cost — CURRENT MONTH by default
curl -H "Authorization: Bearer $JWT" https://zerolith.io/api/usage/summary

# account lifetime, or any explicit range (since/until are half-open)
curl -H "Authorization: Bearer $JWT" "https://zerolith.io/api/usage/summary?period=all"
curl -H "Authorization: Bearer $JWT" \
     "https://zerolith.io/api/usage/summary?since=2026-06-01T00:00:00Z&until=2026-07-01T00:00:00Z"

# every billed window, filterable per function, paginated with limit/offset
curl -H "Authorization: Bearer $JWT" \
     "https://zerolith.io/api/usage/windows?function_id=<id>&limit=100&offset=100"

Les fenêtres d'usage sont la référence de facturation : elles sont conservées même après suppression de la fonction.

Déclenchement cron

Chaque fonction peut être déclenchée à heure fixe par une expression cron (UTC), via l'API :

curl
curl -X POST -H "Authorization: Bearer $JWT" \
     -d '{"cron": "*/15 * * * *"}' \
     https://zerolith.io/api/functions/<id>/schedules

Les invocations cron passent par le même contrôle de crédit que les appels externes : un compte à sec ne réveille rien.

Pilotage par agent (MCP)

zerolith.io est aussi un serveur MCP avec OAuth 2.1 : connectez Claude ou tout client MCP à votre compte et déployez, modifiez et invoquez vos fonctions en langage naturel, sans clé à copier.

Configurer MCP »

Le serveur expose 41 outils, tous limités à votre compte. Les outils en lecture demandent le scope mcp:read ; les autres demandent mcp:write et un compte vérifié :

OutilScopeDescription
whoamimcp:readCompte authentifié et solde de crédit.
get_catalogmcp:readLangages, tailles, valeurs par défaut et tarifs.
list_functionsmcp:readListe de vos fonctions déployées.
get_functionmcp:readUne fonction : config, URL, variables attachées et code source actuel.
deploy_functionmcp:writeDéployer une nouvelle fonction.
update_functionmcp:writeModifier une fonction (code et/ou config).
delete_functionmcp:writeSupprimer une fonction.
invoke_functionmcp:writeInvoquer une fonction déployée (soumis au crédit).
get_function_metricsmcp:readMétriques en direct (rps, latence, instances, CPU/mémoire).
get_function_logsmcp:readSortie standard récente d'une fonction, la plus récente en premier ; filtrage par sous-chaîne.
get_database_logsmcp:readJournal récent du moteur sqld d'une base ; les identifiants de stockage de la plateforme sont masqués.
get_usage_summarymcp:readSolde et totaux facturés (requêtes, GB-secondes, coût).
list_env_varsmcp:readListe des variables d'environnement du compte.
get_env_varmcp:readUne variable (valeur lisible si config ; jamais pour un secret).
create_env_varmcp:writeCréer une variable (config ou secret).
update_env_varmcp:writeRemplacer la valeur d'une variable.
delete_env_varmcp:writeSupprimer une variable (409 si encore attachée à une fonction).
list_custom_domainsmcp:readLister vos domaines personnalisés et les enregistrements DNS à publier.
add_custom_domainmcp:writeRattacher un nom de domaine à une fonction (renvoie les enregistrements DNS à créer).
delete_custom_domainmcp:writeDétacher un domaine personnalisé et supprimer sa route.
create_presigned_urlmcp:writeCréer une URL qui appelle une fonction jusqu'à une date, sans clé d'API.
list_presigned_urlsmcp:readLister les URLs présignées d'une fonction (jamais les jetons eux-mêmes).
revoke_presigned_urlmcp:writeRévoquer une URL présignée avant sa date d'expiration.
list_databasesmcp:readLister vos bases de données privées.
get_databasemcp:readUne base : palier, taille, mode de veille et fonctions attachées.
create_databasemcp:writeCréer une base privée (le jeton n'est jamais renvoyé).
delete_databasemcp:writeSupprimer une base (409 si elle est encore attachée).
attach_databasemcp:writeAttacher une base à une fonction (injecte les variables).
detach_databasemcp:writeDétacher une base d'une fonction.
get_database_metricsmcp:readStatut live : CPU, mémoire, instances et disque utilisé vs le palier.
set_database_sizemcp:writeChanger le préréglage CPU/mémoire d'une base.
set_database_tiermcp:writeAugmenter le palier de stockage d'une base (hausse uniquement — aucune réduction possible).
rotate_database_tokenmcp:writeRenouveler le jeton d'accès d'une base : l'ancien est retiré et les fonctions attachées redéployées.
set_database_always_onmcp:writeGarder une base allumée en permanence, ou la laisser s'endormir.
set_database_stable_windowmcp:writeChanger le délai d'inactivité avant mise en veille d'une base.
export_databasemcp:writeLancer un dump SQL de la base (un export par heure et par compte).
get_database_exportmcp:writeÉtat du dernier export, avec son lien de téléchargement quand il est prêt.
list_database_exportsmcp:writeHistorique des exports d'une base, le plus récent étant le seul téléchargeable.
set_database_migratormcp:writeDésigner une de vos fonctions comme migrateur de schéma.
unset_database_migratormcp:writeRetirer la désignation de migrateur (la fonction reste attachée).
run_migrationmcp:writeExécuter la fonction de migration désignée, à la demande.

La séquence complète, du branchement du serveur MCP à l'appel de la fonction. Le dernier temps appelle réellement une fonction déployée ; rien ne part tant que vous n'avez pas lancé la démo.

zerolith · mcphors ligne

Sécurité & isolation

Chaque compte s'exécute dans son propre espace Kubernetes, cloisonné par une politique réseau qui refuse tout par défaut. Vos fonctions, vos variables d'environnement et votre base de données y vivent ensemble, et n'en sortent pas : les pods d'un autre compte n'ont aucune route vers les vôtres.

Les appels sont authentifiés en bordure, avant même qu'un conteneur ne démarre : une clé invalide, un compte suspendu ou un solde épuisé sont refusés sans jamais réveiller votre code, donc sans rien vous facturer. Vos secrets ne sont relus par personne, pas même par vous : ni l'API, ni l'interface, ni un agent IA ne renvoient leur valeur. Vos clés API sont stockées hachées et ne s'affichent qu'une fois, à la création.

Page dédiée : isolation, authentification en bordure, secrets, agents et données