sendpage API
Create, track and delete showcase sites programmatically. Full v1 API reference.
https://sendpage.io/api/v1Authentification
Créez une clé depuis vos réglages, onglet API. Elle n'est affichée qu'une fois : nous n'en conservons qu'une empreinte, donc une clé perdue se remplace, elle ne se retrouve pas.
Authorization: Bearer sp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxN'utilisez jamais une clé depuis un navigateur. Elle porte l'identité complète de votre compte et serait lisible par quiconque inspecte la page. L'absence volontaire d'en-têtes CORS rend d'ailleurs l'appel direct impossible : c'est une garde, pas un oubli.
Crédits
Une création de site consomme un crédit, débité à la mise en file. Un job qui échoue définitivement, reprises épuisées, est remboursé. Le solde est renvoyé à chaque création dans credits_remaining, et l'API répond 402 quand il est épuisé.
Bornes
Les bornes sont par clé et non par compte : une intégration emballée ne bloque pas les autres. Un dépassement rend un 429 avec un en-tête Retry-After.
Créer un site
/sitesCrée le site et met sa génération en file. Répond 202 Accepted, et non 200 : le site existe, son contenu pas encore. Suivez le job pour savoir quand il est prêt.
| company_name | string | requis | 1 à 100 caractères |
| metier | string | requis | 1 à 100 caractères |
| ville | string | requis | 1 à 100 caractères |
| telephone | string | requis | 6 à 30 caractères |
| slug | string | optionnel | Réassaini côté serveur, et suffixé s'il est déjà pris |
| palette | string | optionnel | Défaut : minimaliste |
curl -X POST https://sendpage.io/api/v1/sites \
-H "Authorization: Bearer $SENDPAGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"company_name": "Plomberie Martin",
"metier": "plombier",
"ville": "Bordeaux",
"telephone": "0556000000"
}'{
"site_id": "3ffccc9b-da6b-4b1c-839d-0b16e02e7e29",
"slug": "plomberie-martin",
"job_id": "c7d00e6e-bf48-4bb1-a0d1-6f1a7496601e",
"status": "queued",
"credits_remaining": 9
}Suivre la génération
/jobs/{job_id}Interrogez toutes les quelques secondes. Un site prend une quinzaine de secondes une fois admis, davantage si la file est chargée.
{
"job_id": "c7d00e6e-bf48-4bb1-a0d1-6f1a7496601e",
"site_id": "3ffccc9b-da6b-4b1c-839d-0b16e02e7e29",
"status": "succeeded",
"attempts": 1,
"error": null,
"images_generated": 8,
"images_failed": 0,
"enqueued_at": 1788787628056,
"finished_at": 1788787643120
}status vaut queued, running, succeeded, failed ou cancelled. Un échec est repris automatiquement jusqu'à trois fois, avec une attente croissante. failed signifie que les reprises sont épuisées — et que le crédit a été remboursé.
Lister vos sites
/sitesDu plus récent au plus ancien, paginé par curseur.
| limit | number | optionnel | 1 à 100, défaut 25 |
| cursor | string | optionnel | next_cursor de la page précédente |
{
"sites": [
{
"site_id": "3ffccc9b-da6b-4b1c-839d-0b16e02e7e29",
"slug": "plomberie-martin",
"company_name": "Plomberie Martin",
"metier": "plombier",
"ville": "Bordeaux",
"status": "draft",
"generation_status": "ready",
"subdomain": "plomberie-martin",
"custom_domain": null,
"created_at": 1788787628056,
"updated_at": 1788787643120
}
],
"next_cursor": null
}Supprimer un site
/sites/{site_id}Supprime définitivement le site : contenu, factures, statistiques, demandes de contact, images, et libération du sous-domaine. Irréversible et sans confirmation — une API n'a pas de dialogue.
204 No ContentErreurs
Toutes les erreurs partagent la mĂŞme forme.
{
"error": {
"code": "no_credits",
"message": "Solde de crédits épuisé."
}
}| 400 | invalid_body | JSON ou champs invalides ; fields détaille lesquels |
| 401 | unauthorized | Clé absente, invalide ou révoquée |
| 402 | no_credits | Solde épuisé — rechargez ou attendez le renouvellement |
| 403 | subscription_inactive | Abonnement suspendu ou résilié |
| 404 | not_found | Ressource inexistante ou appartenant Ă un autre compte |
| 409 | slug_taken | Aucun slug libre dérivable |
| 429 | rate_limited | Borne dépassée ; voir l'en-tête Retry-After |
| 500 | internal_error | Erreur interne |
| 503 | unavailable | Indisponibilité momentanée, ou maintenance |
Un 404 ne distingue pas « inexistant » de « pas à vous », délibérément : la distinction permettrait d'énumérer les identifiants des autres comptes.
Intégrer proprement
Créez, puis suivez le job. Ne supposez pas le site prêt au retour du POST.
const create = await fetch("https://sendpage.io/api/v1/sites", {
method: "POST",
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ company_name, metier, ville, telephone }),
});
if (create.status === 402) throw new Error("crédits épuisés");
const { job_id, site_id } = await create.json();
// Le POST ne garantit PAS un site prĂŞt : on suit le job.
for (;;) {
await new Promise((r) => setTimeout(r, 3000));
const job = await (
await fetch(`https://sendpage.io/api/v1/jobs/${job_id}`, {
headers: { Authorization: `Bearer ${KEY}` },
})
).json();
if (job.status === "succeeded") break;
if (job.status === "failed") throw new Error(job.error);
}Lancer un lot d'un coup est prévu : la file admet les jobs à son rythme et sert les comptes à tour de rôle, de sorte qu'un gros lot n'affame jamais les autres clients — ni ne se fait affamer par eux.