Aller au contenu
CashChap CashChap Docs

API vitrine v2

Gérez votre vitrine depuis votre serveur ou depuis un assistant : création et réglages, catégories, articles et photos, livraison, pages, publication, commandes, messages de la page Contact, assistance IA. Même norme que l'API v2 (une clé par appel, enveloppes data / error, messages en français), mêmes règles que la page Ma vitrine du portail : limites fixées par CashChap, identité vérifiée, contrôle des fichiers, journal d'activité.

https://cashchap.com/v2/vitrine

Authentification

HTTP
Authorization: Bearer SKLIVE_VOTRE_CLE_SECRETE
Content-Type: application/json
  • Clé secrète LIVE du service (SKLIVE_…, Portail › Développeurs › Clés API), côté serveur uniquement. La vitrine vend toujours en mode Live : une clé SKSANDBOX_… répond 403 live_key_required, une clé publique, de terminal ou de session 403 secret_key_required.
  • Clé absente : 401 missing_api_key (en-tête WWW-Authenticate) ; clé inconnue, mal formée ou désactivée : 401 invalid_api_key. La clé est comparée en temps constant, jamais journalisée.
  • La vitrine est celle du service de la clé (une par service). Un service sans vitrine répond 404 storefront_not_found avec details.can_create ; créez-la avec POST /v2/vitrine.
  • Toute écriture est inscrite dans Compte › Journal d'activité du portail (action « VITRINE (API) », mode Live, adresse IP et User-Agent de l'appel).

Réponses, pagination, idempotence, débit

HTTP
HTTP/1.1 200 OK
CashChap-Version: 2.6.0
CashChap-Vitrine-Version: 1.0.0
X-Request-Id: req_01JBXK8W2M3Q4Z5V6T7Y8H9N0P

{
  "data": { "id": "…", "object": "storefront", "mode": "live", "slug": "ma-boutique", "status": "draft", "...": "..." },
  "meta": { "request_id": "req_01JBXK8W2M3Q4Z5V6T7Y8H9N0P" }
}
Erreur
HTTP/1.1 422 Unprocessable Entity

{
  "error": {
    "code": "validation_failed",
    "type": "validation_error",
    "message": "La requête contient 1 champ invalide.",
    "details": { "fields": [ { "field": "slug", "code": "slug_unavailable", "message": "Cette adresse est réservée par CashChap. Choisissez-en une autre." } ] },
    "request_id": "req_…",
    "doc_url": "https://cashchap.com/docs/api-vitrine#erreurs"
  }
}
  • Enveloppes identiques à l'API v2 : {"data", "meta"} ou {"error"}, jamais de champ success ; X-Request-Id reprend meta.request_id. Les champs sont en anglais snake_case, les dates en ISO 8601 UTC, les montants dans la devise du service (currency, XOF sans décimales). Un champ inconnu est refusé (validation_failed, code unknown_field) ; un corps qui n'est pas un objet JSON répond 400 invalid_json.
  • Versions : CashChap-Version donne la version de la norme v2 publiée (2.6.0), CashChap-Vitrine-Version celle de cette API (1.0.0) ; les ajouts compatibles changent la version mineure.
  • Pagination des articles, commandes et messages par curseur : limit (1 à 200, 50 par défaut), cursor = meta.next_cursor de la page précédente ; meta.has_more, meta.total.
  • Idempotence des créations (POST /v2/vitrine, catégories, articles, photos, analyses, génération, réponse à un message) : en-tête Idempotency-Key facultatif (1 à 128 caractères : lettres, chiffres, -, _). Même clé et même corps : même réponse, en-tête Idempotent-Replayed: true ; même clé et corps différent : 409 idempotency_conflict ; requête encore en cours : 409 request_in_progress. Mémoire : 24 h.
  • Débit, par clé et par minute : 300 lectures, 60 écritures, 10 lancements IA. Au-delà : 429 rate_limited avec Retry-After.
  • Actions irréversibles (zone Danger, suppression groupée, suppression des brouillons IA) : le corps doit porter "confirm": true, sinon 422 confirmation_required.

La vitrine

