Início rápido
Três passos da chave ao arquivo pronto. Um site leva em média cerca de um minuto para ser gerado, por isso o pedido retorna o número da tarefa, e não o site: depois você consulta o status e baixa o ZIP.
# 1. Pedir um site
curl -X POST https://exstru.com/api/v1/generate \
-H 'Authorization: Bearer sg_sua_chave' \
-H 'Content-Type: application/json' \
-d '{ "tier": "premium", "siteType": "local-business",
"niche": "odontologia", "language": "es", "country": "ES" }'
# → { "jobId": "V1StGXR8Z5jd", "queue": { "position": 3, … } }
# 2. Consultar o status a cada 3–5 segundos
curl https://exstru.com/api/v1/status/V1StGXR8Z5jd \
-H 'Authorization: Bearer sg_sua_chave'
# 3. Baixar o arquivo quando status = completed
curl -o site.zip https://exstru.com/api/v1/download/V1StGXR8Z5jd \
-H 'Authorization: Bearer sg_sua_chave'Acesso e chave
A API é vinculada à equipe. Uma chave no formato sg_… é emitida pelo dono na seção “API” do bot — lá mesmo ela é reemitida e revogada. Os créditos dos sites são debitados do saldo compartilhado da equipe.
Como enviar a chave
Em qualquer um dos dois cabeçalhos — eles são equivalentes. O segundo serve para quando a sua biblioteca já ocupa Authorization.
Authorization: Bearer sg_sua_chave X-Api-Key: sg_sua_chave
Planos na requisição
O plano é enviado no campo tier e define quantos créditos serão debitados e o que o site recebe. A assinatura da API não limita o plano: com uma mesma chave, dá para pedir qualquer um.
basicpremiumeliteA comparação completa dos recursos está na página “Preços”.
Pedir um site
Coloca um site na fila e debita os créditos conforme o plano. A resposta chega na hora — com o número da tarefa e a posição na fila.
Parâmetros do corpo da requisição
Regras dos campos
Os erros de formato são retornados todos de uma vez: 400 invalid_request, listados em data.issues. Nenhum crédito é debitado. Campos desconhecidos são ignorados.
companyInfoobjectSe o objeto for enviado, name, email e phone são obrigatórios. Sem o objeto, o gerador inventa o nome e os contatos. Todos os campos são strings.
brandColorstring- Formato —
#rrggbbou o curto#rgb, sem diferenciar maiúsculas e minúsculas; espaços nas pontas são removidos.#1E6FD9e#1e6fd9— a mesma cor,#16dé lido como#1166dd. - String vazia ou campo ausente — a paleta é escolhida para o nicho.
- Outros formatos —
rgb(), nomes de cores, código sem#— erro400 invalid_request. - A cor não é copiada exatamente: cada site recebe um tom próximo, só dele, para que os sites de uma mesma equipe não se repitam.
- No Basic, o campo é aceito, mas não aplicado — aviso
brand_color_not_in_tier.
baseUrlstring · até 253 caracteres- Domínio em caracteres latinos: letras, números e hífen, com pelo menos um ponto. Domínios em cirílico — em punycode:
xn--… - O esquema é opcional:
http://ehttps://são aceitos; os links do site sempre usarãohttps. wwwé mantido — você decide qual host é o canônico. Um caminho é permitido para um site em subpasta.- Barra no final, parâmetros
?…e a âncora#…são descartados. - Não são aceitos: espaços, porta, endereço IP, login no endereço (
user@) — erro400 invalid_request. - Sem domínio, o site é gerado sem
sitemap.xml,canonical,og:urlehreflang: eles precisam do endereço completo.
extraLanguagesstring[]- Códigos — como em
language: códigos ISO 639-1 de duas letras, em minúsculas, da lista de idiomas suportados. Variantes regionais (en-GB,pt-BR) não são aceitos. - O idioma principal fica na raiz do site e é marcado como
x-default. Os idiomas adicionais ficam em subpastas/en/,/de/na ordem do array. - Repetições e o idioma principal dentro de
extraLanguagessão ignorados e não contam para o limite. - Limite do plano — Premium 2, Elite 9 adicionais, Basic 0. Os idiomas excedentes são descartados a partir do fim do array, e o preço do pedido não muda — aviso
language_limit. - Código não suportado em
languageouextraLanguages— erro400 invalid_request. - Se o nicho não puder ser feito em um dos idiomas (por exemplo, VPN em russo) —
422 niche_language_refused, e o pedido não é criado. Remova esse idioma e tente de novo.
"tier": "premium",
"language": "es",
"extraLanguages": ["es", "en", "en", "de", "fr"]
→ idiomas do site: es (raiz), /en/, /de/
→ "warnings": [{
"code": "language_limit",
"field": "extraLanguages",
"message": "Premium: até 3 idiomas, fr não adicionado"
}]countrystring · 2 letras- Código ISO 3166-1, sem diferenciar maiúsculas e minúsculas:
mxeMX— é o mesmo país. - Suportados:
US GB CA AU IE NZ DE AT CH ES MX CO CL FR BE RU KZ IT PT BR NL PL CZ RO SE DK NO. - O país não muda o idioma do site — quem define é
language. Um site em inglês para uma empresa alemã é"language": "en", "country": "DE": preços em euros, telefone+49, Impressum em inglês. - Campo ausente ou código fora da lista — erro
400 invalid_requestcom o campocountry, e nenhum crédito é debitado.
"language": "es", "country": "MX" → preços em pesos → telefone +52 55 1234 5678 → <html lang="es-MX">
archiveFormatstring · html | phphtml(padrão) — site estático: qualquer hospedagem, Netlify, GitHub Pages.php— as mesmas páginas, mais um processador de solicitações: os formulários as enviam paracompanyInfo.email. Precisa de hospedagem com PHP 7.0 ou mais recente, com envio de e-mail funcionando (mail()).- Sem
companyInfo.emailo site recebe contatos de demonstração e não há e-mail para as solicitações: os formulários não as aceitam até você colocar um endereço no início do arquivo PHP. - O preço não depende do formato. Qualquer outro valor é erro
400 invalid_request.
Exemplo
POST /api/v1/generate
Authorization: Bearer sg_sua_chave
Content-Type: application/json
{
"tier": "premium",
"siteType": "local-business",
"niche": "odontologia",
"language": "es",
"country": "ES",
"extraLanguages": ["en", "de"],
"baseUrl": "sonrisa-clinica.es",
"brandColor": "#1e6fd9",
"keywords": ["implantes", "ortodoncia"],
"companyInfo": {
"name": "Sonrisa",
"email": "hola@sonrisa.es",
"phone": "+34 600 000 000"
}
}{
"success": true,
"jobId": "V1StGXR8Z5jd",
"queue": {
"position": 3,
"etaSeconds": 90
},
"warnings": []
}Campos da resposta
Ver o status
Mostra em que etapa o pedido está. Consulte a cada 3–5 segundos, até o status ser final: completed, failed ou cancelled.
{
"jobId": "V1StGXR8Z5jd",
"status": "processing",
"queuePosition": null,
"etaSeconds": null,
"progress": 55,
"step": "Textos prontos, escolhendo as fotos…",
"error": null,
"ready": false
}Status
Cancelar um pedido
Funciona enquanto o pedido aguarda na fila (pending). Os créditos voltam para o saldo na hora. Se a geração já começou, não é possível cancelar — a resposta é 409 not_waiting.
{ "jobId": "V1StGXR8Z5jd", "status": "cancelled", "refunded": true }Baixar o arquivo
Retorna application/zip em fluxo, quando o status é completed. O arquivo fica guardado por 14 dias e pode ser baixado quantas vezes quiser. Depois disso, a resposta é 410 gone.
Erros
O código de erro legível por máquina fica em data.code. Baseie-se nele: o texto da mensagem pode mudar.
{
"statusCode": 422,
"message": "Não geramos este nicho: …",
"data": {
"code": "niche_refused",
"category": "gambling",
"reason": "jogos de azar",
"alternatives": [
{ "label": "clube de jogos de tabuleiro", "canonical": null }
]
}
}A tarefa de outra equipe retorna o mesmo 404 de uma tarefa inexistente — assim não dá para descobrir números de tarefa por tentativa e erro.
Limites e fila
Em caso de 429 a resposta traz o cabeçalho Retry-After com o número de segundos. Aguarde: repetir antes do prazo não contorna o limite, só o prolonga. Os limites são contados por chave, e não por endereço IP.