sendpage API

Create, track and delete showcase sites programmatically. Full v1 API reference.

https://sendpage.io/api/v1

Authentification

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.

En-tĂŞte
Authorization: Bearer sp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

N'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

Requêtes60 par minute et par clé
Créations de site200 par heure et par clé

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

POST/sites

Cré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.

Corps de la requĂŞte
company_namestringrequis1 à 100 caractères
metierstringrequis1 à 100 caractères
villestringrequis1 à 100 caractères
telephonestringrequis6 à 30 caractères
slugstringoptionnelRéassaini côté serveur, et suffixé s'il est déjà pris
palettestringoptionnelDéfaut : minimaliste
RequĂŞte
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"
  }'
Réponse
{
  "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

GET/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.

Réponse
{
  "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

GET/sites

Du plus récent au plus ancien, paginé par curseur.

Paramètres d'URL
limitnumberoptionnel1 à 100, défaut 25
cursorstringoptionnelnext_cursor de la page précédente
Réponse
{
  "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

DELETE/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.

Réponse
204 No Content

Erreurs

Toutes les erreurs partagent la mĂŞme forme.

{
  "error": {
    "code": "no_credits",
    "message": "Solde de crédits épuisé."
  }
}
400invalid_bodyJSON ou champs invalides ; fields détaille lesquels
401unauthorizedClé absente, invalide ou révoquée
402no_creditsSolde épuisé — rechargez ou attendez le renouvellement
403subscription_inactiveAbonnement suspendu ou résilié
404not_foundRessource inexistante ou appartenant Ă  un autre compte
409slug_takenAucun slug libre dérivable
429rate_limitedBorne dépassée ; voir l'en-tête Retry-After
500internal_errorErreur interne
503unavailableIndisponibilité 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.

JavaScript
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.