Documentation

Référence

Capabilities, bornes et erreurs

Mis à jour le 4 septembre 2026Plateforme v1 Markdown brut

Capability ou permission ?

Deux champs du manifeste se ressemblent mais répondent à des questions
différentes :

  • capabilities répond à « à quel type d’extension ou de ressource
    technique votre app veut-elle accéder ? »
    — une entrée de menu, un appel
    réseau sortant, un espace de stockage clé/valeur…
  • permissions répond à « à quelles données métier du tenant votre
    app veut-elle accéder ? »
    — lire les contacts CRM, écrire des factures…
    Documentation complète : Permissions et consentement du
    tenant
    .

Une app qui affiche un widget lisant des contacts CRM déclare donc
généralement les deux : "capabilities": ["ui.widget"] et
"permissions": ["crm.contacts:read"].


Liste blanche des capabilities

Seules les capabilities explicitement listées ci-dessous (ou un
http.fetch:<domaine> bien formé) sont acceptées par le validateur du
manifeste — toute autre valeur est rejetée à l’enregistrement.

Capability Autorise Quand la demander
storage.tenant.ro Lecture d’un espace de stockage clé/valeur scopé au tenant qui a installé l’app. Votre app a besoin de relire une configuration qu’il a écrite précédemment, sans avoir à la redemander à l’utilisateur.
storage.tenant.rw Lecture et écriture de ce même espace de stockage. Votre app doit mémoriser un réglage, un état, un cache léger propre à ce tenant.
ui.block Contribuer un bloc au site public de l’organisation via contributes.siteBlocks — posé par l’auteur du site dans l’éditeur de page (groupe « Vos apps »), rendu par votre code hébergé (cf. Le runtime). Votre app ajoute une section (météo, catalogue, formulaire…) que l’utilisateur pose sur une page publique de son site.
ui.menu Contribuer une entrée dans la navigation latérale — page du Kit rendue par votre code hébergé (view) ou page HTTPS montée en iframe isolée (embedUrl) via contributes.menuEntries (cf. le manifeste). Votre app a sa propre page dans l’espace de gestion du tenant.
ui.widget Contribuer un widget sur le tableau de bord d’accueil via contributes.widgets — Kit (code hébergé), déclaratif ou embed ; ajout depuis « Personnaliser ». Votre app affiche un résumé ou un indicateur en un coup d’œil.
ui.panel Contribuer un panneau sur une fiche du tenant (contact/société du CRM) — rendu par votre code hébergé via contributes.panels (cf. Le runtime). Votre app montre, sur la fiche d’un client, un score, un historique, des actions.
ui.site Contribuer une page entière au site public via contributes.sitePages — activée et adressée par l’organisation dans Mon site › Apps, rendue par votre code hébergé. Votre app a besoin d’une adresse à lui sur le site de l’organisation (catalogue, prise de rendez-vous, espace dédié…).
ui.settings Réservée : contribuer une section dans la centrale de paramétrage du tenant (contributes.settingsSections, pas encore montée). Les réglages d’une app passent aujourd’hui par settings.fields du manifeste (cf. Données) — aucune capability nécessaire. Ne la demandez pas encore : elle n’ouvre rien de visible.
ai.tool Réservée : exposer un outil à l’assistant IA de la plateforme (contributes.aiTools, pas encore monté). Ne la demandez pas encore : l’assistant n’appelle pas les apps aujourd’hui.
events.subscribe Souscrire aux événements de domaine typés émis par les apps (ex. facture payée, devis accepté) — livrés à votre code hébergé (events du manifeste + defineApp({ events })) ou à un webhook sortant. Votre app réagit à un événement métier plutôt que d’interroger l’API en boucle.
data.collections Lire et écrire les collections déclarées dans collections du manifeste (ctx.data.<nom>) — enregistrements typés, validés, stockés chez l’organisation (cf. Données). Votre app a de vraies données structurées (favoris, scores, tickets…) et ne veut ni base à gérer ni migration.
files.private Déposer et relire des fichiers privés (ctx.files, ≤ 5 Mo, types bornés), servis par une route gardée de l’organisation. Votre app génère un PDF, une image ou un export à remettre aux personnes autorisées.
mail.send Envoyer un e-mail transactionnel (ctx.mail.send) à des personnes liées à l’organisation — ≤ 5 par appel, contenu en blocs, coque de l’organisation, désinscription en un clic, quota par jour (cf. Moteurs). Votre app confirme, rappelle ou relance une personne précise. Jamais une campagne.
notify.send Notifier dans Capibara (ctx.notify.user / permission / admins) — cloche + push, quota par jour. Votre app a quelque chose à dire à une personne, aux porteurs d’une permission ou aux administrateurs.
calendar.project Projeter des dates dans le calendrier de l’organisation (ctx.calendar, source app:<clé>, ≤ 500). Vos objets ont des dates (échéances, interventions, sessions) qui méritent d’apparaître dans le planning.
search.index Rendre vos fiches trouvables dans la recherche ⌘K (ctx.search, ≤ 2 000), groupe au nom de l’app, visible des personnes qui ont « utiliser l’app ». Vos utilisateurs cherchent vos objets par leur nom depuis n’importe quel écran.
approvals.request Demander une validation à un humain (ctx.approvals.request) — cloche « Validations », décision livrée par l’événement approval.decided. Une action de votre app engage l’organisation (remboursement, commande) et doit être tranchée par quelqu’un.

