Criar, listar e remover projetos

Esta página cobre o ciclo básico de um projeto pela API: consultar os catálogos públicos de planos, criar o projeto com POST /v1/project, listá-lo e removê-lo com DELETE /v1/project/:id. Para consultar ou alterar um projeto existente, veja Informações de projetos.

Autenticação e permissões

Criação, listagem e remoção aceitam uma API Key da organização (x-api-key: znf_... ou Authorization: Bearer znf_...) ou um token de usuário com x-organization-id. Os catálogos de planos são públicos e não exigem autenticação.

OperaçãoScope mínimo
Criar aplicação HTTP ou Job agendadoproject.create em organization:*
Criar PostgreSQL, MariaDB ou ClickHousedatabase.create em organization:*
Criar Valkey (Key-Value, Cache ou Queue)managed_service.create em organization:*
Listar projetosNenhum scope extra; a lista traz apenas os projetos que a credencial pode ler
Remover projetoproject.delete em project:<project-id>

owner tem acesso completo. Sem o scope correspondente ao tipo informado em config.type_project, a API responde 403 com insufficient organization permission.

Catálogos públicos

Consulte os catálogos antes de criar um projeto: eles informam os planos aceitos, os preços vigentes e o que cada plano inclui. Os valores monetários são números em centavos de BRL. Não fixe preços em automações.

RotaConteúdoLimite
GET /v1/project/plansPlanos de aplicações HTTP50 por minuto
GET /v1/project/job/plansPlanos de Jobs agendados60 por minuto
GET /v1/project/storage/plansPreço do armazenamento50 por minuto
GET /v1/project/database/plansPlanos de bancos e capacidade por mecanismo10 por minuto
GET /v1/project/database/catalogMecanismos de banco e campos de configuração100 por minuto

Planos de aplicações HTTP

GET /v1/project/plans
{
  "status": "success",
  "message": "get plans with success",
  "data": [
    {
      "plan": "premium",
      "prices": { "hourly": 0, "monthly": 0, "yearly": 0 },
      "features": ["..."],
      "permissions": { },
      "capabilities": {
        "logs": true,
        "metrics": true,
        "healthcheck": true,
        "autoscaling": true,
        "custom_subdomain": true,
        "network_access": true
      }
    }
  ]
}

Os preços do exemplo são ilustrativos; leia os valores da resposta. A lista vem ordenada pelo preço por hora, e os planos gratuitos retornam preços 0. O objeto capabilities indica, com valores booleanos, o que o plano inclui. logs é sempre true, porque os logs da aplicação estão disponíveis em todos os planos; os demais campos indicam se o plano inclui métricas, health check, auto-scaling, subdomínio personalizado e controle de acesso de rede. Prefira capabilities para decidir o que oferecer em integrações.

Planos de Jobs agendados

GET /v1/project/job/plans retorna os planos job-* com price_per_minute, currency, payment_mode: "per_minute", features e permissions. Quando os Jobs estão temporariamente indisponíveis, a rota responde 503 com SCHEDULED_JOBS_UNAVAILABLE. Detalhes em Jobs agendados.

Armazenamento

GET /v1/project/storage/plans retorna uma entrada por tipo de armazenamento:

{
  "status": "success",
  "message": "get storage prices with success",
  "data": [
    {
      "storage": "1gb_persistente",
      "prices": { "hourly": 0, "monthly": 0, "yearly": 0 },
      "persistent": true
    }
  ]
}

Os preços se referem a 1 GB. O valor por hora corresponde ao valor mensal dividido por 720, e o anual a 12 vezes o mensal.

Planos de bancos de dados

GET /v1/project/database/plans retorna os planos db-* (PostgreSQL, MariaDB e Key-Value) e analytics-* (ClickHouse). Cada item traz plan, prices (hourly, monthly, yearly), features e engines, com a capacidade aceita por mecanismo:

{
  "plan": "db-basic",
  "prices": { "hourly": 0, "monthly": 0, "yearly": 0 },
  "features": ["..."],
  "engines": {
    "postgresql": {
      "instances": { "min": 1, "default": 1, "max": 3, "editable": true },
      "storage": { "min": 1, "default": 1, "max": 250, "editable": true, "included": 0, "step": 1 },
      "versions": ["15", "16", "17", "18"],
      "default_version": "18",
      "billing": { "instances": "per_instance", "storage": "per_instance_gb" }
    }
  }
}

Os números do exemplo são ilustrativos. Se o catálogo estiver indisponível, a rota responde 503 com database catalog is temporarily unavailable.

Catálogo de mecanismos de banco

GET /v1/project/database/catalog organiza as mesmas informações por mecanismo. Cada item traz id, name, versions, plans (com id, payment_modes e capability), configuration_fields (versão, instâncias e capacidade em GB; os limites de cada plano ficam em plans[].capability) e connection_fields, que descreve os dados de conexão entregues pelo produto. A resposta usa o envelope { "status": "success", "data": [...] } e pode responder 503 nas mesmas condições do catálogo de planos.

