Templates API

The Templates API lets you browse the active organization catalog, create and revise starting points for HTTP applications, and start a project from a specific revision. Routes require an authenticated user or organization API key and the x-organization-id context.

Authentication and context

Send a user token in Authorization: Bearer <token> or an organization API key according to your integration's authentication flow. Include x-organization-id in every call. The active organization defines private templates and the destination of a new project.

Authorization: Bearer <token>
x-organization-id: <organization-id>
Content-Type: application/json

The public catalog still requires an authenticated session. Public read access does not grant permission to create projects or change templates.

List templates

GET /v1/templates?scope=organization&sort=updated&page=1&limit=20
ParameterRequiredValues
scopeYesorganization or public
sortYesupdated, newest, or most_used
officialNotrue or false; available with scope=public
pageNoPage starting at 1
limitNoUp to 50 items

organization returns templates accessible in the active organization, sorted by last update. public returns published templates. most_used ranks by unique consumer organizations and uses completed deployments as a tie-breaker. Use official=true to return only Templates published by Zenifra or official partners.

Each returned Template includes only the public name of the author organization. The organization identifier is not shared. owned_by_current_organization indicates whether the Template belongs to the active organization, while official indicates whether the author is an official publisher. Both fields are informational and do not grant permissions.

{
  "author": {
    "organization_name": "Author organization"
  },
  "owned_by_current_organization": false,
  "official": true
}

Read, create, and update

GET /v1/templates/:id
POST /v1/templates
PATCH /v1/templates/:id

Create and update bodies contain name, summary, description, tags, application, variables, and defaults. The application contains reference, port, and storage_path; defaults contain plan, billing, exposure, instances, auto-scaling, and storage. A template that needs a database can also include an optional database_dependency.

Public references are accepted for Docker Hub, GHCR, Quay, and GitLab Registry. Zenifra validates the application before accepting a revision. Do not send credentials, tokens, or secret values in a template.

Every update requires expected_revision:

{
  "expected_revision": 2,
  "name": "support-api v2",
  "summary": "HTTP API for new projects",
  "description": "A safe starting point for an API.",
  "tags": ["api", "support"],
  "application": {
    "reference": "ghcr.io/example/support:2.1.0",
    "port": 3000,
    "storage_path": "/app/data"
  },
  "variables": [
    { "name": "APP_ENV", "description": "Environment", "required": true, "secret": false, "default_value": "production" },
    { "name": "APP_SECRET", "description": "Application secret", "required": true, "secret": true }
  ],
  "defaults": {
    "plan": "basic",
    "payment_mode": "monthly",
    "exposure": "public",
    "instances": 1,
    "storage": { "persistent": true, "capacity": 10 }
  }
}

An outdated revision returns 409 Conflict. Secret variables cannot have default_value.

Database dependency

Get the current database catalog before creating or editing a dependency:

GET /v1/project/database/catalog

The response lists engine IDs, plans, billing modes, configuration fields, and connection fields. Use these IDs rather than maintaining a fixed list in your integration. A database_dependency declares compatible engines, one recommendation for each engine, and optional variable bindings:

{
  "database_dependency": {
    "compatible_engine_ids": ["postgresql", "mariadb"],
    "recommended_engine_id": "postgresql",
    "recommendations": [
      { "engine_id": "postgresql", "plan_id": "db-basic", "payment_mode": "monthly", "configuration": { "version": "18", "instances": 1, "storage_capacity_gb": 10 } },
      { "engine_id": "mariadb", "plan_id": "db-basic", "payment_mode": "monthly", "configuration": { "version": "11", "instances": 3, "storage_capacity_gb": 10 } }
    ],
    "environment_bindings": [
      { "variable_name": "DB_PASSWORD", "connection_field_id": "password" },
      { "variable_name": "DB_HOST", "connection_field_id": "host" }
    ]
  }
}

Each binding must reference a declared variable and a connection field supported by every compatible engine. Bindings for sensitive fields require a secret variable and cannot have a default value. Non-sensitive bindings can provide a suggested value for an external database; a combined deployment replaces it with the newly created connection. Omitting database_dependency creates a template without a database dependency. When updating, omit it to preserve the current dependency or send null to remove it.

Publish and remove

PATCH /v1/templates/:id/visibility
DELETE /v1/templates/:id

The visibility body is { "visibility": "public", "expected_revision": 2 }. Creation starts private. Publishing or making a template private requires template.publish; removal requires template.delete and expected_revision. Removal is logical and does not alter existing projects.

Create a project from a template

Use the alternate payload of POST /v1/project:

{
  "template_id": "507f1f77bcf86cd799439011",
  "template_revision": 2,
  "name": "support-api",
  "description": "Project created from a template",
  "plan": "basic",
  "payment_mode": "monthly",
  "config": {
    "exposure": "public",
    "instances": 1,
    "storage": { "persistent": true, "capacity": 10 },
    "envs": [
      { "name": "APP_ENV", "value": "production" },
      { "name": "APP_SECRET", "value": "provided-at-deploy-time" }
    ],
    "network_access": {
      "ingress_white_list": [{ "cidr": "0.0.0.0/0", "description": "Project access" }],
      "ingress_black_list": []
    }
  }
}

The application, port, HTTP type, and persistent path come from the template revision. The request must contain exactly the declared variables. Deployment requires project.create and, for a private template, template.read. Failed deployments do not count usage or change ranking.

Create an application and database together

For a template with a database dependency, create both resources with:

POST /v1/templates/:id/deployments
Idempotency-Key: <unique-key-with-at-least-16-characters>

The body includes expected_revision, the regular application configuration, and the selected database:

{
  "expected_revision": 2,
  "application": { "name": "support-api", "plan": "basic", "payment_mode": "monthly", "config": { "exposure": "public", "instances": 1, "storage": { "persistent": true, "capacity": 10 }, "envs": [{ "name": "APP_ENV", "value": "production" }], "network_access": { "ingress_white_list": [], "ingress_black_list": [] } } },
  "database": { "name": "support-db", "engine_id": "postgresql", "plan_id": "db-basic", "payment_mode": "monthly", "configuration": { "version": "18", "instances": 1, "storage_capacity_gb": 10 } }
}

The request requires project.create and database.create. The response contains a deployment ID, status, and project IDs only; it never contains connection credentials. Reuse the same idempotency key and identical body to safely retry. A changed body with the same key returns 409 Conflict.

GET /v1/template-deployments/:deploymentId

Use this endpoint to follow creating, ready, rolling_back, or failed. A successful database and application remain independent projects after creation.

Limits

Reads are limited to 100 requests per minute. Creation and updates are limited to 10 per minute. Publishing and removal are limited to 5 per minute. Respect 429 responses and wait before retrying.

Next steps