Accès réseau — http.fetch:<domaine>

Pas de capability fixe pour le réseau sortant : chaque domaine externe que
votre app appelle doit être déclaré explicitement, un par un, sous la
forme http.fetch:<domaine pleinement qualifié> :

{
  "capabilities": ["http.fetch:api.exemple.com", "http.fetch:hooks.exemple.io"]
}

Un domaine sans point ni TLD d’au moins deux lettres est refusé
(http.fetch:localhost ou http.fetch:api ne valident pas). Il n’y a pas
d’accès au réseau interne de la plateforme : seul l’egress vers des domaines
publics explicitement déclarés passe, et chaque appel de ctx.fetch est
proxifié par la plateforme (HTTPS seul, 10 s, réponse ≤ 512 Ko, aucune
redirection suivie) et audité — cf. les bornes ci-dessous. Cette capability
ne concerne que le code hébergé : une app hybride ou à backend
externe
fait ses appels réseau depuis son propre serveur, et appelle
l’API publique de Capibara avec une clé API ou un
jeton OAuth, comme n’importe quel client HTTP.


Bornes, quotas et erreurs (référence unique)

Tout ce qui borne l’exécution d’une app est réuni ici ; les autres pages y
renvoient. Ces valeurs sont celles appliquées aujourd’hui par la
plateforme — pas des intentions.

Une interaction (page, action, événement, planification, HTTP entrant)

Borne Valeur Dépassement
Durée d’une interaction 15 s (code hébergé et backend externe) l’interaction échoue : « L’app n’a pas répondu à temps », ligne d’erreur au journal
Corps envoyé à votre code ≤ 256 Ko refus avant exécution
Réponse de votre code ≤ 1 Mo refus, l’écran dit « L’app n’a pas répondu »
Appels au broker par interaction (ctx.kv, ctx.fetch, ctx.capibara, moteurs…) ≤ 100 quota_exceeded : l’appel suivant lève, l’interaction échoue
Journal ctx.log 200 lignes × 500 caractères par interaction lignes suivantes ignorées
État state renvoyé ≤ 8 Ko, signé par la plateforme, lié à l’installation et à la surface, 24 h rejeté (état absent au prochain appel)
followup après un ack dans les 15 min de l’ack followup_rejected
Document du Kit page / sitePage ≤ 500 composants, autres surfaces ≤ 200, profondeur 8, 32 Ko de texte (cf. Le Kit) non affiché, raison au journal

Par app et par organisation

Borne Valeur Dépassement
Interactions simultanées (par app, tous tenants) ≤ 8 busy : réponse 503 « occupée », réessayez
Disjoncteur ≥ 10 échecs en 5 min et ≥ 50 % des appels → app suspendue 5 min, puis une sonde open : 503 pendant la suspension, visible au journal
Kill switch incident posé par l’équipe Capibara (global ou pour une organisation) killed : toute invocation refusée jusqu’à la levée — cf. Publication et review
Planifications intervalle minimum Gratuit 1 h · Pro 15 min · Business 5 min ; une seule exécution en vol par planification occurrences sautées
HTTP entrant corps ≤ 256 Ko, JSON ou texte, rate-limit par IP 413 / 429 au tiers appelant

ctx.* — bornes par capability

Capability Bornes
storage.tenant.* (ctx.kv) clé 1–128 caractères (a-z 0-9 _ . : -), valeur ≤ 32 Ko, 200 clés par installation, list paginé
http.fetch:<host> (ctx.fetch) HTTPS seul, host déclaré seul, 10 s, réponse ≤ 512 Ko (truncated: true au-delà), aucune redirection suivie (redirect: 'error')
data.collections (ctx.data) 10 collections × 30 champs, enregistrement ≤ 64 Ko, 100 lignes par page, 5 conditions, lignes par installation : 5 000 / 100 000 / 1 000 000 selon la formule (cf. Données)
files.private (ctx.files) ≤ 5 Mo par fichier, 500 fichiers par installation, types bornés, quota de stockage de la formule
mail.send ≤ 5 destinataires liés par appel, quota par jour et par installation 20 / 200 / 1 000 (cf. Moteurs)
notify.send quota par jour 200 / 2 000 / 10 000 ; permission ≤ 50 personnes
calendar.project ≤ 500 projections, ≤ 366 jours
search.index ≤ 2 000 fiches
approvals.request ≤ 200 demandes en attente
Façades ctx.capibara.* take ≤ 100 ; en REST, 120 appels / minute par installation (cf. Façades)

Les erreurs que votre code voit

Un appel ctx.* refusé lève une exception dans votre handler (le
message dit pourquoi) ; l’interaction échoue proprement si vous ne
l’attrapez pas, et le journal de l’app porte le code :

