#Atelier, transfert et devis
Les routes que la portée atelier ouvre aux clés d'API — ce que le CLI
appelle pour construire un site et le remettre à sa cliente. Même
authentification que le reste (Authorization: Bearer wc_…,
X-Webcosa-Site-Cible pour désigner le site), même forme d'erreur.
Une clé atelier ne se délivre qu'à un compte partenaire ou agence
(POST /api/cles répond 403 portee_reservee sinon). Elle couvre aussi tout ce
que themes couvrait.
#Sites
| Route | Ce qu'elle fait |
|---|---|
POST /api/sites | crée un site. Corps { nom, slug?, theme?, metier?, pour? }. Un partenaire le crée en atelier (formule atelier, rien de facturé, pas d'essai) ; un client, dans la limite de sa formule ; l'agence peut ajouter proprietaire. pour: { courriel?, formule?, presence?, devis_id? } pose l'intention « pour une cliente » : elle préremplit le transfert et relie le devis au site |
GET /api/sites | les sites accessibles : { id, slug, name, status, role, proprietaire, formule, etat, presence } |
POST /api/sites/publier | draft → live. Refusé si le site attend son paiement (402 site_a_activer) ou est suspendu |
POST /api/sites/transfert | envoie « Reprenez votre site ». Corps { courriel, formule: depart|pro|croissance, presence?, message?, devis? } — chaque champ absent est pris dans le pour de la création. Propriétaire + partenaire, site en atelier |
GET /api/sites/transfert | le transfert vivant, ou null — jamais l'empreinte du lien. ouvert_le / ouvertures : la cliente a demandé son code |
PATCH /api/sites/transfert | { action: "renvoyer" } : jeton neuf, l'ancien meurt, trois par jour |
DELETE /api/sites/transfert | annule |
theme doit être un identifiant du catalogue (vela, origo, fondant…) ;
sinon 400 theme_inconnu avec la liste themes.
#Contenu
| Route | Ce qu'elle fait |
|---|---|
GET /api/pages, POST /api/pages | le routage des pages ; POST { title, slug? } |
PATCH /api/pages/<id>, DELETE, POST /api/pages/<id>/publish | titre, adresse, SEO, accueil ; corbeille ; publication. Une nouvelle adresse emporte la mise en page (templates/<adresse>.json de chaque thème du site, sans jamais écraser un gabarit déjà présent à la nouvelle), les liens du menu et, avec rediriger: true si la formule comprend les redirections, une 301 depuis l'ancienne ; la réponse dit ce qui a suivi (renommage) |
PATCH /api/pages/referencement | le référencement de plusieurs pages en une requête : { pages: [{ id, seo }] }, 1 à 60 pages, seo complet (déjà fusionné) ; même permission que la route unitaire, pages à la corbeille refusées, cache public purgé une fois |
GET /api/menus, PATCH /api/menus | { slug: "main-menu" | "footer-menu", content: { items } }, le menu entier. garde: { site, majLe } facultatif : 409 autre_site ou 409 modifie_entre_temps si le site ou le menu a changé depuis la lecture (updated_at) |
GET /api/parametres/<onglet>, PATCH | general, contact, seo, legal, suivi, acces, ia ; { content }, le bloc entier. Quatre champs, ajoutés depuis, sont facultatifs et conservés quand l'envoi les omet : general.icone (l'icône d'onglet), seo.imagePartage (l'image de partage par défaut), contact.horairesDetail (les horaires jour par jour, lundi d'abord), contact.whatsapp (le bouton WhatsApp) ; "" retire une image, null les horaires. ia règle les robots d'IA du robots.txt du site : { gere, chatgpt, perplexity, claude, gemini, entrainement }, six booléens ; gere: true (le défaut) laisse la plateforme décider et ignore les cinq autres, false ferme chaque assistant à false (gemini = Google-Extended, entrainement = GPTBot, ClaudeBot, CCBot). garde: { site, majLe } facultatif, comme pour les menus |
PUT /api/theme/composition | une page validée contre les schémas de section |
POST /api/fichiers/televerser | multipart/form-data, champ fichier (JPEG, PNG, WebP, AVIF, 5 Mo). Rend { fichier: { path, publicUrl, … } } |
Les textes d'une page vivent dans templates/<adresse>.json du thème publié :
PUT /api/themes/<id>/fichiers les écrit, comme n'importe quel fichier de thème.
#Devis
| Route | Ce qu'elle fait |
|---|---|
POST /api/devis | un brouillon. Corps : { site_id?, cliente: { nom, courriel, entreprise?, adresse?, code_postal?, ville?, siret? }, objet, formule, presence?, inclus[], delais?, validite_jours?, mise_en_route_cents?, corrections_heure_cents? } |
GET /api/devis | les devis du compte |
GET /api/devis/<id ou numéro> | le devis, une URL de PDF valable cinq minutes, et evenements[] — le journal : cree, modifie, envoye, renvoye, ouvert, code_demande, signe, refuse, paye, annule, expire, chacun avec cree_le et un detail (le motif d'un refus, par exemple). Le devis porte aussi ouvert_le, ouvertures, refuse_le, refus_motif |
GET /api/devis/<id>/pdf | le PDF lui-même — le signé s'il existe ; un brouillon se rend à la volée (aperçu) — servi par notre domaine, inline |
GET /api/devis/completer?siret=… ou ?adresse=… | remplir la cliente : l'entreprise et son adresse d'après le SIRET (annuaire des entreprises), ou des adresses complètes en tapant (Base Adresse Nationale) — { entreprise, adresse, code_postal, ville, siret } / { adresses: [{ label, adresse, code_postal, ville }] } |
PATCH /api/devis/<id> | tant qu'il est brouillon |
POST /api/devis/<id>/envoyer | rend le PDF, pose le lien de signature, envoie le courriel |
DELETE /api/devis/<id> | annule (jamais un devis signé) |
Le numéro est WC-AAAA-NNNN. La facture, elle, est émise par Stripe au paiement.
#La signature — publique
Ces routes n'ont ni clé ni session : le jeton du lien reçu par la cliente est
l'autorisation. Elles répondent sur signature.webcosa.com comme sur le CMS.
| Route | Ce qu'elle fait |
|---|---|
GET /api/signature/<jeton> | le devis tel que la page l'affiche, peutSigner, peutPayer, URL du PDF |
POST /api/signature/<jeton>/otp | envoie un code à six chiffres à l'adresse du devis (cinq par heure) |
POST /api/signature/<jeton>/signer | { nom, code, consentement: true, demarrageImmediat: true }. Empreinte SHA-256 du PDF, horodatage RFC 3161, PDF signé, ligne de preuve |
POST /api/signature/<jeton>/payer | Stripe Checkout, paiement unique, facture Stripe. Exige un devis signé |
POST /api/signature/<jeton>/ouvert | « la page a été affichée » — appelée par le navigateur au montage, jamais au rendu serveur (les antivirus suivent les liens). Compte les ouvertures ; le partenaire est prévenu à la première |
POST /api/signature/<jeton>/compte | { motDePasse } (8 caractères au moins), sur un devis réglé : crée le compte Webcosa à l'adresse du devis (déjà prouvée par le code de signature), le relie au devis (compte_id), journalise acces_cree. 409 compte_existant si l'adresse a déjà un compte (relié au passage) ; 409 devis_non_paye avant le paiement |
POST /api/signature/<jeton>/refuser | { motif? } (600 caractères). Le devis passe refuse, la signature se ferme, le partenaire reçoit le mot. Sans code : refuser n'engage à rien |
#La reprise d'un site — publique
POST /api/transferts/code avec { jeton } envoie un code de connexion à
l'adresse du transfert — et crée le compte s'il n'existe pas. La réponse est la
même que le jeton existe ou non. L'acceptation (POST /api/transferts/accepter)
exige une session à cette adresse.
#Codes d'erreur propres à l'atelier
error | Statut | Signification |
|---|---|---|
portee_reservee | 403 | une clé atelier demandée par un compte qui n'est ni partenaire ni agence |
partenaire_requis | 403 | la route exige un partenaire ou l'agence |
theme_inconnu | 400 | theme n'est pas dans le catalogue (themes liste les valeurs) |
transfert_en_attente | 409 | cette adresse a un site qui l'attend : elle passe par le lien de reprise, pas par une création |
site_hors_atelier | 409 | le site n'est plus en atelier, il ne se transfère pas |
transfert_deja_envoye | 409 | un transfert vivant existe déjà (renvoyer ou annuler) |
transfert_a_soi_meme | 400 | l'adresse est celle du partenaire |
site_a_activer | 402 | le site attend son premier paiement : rien ne le met en ligne d'ici là |
devis_fige | 409 | le devis n'est plus brouillon (ou est signé, payé, annulé) |
devis_non_signe | 409 | paiement demandé avant la signature |
devis_non_refusable | 409 | seul un devis envoyé se refuse |
code_faux, code_expire, code_epuise | 403 | le code de signature |