Criar um projeto

POST /v1/project
HeaderObrigatórioDescrição
x-api-key ou AuthorizationSimAPI Key da organização ou token de usuário
x-organization-idCom token de usuárioOrganização ativa
Content-Type: application/jsonSimFormato do corpo
Idempotency-KeyObrigatório para Valkey16 a 200 caracteres entre letras, números, ., _ e -

Limite: 10 requisições por minuto.

Campos comuns

CampoTipoObrigatórioDescrição
namestringSim6 a 32 caracteres, letras minúsculas, números e hífens entre eles
descriptionstringNão1 a 256 caracteres
planstringSimPlano compatível com o tipo do projeto (veja os catálogos)
payment_modestringSimhourly, monthly ou yearly; Jobs usam somente per_minute
configobjectSimConfiguração por tipo; config.type_project é http, postgresql, mariadb, clickhouse, valkey ou job

Os números do corpo devem ser enviados como números JSON, não como strings. Campos que não pertencem ao tipo informado são rejeitados com 400.

Aplicação HTTP a partir de imagem

Campo de configTipoObrigatórioDescrição
type_projectstringSimhttp
exposurestringSimpublic ou private
image.urlstringSimReferência da imagem, 8 a 256 caracteres, sem http:// ou https://
image.is_publicbooleanSimfalse exige image.authentication
image.authenticationobjectPara imagem privadaauth_type: "username_password" com username e token, ou auth_type: "aws" com aws.access_key_id, aws.secret_access_key, aws.region e aws.account_id
portintegerSimPorta em que a aplicação escuta
instancesnumberSimQuantidade inicial de instâncias
storage.persistentbooleanSimSe o armazenamento é persistente
storage.capacitynumberSim1 a 250 GB; no plano free, exatamente 1
storage.dir_path_to_persiststringCom persistent: trueCaminho absoluto persistido, até 80 caracteres
envsarraySimAté 50 itens { "name", "value" }; name com 1 a 120 caracteres e value com 1 a 32.760. Use [] para nenhuma
network_accessobjectSimingress_white_list (1 a 10 itens) e ingress_black_list (até 10 itens), cada item com cidr e description. Veja Acesso de rede
subdomainstringNão3 a 54 caracteres; proibido com exposure: private
custom_domainsarrayNãoAté 20 domínios; deve ficar vazio com exposure: private
healthcheckobjectNão{ "enabled": true, "path": "/health" }. Exige plano com capabilities.healthcheck: true. Veja Health check
autoscalingobjectNãoenabled: true, max_instances maior ou igual a instances e alvos opcionais de CPU e memória de 1 a 100. Exige plano com capabilities.autoscaling: true
curl -X POST "https://api.zenifra.com/v1/project" \
  -H "x-api-key: znf_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "minha-api",
    "plan": "basic",
    "payment_mode": "hourly",
    "config": {
      "type_project": "http",
      "exposure": "public",
      "image": { "url": "ghcr.io/minha-org/minha-api:1.4.2", "is_public": true },
      "port": 8080,
      "instances": 1,
      "storage": { "persistent": false, "capacity": 1 },
      "envs": [{ "name": "NODE_ENV", "value": "production" }],
      "network_access": {
        "ingress_white_list": [{ "cidr": "0.0.0.0/0", "description": "Acesso público" }],
        "ingress_black_list": []
      }
    }
  }'
{
  "status": "success",
  "message": "create a project with success",
  "data": {
    "project_id": "6650f1a2b3c4d5e6f7a8b9c0",
    "domain": "<domínio-do-projeto>",
    "type": "application"
  }
}

Para imagens em registros privados, veja Registro privado.

Aplicação HTTP a partir de um repositório Git

Substitua image por source e build, que devem ser enviados juntos. A conexão precisa existir antes; veja Conexões Git.

CampoTipoObrigatórioDescrição
source.connection_idstringSimID da conexão Git da organização
source.repository_idstringSimRepositório selecionado na conexão (até 1.024 caracteres)
source.branchstringSimBranch publicada (até 255 caracteres)
source.auto_deploybooleanSimPublica a cada push na branch
source.version_deployobjectNãoenabled, event (tag ou release), tag_pattern e include_prereleases. Não pode ser habilitado junto com auto_deploy
build.runtimestringNãoRuntime do catálogo GET /v1/git/runtime-catalog
build.versionstringNãoVersão aceita pelo runtime
build.start_command, build.pre_build_command, build.build_commandstringNãoAté 256 caracteres
build.dockerfile_pathstringNãoAté 256 caracteres
build.context_pathstringNãoDiretório raiz do build, relativo à raiz do repositório. Padrão ".". Veja Diretório raiz do build
{
  "name": "minha-api",
  "plan": "basic",
  "payment_mode": "hourly",
  "config": {
    "type_project": "http",
    "exposure": "public",
    "source": {
      "connection_id": "<connection-id>",
      "repository_id": "<repository-id>",
      "branch": "main",
      "auto_deploy": true
    },
    "build": { "start_command": "npm start" },
    "port": 3000,
    "instances": 1,
    "storage": { "persistent": false, "capacity": 1 },
    "envs": [],
    "network_access": {
      "ingress_white_list": [{ "cidr": "0.0.0.0/0", "description": "Acesso público" }],
      "ingress_black_list": []
    }
  }
}