RouteRôle
GET /v2/vitrineObjet storefront : adresse (slug, url), status (draft, published, suspended), nom, présentation, thème et couleur, logo et bannière, téléphone, WhatsApp et adresse (montrés au client après son paiement seulement), options de livraison, seo, pixels, kyc et daily_limit, compteurs (counts), limites de la plateforme (limits), disponibilité de l'IA.
POST /v2/vitrineCréation en brouillon : public_name (2 à 80), slug (3 à 30 caractères, minuscules, chiffres, tirets ; noms de la plateforme réservés) et, facultatifs, description, theme, accent_color (#rrggbb), phone, whatsapp, address, seo {title, description, keywords, auto}, pixels {meta, tiktok}, logo et banner (image). Identité du promoteur vérifiée exigée (403 account_not_activated) ; KYC partiel : daily_limit 200 000 XOF. 201.
PATCH /v2/vitrineRéglages : seuls les champs envoyés changent (mêmes champs que la création, sauf slug, plus pickup_enabled et delivery_enabled). logo: null retire le logo. Un texte SEO saisi passe en source manual : l'IA ne l'écrase plus.
GET /v2/vitrine/themesLes treize designs (id, label, accent_color…), les univers de démonstration et les limites.
POST /v2/vitrine/slugChanger l'adresse (slug) : une fois par 30 jours (409 slug_change_too_soon), l'ancienne redirige pendant 30 jours.
POST /v2/vitrine/publier · /suspendreMise en ligne (au moins un article actif, sinon 409 storefront_not_publishable) ; suspension (page « momentanément indisponible »).
DELETE /v2/vitrine/logo · /bannerRetrait du logo (icônes régénérées) ou de la bannière.
POST /v2/vitrine/demo · DELETEArticles de démonstration d'un univers (universe : mode, alimentation, electronique, beaute, services, artisanat), marqués demo ; suppression de tous les articles de démonstration.
POST /v2/vitrine/dangeraction : delete_all_articles, delete_all_categories ou reset (brouillon, textes et thème par défaut, adresse conservée, e-mail au titulaire), avec confirm: true. Les commandes sont toujours conservées.
Créer la vitrine
curl -X POST https://cashchap.com/v2/vitrine \
  -H "Authorization: Bearer $CASHCHAP_CLE_SECRETE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: creation-vitrine-1" \
  -d '{
    "public_name": "Boutique Afi",
    "slug": "boutique-afi",
    "description": "Pagnes, robes et accessoires cousus à Cotonou.",
    "theme": "wax",
    "accent_color": "#d97706",
    "seo": { "auto": true }
  }'

Images : logo, bannière, photos

Une image se donne sous trois formes, dans un objet : {"base64": "…", "filename": "photo.jpg"} (ou une URL de données data:image/jpeg;base64,…), {"url": "https://cashchap.com/vitrine-image/signee/…"} (lien signé CashChap : l'image est copiée, rien n'est téléchargé) ou, pour les photos d'article seulement, {"url": "https://…"} : le serveur télécharge l'image (hôte public résolu, https, sans redirection, 4 Mo au plus). Toute image passe les contrôles de Ma vitrine : nom et contenu suspects refusés (malicious_file_rejected, inscrit dans le journal du compte), type réel vérifié, réencodage et redimensionnement par le serveur, miniature. Formats : JPEG, PNG, GIF, WebP, AVIF, HEIC, BMP, TIFF, SVG assaini.

Catégories

RouteRôle
GET /v2/vitrine/categoriesListe ordonnée (position), avec articles_count ; meta.uncategorized_articles.
POST /v2/vitrine/categoriesname (80 car.), description (200), active. 30 catégories au plus ; un nom déjà pris (ou trop proche) répond 409 category_name_taken. 201.
PATCH /v2/vitrine/categories/{id}name, description, active, position.
POST /v2/vitrine/categories/ordreids dans l'ordre voulu (les catégories absentes suivent).
DELETE /v2/vitrine/categories/{id}Suppression ; ses articles deviennent « sans catégorie ».

Articles, variantes, photos