Code Sens Ce qu’il faut faire
not_granted capability absente du manifeste, ou non consentie par l’organisation (scope) déclarez-la, puis faites re-consentir (nouvelle version)
forbidden borne dépassée : clé KV invalide, valeur > 32 Ko, quota de clés, host non déclaré, type de fichier refusé… corrigez l’appel ; le message nomme la borne
quota_exceeded plus de 100 appels broker dans l’interaction regroupez vos lectures, mettez en cache dans state ou kv
killed incident actif sur la version (kill switch) attendez la levée ; l’e-mail d’incident vous a dit le motif
scope_not_granted, invalid_input, not_found, conflict, precondition_failed, service_account_missing réponses des façades cf. leur table
handler_missing la capability existe mais aucune implémentation ne la sert sur cette instance contactez le support de l’instance

Côté organisation, une app qui échoue trop (disjoncteur) ou qui fait l’objet
d’un incident est isolée sans affecter les autres apps ni les autres
organisations
; chaque appel qui passe par le broker est audité (journal
consultable par l’équipe de review). Ni le temps CPU ni la mémoire ne sont
mesurés individuellement aujourd’hui : c’est le délai de 15 s et
l’isolation de la sandbox qui bornent une interaction.

Trois façons de faire tourner votre logique

A — Code HÉBERGÉ (sandbox). Ajoutez main à votre manifeste (chemin
relatif, ex. "main": "src/index.ts"). À la publication, Capibara empaquette
ce point d’entrée (sans jamais exécuter votre code au build) et le fait tourner
dans un isolate contraint (workerd). Votre module exporte un handler façon
worker :

import { defineApp, ui } from '@capibara-dev/sdk';

export default defineApp({
  pages: {
    home: async ({ ctx }) => {
      // ctx = votre seule porte vers la plateforme (servie par le broker, qui
      // applique capabilities + permissions + quotas + audit).
      await ctx.kv.put('compteur', { n: 1 });                       // storage.tenant.rw
      const row = await ctx.kv.get('compteur');                      // storage.tenant.ro
      const r = await ctx.fetch('https://api.exemple.com/x');        // http.fetch:api.exemple.com
      return ui.text({ markdown: `Compteur ${row?.value?.n} — amont ${r.status}` });
    },
  },
});

Votre code est invoqué par la plateforme à chaque interaction (page ouverte,
clic, formulaire, événement, planification, requête HTTP entrante) — cf.
Le runtime. ctx.kv expose get/list/put/delete (KV
cloisonné par organisation) et ctx.fetch(url, init) la sortie réseau bornée,
vers les domaines http.fetch: déclarés. Aucun autre accès : ni DB, ni
réseau libre, ni secret. Les bornes ci-dessus s’appliquent à ce code.

Activation. Le runtime hébergé est un service de l’instance (profil
Docker addons), actif par défaut sur capibara.fr — chaque publication
est rechargée automatiquement en quelques secondes. Sur une instance où il
serait désactivé, l’invocation renvoie proprement « runtime non activé » —
votre app reste distribuée et installable, et son mode hybride
(ci-dessous) fonctionne sans dépendre du runtime.

Écrans : vos pages, widgets et panneaux sont décrits en JSON (le
Kit) et rendus par Capibara avec ses composants — jamais de
HTML ni de JavaScript côté client. L’exemple « Météo » (téléchargeable depuis
l’index) montre le motif complet.

B — BACKEND EXTERNE (backend: { url }, sans main). Le même
defineApp, servi par VOTRE serveur, avec les mêmes bornes et le même
disjoncteur ; les façades passent par l’API publique, et collections,
fichiers et moteurs restent réservés au code hébergé. Tout est sur la page
dédiée Backend externe.

C — Mode HYBRIDE (sans main ni backend). Votre propre backend
appelle l’API publique, vos pages sont montées côté
tenant via contributes.menuEntries (iframe isolée + jeton de contexte
signé), et les webhooks sortants vous poussent les
événements. Ce chemin ne dépend d’aucune activation et fonctionne dès
aujourd’hui. A et C se combinent librement ; B est exclusif de A.


Déclarer des capabilities : bonnes pratiques

  • Minimum nécessaire — chaque capability demandée est visible par la
    review ; l’administrateur du tenant voit, lui, les scopes (phrases de
    consentement) et l’hébergement (« Tourne chez Capibara » / « Backend chez
    l’éditeur ») au moment de l’installation. N’en demandez que ce que votre
    app utilise vraiment.
  • Cohérence avec contributes — si vous déclarez ui.widget, la review
    s’attend à trouver une entrée correspondante dans contributes.widgets (et
    réciproquement).
  • http.fetch un domaine à la fois — pas de joker, pas de sous-domaine
    générique : un domaine par service tiers que vous appelez réellement.
  • Pas de capability « au cas où » — en ajouter une que vous n’utilisez
    pas encore ralentit la review et n’apporte rien tant qu’elle n’est pas
    exercée.
Capabilities, bornes et erreurs · Capibara for Developers