A resposta 201 inclui build_id do primeiro build, que você acompanha em Builds de repositórios Git. Para repositórios GitHub, a criação continua usando o campo github; source com a conexão GitHub responde 409 com GIT_SOURCE_CREATION_UNAVAILABLE. Em github, o campo opcional context_path segue as mesmas regras de build.context_path.

Diretório raiz do build

build.context_path (ou github.context_path na criação GitHub) define o diretório raiz do build: a pasta do repositório onde a instalação de dependências, o pre-build, o build e o start são executados. Use esse campo para publicar uma aplicação que está em uma subpasta de um monorepo.

  • Opcional; o padrão é ".", a raiz do repositório.
  • O valor é normalizado: espaços nas pontas, ./ no início e / no fim são removidos. " ./apps/api/ " é salvo como "apps/api", e um valor vazio vira ".".
  • Aceita somente caminhos relativos dentro do repositório, com / como separador e até 256 caracteres.
  • Caminhos absolutos (/app), segmentos .. ou . (apps/../api), barras invertidas (\) e caracteres de controle são rejeitados com 400.
  • Somente o conteúdo da pasta entra no build. Se ela não existir na branch publicada, o build falha informando que o diretório raiz configurado não existe no repositório.
  • A pasta .git do repositório não entra no build nem na aplicação publicada. Um context_path apontando para .git é tratado como pasta inexistente.
{
  "build": {
    "start_command": "npm start",
    "build_command": "npm run build",
    "context_path": "apps/api"
  }
}

PostgreSQL, MariaDB e ClickHouse

Campo de configTipoObrigatórioDescrição
type_projectstringSimpostgresql, mariadb ou clickhouse
versionstringSimPostgreSQL: 15, 16, 17 ou 18. MariaDB: 10 ou 11. ClickHouse: 26.8.6.5
instancesnumberSimPostgreSQL: 1 a 3 (no db-free, 1). MariaDB: exatamente 3. ClickHouse: conforme o catálogo do plano
storage.persistentbooleanSimPersistência dos dados
storage.capacitynumberSim1 a 250 GB; nos planos gratuitos, exatamente 1
envsarraySimMesmas regras da aplicação HTTP; use []
network_accessobjectSimMesmo formato da aplicação HTTP

Use planos db-* para PostgreSQL e MariaDB (MariaDB não está disponível no db-free) e planos analytics-* para ClickHouse.

{
  "name": "banco-pedidos",
  "plan": "db-basic",
  "payment_mode": "hourly",
  "config": {
    "type_project": "postgresql",
    "version": "18",
    "instances": 1,
    "storage": { "persistent": true, "capacity": 10 },
    "envs": [],
    "network_access": {
      "ingress_white_list": [{ "cidr": "203.0.113.0/24", "description": "Escritório" }],
      "ingress_black_list": []
    }
  }
}
{
  "status": "success",
  "message": "create a project with success",
  "data": {
    "project_id": "6650f1a2b3c4d5e6f7a8b9c1",
    "type": "database",
    "database": {
      "connection": {
        "host": "<host>",
        "port": 5432,
        "database": "<database>",
        "username": "<username>",
        "password": "<senha>",
        "connectionString": "postgresql://<username>:<senha>@<host>:5432/<database>"
      },
      "version": "18",
      "replicas": 1
    }
  }
}

A senha só aparece na criação e na renovação de credenciais. Guarde-a em local seguro. Se os dados de conexão ainda não estiverem prontos, connection pode vir null; consulte depois GET /v1/project/:id/database/connection. Veja também Bancos de dados e ClickHouse.

Valkey: Key-Value, Cache e Queue

Campo de configTipoObrigatórioDescrição
type_projectstringSimvalkey
profilestringSimkey_value, cache ou queue
versionstringSim9.1.1
storageobjectPara key_value e queue{ "persistent": true, "capacity": 1-250 }; proibido em cache
network_accessobjectNãoMesmo formato, com ingress_white_list de 0 a 10 itens