RouteRôle
GET /v2/vitrine/articlesListe paginée ; filtres category_id (ou none), active, featured, demo, ai_draft, search.
GET /v2/vitrine/articles/{id}Fiche : prix, promo, effective_price, strikethrough_price, stock, photos, variants, video, seo, url publique.
POST /v2/vitrine/articlesname (2 à 120), price (> 0, arrondi à la devise), description, promo {type: none | price | percent, price, percent (1 à 99), starts_at, ends_at (une date seule est lue à l'heure de Porto-Novo)}, stock (null = illimité), category_id, active, featured (6 au plus), position, variants [{name, price, stock, active}], video_url (YouTube, Vimeo, TikTok), photos [image…]. Les photos refusées sont détaillées dans meta.photos. Limite d'articles de la plateforme : 409 article_limit_reached. 201.
PATCH /v2/vitrine/articles/{id}Modification partielle (mêmes champs sauf photos) ; variants remplace la liste (une variante avec id est conservée) ; active, featured, category_id (déplacement), position.
POST /v2/vitrine/articles/ordreids dans l'ordre voulu ; list : catalog (défaut) ou featured (« À la une »).
POST /v2/vitrine/articles/groupeAction groupée : action (move + category_id, feature, unfeature, hide, show, delete + confirm), ids (500 au plus).
DELETE /v2/vitrine/articles/{id}Suppression (photos, variantes, vidéo) ; les commandes passées gardent le nom de l'article.
POST /v2/vitrine/articles/{id}/photosphotos [image…] : résultat par photo dans meta.results, liste des photos en data. Limite par article : photo_limit_reached. 201 dès qu'une photo est acceptée.
POST /v2/vitrine/articles/{id}/photos/ordreids des photos dans l'ordre voulu.
POST /v2/vitrine/photos/{id}/principalePhoto principale.
PATCH /v2/vitrine/photos/{id}alt (texte alternatif, 125 car., source manual), main.
DELETE /v2/vitrine/photos/{id} · /articles/{id}/videoSuppression d'une photo (fichier et miniature retirés) ; retrait de la vidéo.
Ajouter un article avec une photo
curl -X POST https://cashchap.com/v2/vitrine/articles \
  -H "Authorization: Bearer $CASHCHAP_CLE_SECRETE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: article-robe-wax-1" \
  -d '{
    "name": "Robe en pagne wax",
    "description": "Robe longue taillée dans un pagne authentique, doublure en coton.",
    "price": 18000,
    "promo": { "type": "percent", "percent": 20, "ends_at": "2026-12-31" },
    "stock": 12,
    "category_id": "9b2f1a3c-0000-4000-8000-00000000abcd",
    "variants": [ { "name": "Taille M" }, { "name": "Taille L" }, { "name": "Taille XL", "stock": 2 } ],
    "photos": [ { "url": "https://photos.exemple.com/robe-wax.jpg" } ]
  }'

Livraison et pages

RouteRôle
GET /v2/vitrine/livraisonpickup_enabled, delivery_enabled, zones (nom, fee, delay).
POST /v2/vitrine/livraisonRemplacement d'un bloc : pickup_enabled, delivery_enabled, zones [{id (pour garder une zone), name, fee, delay}]. Sans zone, la livraison se ferme et le retrait en boutique est forcé.
GET /v2/vitrine/pagesLes trois pages du menu : about, delivery, contact.
POST /v2/vitrine/pages/{type}title (80), content (5 000, texte sans HTML), active. La page contact est toujours visible (formulaire relayé par CashChap) : seul son titre se règle, aucune coordonnée du vendeur n'y figure.

Commandes

RouteRôle
GET /v2/vitrine/commandesListe paginée, de la plus récente à la plus ancienne ; status : pending, paid, failed, expired, cancelled, to_deliver (payées, non livrées), deposit (acompte reçu) ; from, to, search. meta.totals_by_status. Téléphones masqués.
GET /v2/vitrine/commandes/{id}Détail synchronisé avec les paiements : status, settlement (full, deposit_pending, deposit_received), delivery_state et next_delivery_state, montants, customer (téléphone en clair), delivery (mode, zone, adresse structurée, position, contact), lines, transaction, payment_url, tracking_url.
POST /v2/vitrine/commandes/{id}/livraisonstate : prepared, shipped, delivered, dans l'ordre, pour une commande payée (ou acompte reçu) ; le client est prévenu par e-mail. Sinon 409 delivery_state_invalid.
POST /v2/vitrine/commandes/{id}/annulerAnnulation d'une commande encore payable (sans acompte reçu) : lien de paiement désactivé. Sinon 409 order_not_cancellable.

Messages de la page Contact

RouteRôle
GET /v2/vitrine/messagesListe paginée ; status : new, read, replied, spam ; search ; meta.unread. La lecture ne change jamais le statut.
GET /v2/vitrine/messages/{id}Détail : texte complet, téléphone en clair, article et commande concernés, réponses envoyées.
POST /v2/vitrine/messages/{id}/statutstatus : read (marquer lu), replied, spam, new.
POST /v2/vitrine/messages/{id}/repondretext (2 à 3 000 car.) : e-mail envoyé par CashChap au nom de la boutique, message d'origine cité, journalisé. Sans adresse e-mail du client : 422 message_reply_impossible. 201.

Assistance IA

Disponible quand CashChap l'active (GET /v2/vitrine/ia : available, quota_remaining). Sinon les lancements répondent 503 ai_unavailable ; quota du jour épuisé : 429 ai_quota_exceeded. Les traitements longs sont asynchrones : la route de lancement répond 202 avec l'objet et son status (pending, processing, completed, failed, confirmed) ; interrogez la route d'état jusqu'à completed, puis confirmez les brouillons.

RouteRôle
POST /v2/vitrine/analysesAnalyse de photos : photos [{base64, filename}] (30 au plus par défaut). Chaque photo passe les contrôles des photos d'article. 202.
GET /v2/vitrine/analyses · /analyses/{id}Analyses récentes ; état d'une analyse avec ses drafts (index, name, description, category, price, variants, photos, state).
POST /v2/vitrine/analyses/{id}/confirmerlines [{index, name, price, category, description, variants (« Taille M, Taille L »)}], active : articles créés avec leurs photos. 201.
DELETE /v2/vitrine/analyses/{id}Suppression d'une analyse et de ses fichiers.
POST /v2/vitrine/generationGénération d'articles : category_ids (catégories existantes, 10 au plus), count (1 à 10 par catégorie), theme. Les brouillons sont de vrais articles, inactifs, marqués ai_draft, avec une image provisoire. 202.
GET /v2/vitrine/generation · /generation/{id}Générations récentes ; état d'une génération avec draft_articles.
POST /v2/vitrine/generation/confirmerlines [{id, name, price, description, category_id, variants}] : mis en vente (référencement IA planifié).
DELETE /v2/vitrine/generation/brouillonsEfface les brouillons IA non confirmés (confirm: true).
POST /v2/vitrine/referencementRégénère tout le référencement (accueil et articles) en tâche de fond ; les textes saisis à la main sont conservés. 202.
POST /v2/vitrine/referencement/vitrine · /referencement/articles/{id}Référencement immédiat de la page d'accueil ou d'un article (titre, description, mots-clés, textes alternatifs des photos).
Analyse de photos en trois temps
# 1. Lancer l'analyse (202)
curl -X POST https://cashchap.com/v2/vitrine/analyses \
  -H "Authorization: Bearer $CASHCHAP_CLE_SECRETE" -H "Content-Type: application/json" \
  -d "{\"photos\": [{\"base64\": \"$(base64 -w0 robe.jpg)\", \"filename\": \"robe.jpg\"}]}"

# 2. Interroger l'état jusqu'à "status": "completed"
curl https://cashchap.com/v2/vitrine/analyses/ANALYSE_ID -H "Authorization: Bearer $CASHCHAP_CLE_SECRETE"

# 3. Confirmer les brouillons corrigés (201)
curl -X POST https://cashchap.com/v2/vitrine/analyses/ANALYSE_ID/confirmer \
  -H "Authorization: Bearer $CASHCHAP_CLE_SECRETE" -H "Content-Type: application/json" \
  -d '{"lines": [{"index": 0, "name": "Robe en pagne wax", "price": 18000, "category": "Mode", "variants": "Taille M, Taille L"}], "active": true}'

Serveur MCP

Le serveur MCP officiel @cashchap/mcp expose chaque route de cette API sous la forme d'un outil vitrine_* pour les assistants (Claude Desktop, Claude Code, Cursor…) : vitrine_voir, vitrine_creer, vitrine_modifier, vitrine_publier, vitrine_categories_*, vitrine_articles_*, vitrine_photos_*, vitrine_livraison_*, vitrine_pages_*, vitrine_commandes_*, vitrine_messages_*, vitrine_analyse_*, vitrine_generation_*, vitrine_referencement_*. Il utilise la même clé secrète que les outils de paiement (CASHCHAP_API_KEY, mode live), appelle CASHCHAP_PORTAIL_URL (https://cashchap.com par défaut) et exige une confirmation explicite pour les actions destructrices. Variable CASHCHAP_MCP_VITRINE : true (gestion complète, défaut), lecture (consultation seule) ou false.

Codes d'erreur

Codes propres à l'API vitrine, en plus des codes communs de la norme v2 (missing_api_key, invalid_api_key, secret_key_required, validation_failed, rate_limited, idempotency_conflict…). Chaque erreur porte error.type, le code HTTP et un message en français.

CodeHTTPTypeMessage
invalid_json400invalid_requestLe corps de la requête doit être un objet JSON valide.
request_too_large413invalid_requestLe corps de la requête est trop lourd.
invalid_idempotency_key400invalid_requestLa clé d'idempotence doit contenir 1 à 128 caractères (lettres, chiffres, - et _).
route_not_found404not_foundCette route n'existe pas dans l'API vitrine. Consultez la documentation.
method_not_allowed405invalid_requestCette méthode HTTP n'est pas acceptée sur cette route.
missing_api_key401authentication_errorClé API absente : envoyez l'en-tête Authorization: Bearer <clé secrète>.
invalid_api_key401authentication_errorLa clé API est inconnue ou n'est plus active. Vérifiez la clé dans Développeurs › Clés API.
secret_key_required403permission_deniedL'API vitrine exige la clé SECRÈTE du service (SKLIVE_…), côté serveur uniquement.
live_key_required403permission_deniedLa vitrine est un objet Live : utilisez la clé secrète LIVE du service, jamais la clé Sandbox.
account_unavailable403permission_deniedLe compte de ce service est indisponible (supprimé ou suspendu). Contactez le support CashChap.
service_inactive403permission_deniedCe service n'existe pas ou a été désactivé.
account_not_activated403permission_deniedL'identité du promoteur doit être vérifiée (dossier d'activation validé) pour ouvrir ou publier une vitrine.
rate_limited429rate_limitedTrop de requêtes. Réessayez dans quelques instants.
request_in_progress409conflictUne requête avec cette clé d'idempotence est encore en cours. Réessayez dans une seconde.
idempotency_conflict409conflictCette clé d'idempotence a déjà servi pour une autre requête.
validation_failed422validation_errorLa requête contient des champs invalides.
confirmation_required422validation_errorCette action est irréversible : renvoyez-la avec "confirm": true.
internal_error500api_errorUne erreur interne est survenue. Réessayez dans un instant.
storefront_feature_disabled503service_unavailableLes vitrines ne sont pas encore ouvertes sur la plateforme CashChap.
storefront_not_found404not_foundCe service n'a pas encore de vitrine : créez-la avec POST /v2/vitrine.
storefront_already_exists409conflictCe service a déjà une vitrine : modifiez-la avec PATCH /v2/vitrine.
storefront_limit_reached409conflictVotre compte a atteint le nombre maximal de vitrines.
storefront_not_publishable409conflictAjoutez au moins un article actif avant de publier la vitrine.
slug_change_too_soon409conflictL'adresse ne peut être changée qu'une fois par 30 jours.
theme_unknown422validation_errorThème inconnu : consultez GET /v2/vitrine/themes.
category_not_found404not_foundCatégorie introuvable dans cette vitrine.
category_limit_reached409conflictLe catalogue est limité à 30 catégories.
category_name_taken409conflictUne catégorie porte déjà ce nom (ou un nom trop proche).
article_not_found404not_foundArticle introuvable dans cette vitrine.
article_limit_reached409conflictLe catalogue a atteint le nombre maximal d'articles (limite de la plateforme).
featured_limit_reached409conflictSix articles au plus peuvent être mis en avant. Retirez-en un d'abord.
photo_not_found404not_foundPhoto introuvable dans cette vitrine.
photo_limit_reached409conflictLimite de photos par article atteinte.
invalid_image422validation_errorL'image n'a pas pu être lue ou traitée.
malicious_file_rejected422validation_errorFichier refusé : son nom ou son contenu est suspect. Ce refus est inscrit dans votre journal d'activité.
remote_image_refused422validation_errorCette adresse d'image n'est pas acceptée.
remote_download_disabled403permission_deniedLe téléchargement d'images par adresse est fermé : envoyez l'image en base64.
demo_universe_unknown422validation_errorUnivers de démonstration inconnu.
page_type_unknown404not_foundType de page inconnu : about, delivery ou contact.
order_not_found404not_foundCommande introuvable dans cette vitrine.
order_not_cancellable409conflictCette commande ne peut plus être annulée.
delivery_state_invalid409conflictCette étape de livraison n'est pas possible pour cette commande.
message_not_found404not_foundMessage introuvable dans cette vitrine.
messages_unavailable503service_unavailableLes messages de la page Contact sont momentanément indisponibles.
message_reply_impossible422validation_errorImpossible de répondre à ce message par e-mail.
email_not_sent503service_unavailableL'e-mail n'a pas pu partir. Réessayez dans un instant.
ai_unavailable503service_unavailableL'assistance IA n'est pas disponible sur la plateforme pour le moment.
ai_quota_exceeded429rate_limitedQuota d'assistance IA atteint pour aujourd'hui. Réessayez demain.
ai_model_failed502provider_errorLe modèle n'a pas répondu : les textes actuels sont conservés. Réessayez dans un instant.
analysis_not_found404not_foundAnalyse de photos introuvable dans cette vitrine.
analysis_not_ready409conflictCette analyse n'est pas prête à être confirmée.
analysis_no_photo422validation_errorAucune photo exploitable dans cet envoi.
generation_not_found404not_foundGénération d'articles introuvable dans cette vitrine.