Templates API

The Templates API lets you browse the public catalog or the active organization catalog, create and revise starting points for HTTP applications, and start a project from a specific revision. Management routes require an authenticated user or organization API key and the x-organization-id context. The official public listing has a separate unauthenticated route.

Authentication and context

Send a user token in the Authorization header with the Bearer scheme or an organization API key according to your integration's authentication flow. Include x-organization-id in authenticated calls. 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

GET /v1/public/templates is the only route in this reference that requires neither Authorization nor x-organization-id. It only exposes the official public catalog. Creating projects or changing templates still requires authentication and the corresponding permissions.

Required permissions

Use only the scope for the requested action and the template ID when operating on a specific template:

ActionMinimum scope
List or read a templatetemplate.read on template:<template-id>; public templates follow the public contract
Create a private templatetemplate.create on organization:*
Update a templatetemplate.update on template:<template-id>
Publish or make privatetemplate.publish on template:<template-id>
Remove a templatetemplate.delete on template:<template-id>
Create a project from a templatetemplate.read on template:<template-id> and the corresponding project creation scope

An owner has full access in a user session. assistant, member, and organization API Keys need the specific grants. A template that creates a database also requires database.create on organization:*; a template that creates cache or queue requires managed_service.create on organization:*.

List the official public catalog

Use this route in websites, showcases, and other integrations that need to display official Templates without opening a user session:

GET /v1/public/templates?sort=most_used&page=1&limit=12

Do not send Authorization or x-organization-id with this request.

ParameterRequiredValues and default
sortNomost_used (default), newest, or updated
pageNoPage starting at 1; default 1
limitNoFrom 1 to 24 items; default 12

The response includes only public, active Templates from active official organizations. most_used sorts by unique consumer organizations, then completed deployments and creation date. newest uses the creation date; updated uses the last update. A deterministic tie-breaker keeps pagination stable.

Each public item contains only id, name, summary, description, tags, official, author.organization_name, requires_database, and updated_at. Application configuration, variables, plan defaults, usage data, and organization identifiers are not part of this contract.

Example response structure:

{
  "status": "success",
  "data": {
    "items": [
      {
        "id": "507f1f77bcf86cd799439011",
        "name": "Support API",
        "summary": "Starting point for an HTTP API.",
        "description": "Official template for starting an API.",
        "tags": ["api"],
        "official": true,
        "author": {
          "organization_name": "Zenifra"
        },
        "requires_database": true,
        "updated_at": "2026-09-03T12:00:00.000Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 12,
      "total": 1,
      "pages": 1
    }
  }
}

List templates with authentication

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

The official public catalog is limited to 120 requests per minute per IP. Authenticated 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

On this page