O plano precisa combinar com o perfil: db-* para key_value, cache-* para cache e queue-* para queue. O header Idempotency-Key é obrigatório. Ao repetir a mesma requisição com a mesma chave, a API devolve o mesmo projeto sem criar outro; a mesma chave com outro corpo responde 409 com IDEMPOTENCY_KEY_CONFLICT.

curl -X POST "https://api.zenifra.com/v1/project" \
  -H "x-api-key: znf_..." \
  -H "Idempotency-Key: cache-sessoes-2026-10-08-001" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "cache-sessoes",
    "plan": "cache-basic",
    "payment_mode": "hourly",
    "config": { "type_project": "valkey", "profile": "cache", "version": "9.1.1" }
  }'

A resposta 201 (message: "project created") traz project_id, status, type: "managed_service" e managed_service com engine, profile, version, username, host, port, tls e connection_string. A connection_string com a senha só aparece na primeira resposta; uma repetição com a mesma chave devolve os demais campos. Veja Serviços gerenciados, Cache e Filas.

Outros tipos

  • Jobs agendados (type_project: "job"): planos job-*, payment_mode: "per_minute" e config.job.cron. Veja Jobs agendados.
  • Aplicação a partir de template: corpo alternativo com template_id e template_revision. Veja Templates.

Erros da criação

CódigoSituação
400Corpo inválido, plano incompatível com o tipo ("plan" must match "config.type_project"), plano inexistente, versão não suportada, IDEMPOTENCY_KEY_REQUIRED em Valkey ou modo de cobrança inválido (PAYMENT_MODE_INVALID, SCHEDULED_JOB_PAYMENT_MODE_INVALID)
402Organização bloqueada por pagamento pendente
403Scope insuficiente para o tipo de projeto, ou plano sem health check (HEALTHCHECK_NOT_AVAILABLE_FOR_PLAN) ou sem auto-scaling
409Limite de planos gratuitos atingido, capacidade indisponível (INSUFFICIENT_PLAN_CAPACITY), domínio indisponível, conflito de idempotência ou conexão Git indisponível (GIT_CONNECTION_UNAVAILABLE), ou origem Git indisponível para criação direta (GIT_SOURCE_CREATION_UNAVAILABLE)
429Limite de requisições excedido
503Recurso temporariamente indisponível, como PLAN_CAPACITY_TEMPORARILY_UNAVAILABLE ou ORGANIZATION_RUNTIME_UNAVAILABLE; tente novamente

Limites do plano Free

Quando a criação esbarra em um limite gratuito, a resposta 409 traz code e limit, além de message:

codeCampos extrasSituação
FREE_PLAN_INSTANCE_LIMIT_REACHEDlimit: 2A organização já usa as 2 instâncias do plano free
FREE_PLAN_INSTANCE_LIMIT_EXCEEDEDlimit: 2, remainingAs instâncias pedidas passam do que ainda resta no plano free; remaining informa quantas ainda cabem
DB_FREE_PROJECT_LIMIT_REACHEDlimit: 2A organização já tem 2 projetos db-free ativos
{
  "status": "failed",
  "code": "FREE_PLAN_INSTANCE_LIMIT_REACHED",
  "limit": 2,
  "message": "You have reached the limit of 2 instances on the free plan. Delete an existing project to create a new one."
}

Prefira code para tratar o erro no seu código; message é um texto legível para pessoas.

Nos planos gratuitos, a organização pode ter no máximo 2 instâncias no plano free, 2 projetos no db-free (PostgreSQL e Key-Value somados) e 1 serviço por perfil nos planos cache-free e queue-free.

Listar projetos

GET /v1/project

Aceita page (padrão 1), limit (1 a 50, padrão 15), type (http, postgresql, mariadb ou valkey), profile (somente com type=valkey), status, search (1 a 256 caracteres, busca por nome ou descrição), sort_by (updated_at ou created_at) e sort_order (asc ou desc). A resposta traz data.projects e data.pagination com page, limit, total e pages. Limite: 100 requisições por minuto. Os campos de cada projeto estão em Informações de projetos.

Remover um projeto

DELETE /v1/project/:id
curl -X DELETE "https://api.zenifra.com/v1/project/6650f1a2b3c4d5e6f7a8b9c0" \
  -H "x-api-key: znf_..."
{
  "status": "success",
  "message": "deleted the project with success"
}

Projetos Valkey são removidos em segundo plano: a resposta é 202 com { "project_id": "...", "status": "deleting" }. A remoção é definitiva; faça backup dos dados antes. Limite: 10 requisições por minuto.

CódigoSituação
404Projeto não encontrado na organização
409Projeto já removido, execuções de Job ainda em finalização (JOB_RUNS_PENDING) ou operação do serviço gerenciado em andamento
503Falha temporária ao liberar o domínio; tente novamente

Próximos passos

Última atualização em

Nessa página