AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
coolipy is the (un)official, fully-typed Python client for the Coolify API — synchronous and asynchronous, wrapping every token-gated endpoint with pydantic models. Deploy applications, provision data
Developer toolsHTML · PythonGitHub @gbbocchiniOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
The (un)official Python client for Coolify.io!
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
The (un)official Python client for Coolify
Synchronous and asynchronous · typed pydantic models · no raw dicts, no manual JSON
• 📚 Lib docs: https://coolipydocs.gabrielbocchini.com.br/
• 🧭 Coolify API docs: https://coolify.io/docs/api
Requires Python 3.10+. Runtime dependencies: httpx and pydantic.
Use it as a context manager to close automatically:
Each resource is a sub-client on the client instance:Read the full README ↗pip install coolipy
# or
uv add coolipy
Requires Python 3.10+. Runtime dependencies: httpx and pydantic.
| ⚡ Sync + async | One package, two clients — Coolipy and AsyncCoolipy |
| 🧱 Typed models | Every request and response is a pydantic model |
| 📦 One envelope | CoolipyAPIResponse[T] → status_code, validated data, headers |
| 🚨 Typed errors | CoolipyHTTPError carries the API's validation details |
| 🔌 httpx + DI | Dependency-injected transport — trivial to mock in tests |
from coolipy import Coolipy
client = Coolipy(
coolify_api_key="YOUR_API_TOKEN",
coolify_endpoint="your-coolify-instance.com",
http_protocol="https",
coolify_port=8000,
)
resp = client.version()
print(resp.status_code) # 200
print(resp.data) # '4.3.17'
client.close()
Use it as a context manager to close automatically:
with Coolipy("YOUR_API_TOKEN", "your-coolify-instance.com") as client:
print(client.health().data) # 'OK'
import asyncio
from coolipy import AsyncCoolipy
async def main() -> None:
async with AsyncCoolipy("YOUR_API_TOKEN", "your-coolify-instance.com") as client:
resp = await client.version()
print(resp.data)
asyncio.run(main())
Each resource is a sub-client on the client instance:
| Sub-client | What it manages |
|---|---|
client.projects | Projects and environments |
client.servers | Servers, destinations, resources, validation |
client.applications | Applications (git / docker image / dockerfile), envs, storages, tags, scheduled tasks |
client.databases | PostgreSQL, MySQL, MariaDB, MongoDB, Redis, ClickHouse, Dragonfly, KeyDB |
client.services | Docker Compose services (incl. per-service apps and databases) |
client.deployments | Deployments and deploy |
client.teams | Teams, members, shared environment variables |
client.tags | Global tags |
client.s3_storages | S3 storage backends |
client.security | Private keys |
Every method returns a CoolipyAPIResponse with three fields:
| Field | Type | Description |
|---|---|---|
status_code | int | HTTP status code. |
data | T | Parsed body, validated against a model. |
headers | dict[str, str] | Response headers. |
resp = client.enable_api()
print(resp.data) # SystemMessage(message='API enabled.')
print(resp.data.message) # 'API enabled.'
Every request and response body is a pydantic model deriving from CoolipyBaseModel. Response models are tolerant: every field is optional and unknown fields are ignored, so they never fail against a live instance.
*Create / *Update classes you build and pass in (e.g. ProjectCreateModel, ApplicationDockerImageModelCreate, PostgreSQLModelCreate). Unset fields are omitted from the request body.*Model classes returned in resp.data (e.g. ProjectModel, ServerModel, ApplicationModel).Enums mirror the API's string-constrained fields:
from coolipy.enums import BuildPack, ProxyType, ServiceType
BuildPack.NIXPACKS.value # 'nixpacks'
BuildPack.RAILPACK.value # 'railpack'
ProxyType.NONE.value # 'none'
All examples below were captured against a live Coolify instance (v4.3.17).
from coolipy.models.projects import ProjectCreateModel
resp = client.projects.create(
ProjectCreateModel(name="My Project", description="Created with Coolipy")
)
print(resp.status_code) # 201
print(resp.data) # UUIDResponse(uuid='og888os')
resp = client.projects.list()
print(resp.data)
# [
# ProjectModel(id=7, uuid='mawhjk3svlsd9v9dujlck4cq',
# name='coolipy-smoke-apps-async', description=''),
# ]
resp = client.servers.list()
server = resp.data[0]
print(server)
# ServerModel(
# uuid='g7jdko9weqgokkm7m9lhkzqb',
# name='localhost',
# ip='host.docker.internal',
# user='root',
# port=22,
# is_coolify_host=True,
# is_reachable=True,
# is_usable=True,
# proxy={'redirect_enabled': True},
# settings=ServerSetting(id=1, concurrent_builds=2, ...),
# )
from coolipy.models.applications import ApplicationDockerImageModelCreate
app = ApplicationDockerImageModelCreate(
project_uuid="your_project_uuid",
server_uuid="your_server_uuid",
environment_name="production",
docker_registry_image_name="nginx",
docker_registry_image_tag="latest",
name="my-nginx",
ports_exposes="80",
)
resp = client.applications.create(app)
print(resp.data) # UUIDResponse(uuid='6zacuhbss0pnxtjihzmxolds')
resp = client.applications.list()
app = resp.data[0]
print(app.docker_registry_image_name, app.build_pack, app.fqdn)
# nginx dockerimage http://6zacuhbss0pnxtjihzmxolds.178.104.56.250.sslip.io
Applications can also be created from a public/private git repository, a deploy key, or a Dockerfile — ApplicationPublicModelCreate, ApplicationPrivateGHModelCreate, ApplicationPrivateDeployKeyModelCreate, ApplicationDockerfileModelCreate.
from coolipy.models.databases import PostgreSQLModelCreate
db = PostgreSQLModelCreate(
project_uuid="your_project_uuid",
server_uuid="your_server_uuid",
environment_name="production",
postgres_user="dbuser",
postgres_password="password",
postgres_db="mydatabase",
name="My PostgreSQL DB",
)
resp = client.databases.create(db)
print(resp.data) # UUIDResponse(uuid='...')
Eight database types are supported: PostgreSQLModelCreate, MySQLModelCreate, MariaDBModelCreate, MongoDBModelCreate, RedisModelCreate, ClickhouseModelCreate, DragonflyModelCreate, KeyDBModelCreate.
from coolipy.models.services import ServiceCreateModel
service = ServiceCreateModel(
name="my-service",
project_uuid="your_project_uuid",
server_uuid="your_server_uuid",
environment_name="production",
docker_compose_raw="<base64 docker-compose.yml>",
)
resp = client.services.create(service)
resp = client.teams.current()
print(resp.data)
# TeamModel(id=0, name='Root Team', personal_team=True, ...)
resp = client.teams.current_members()
print(resp.data)
# [UserModel(id=0, name='Gabriel B. Bocchini', email='[email protected]', ...)]
resp = client.tags.list()
print(resp.data) # [Tag(uuid='cz8op2sw7b0ysjvjq9ykeips', name='coolipy-smoke-tag', ...)]
resp = client.security.list()
print(resp.data) # [PrivateKeyModel(uuid='...', name="localhost's key", is_git_related=False, ...)]
resp = client.s3_storages.list()
print(resp.data) # []
resp = client.deployments.deploy(tag="my-tag", force=True)
print(resp.data)
# DeployResponse(deployments=[DeploymentEntry(message='...', resource_uuid='...', deployment_uuid='...')])
Every method has an async equivalent on AsyncCoolipy:
async with AsyncCoolipy("YOUR_API_TOKEN", "your-coolify-instance.com") as client:
resp = await client.projects.list()
print(resp.data)
Non-2xx responses raise CoolipyHTTPError, which carries the API's error details:
from coolipy.exceptions import CoolipyHTTPError
try:
client.version()
except CoolipyHTTPError as exc:
print(exc.status_code) # e.g. 401
print(exc.message) # e.g. "Unauthenticated."
print(exc.errors) # field-level messages for 422 responses
CoolipyError is the base class; CoolipyConfigError and CoolipyValidationError cover client-side problems.
Both clients take the same arguments:
| Argument | Type | Default | Description |
|---|---|---|---|
coolify_api_key | str | — | Bearer token for the Coolify API. |
coolify_endpoint | str | — | Hostname/IP of the Coolify instance. |
coolify_port | int | 8000 | Port (ignored when omit_port=True). |
omit_port | bool | False | Build the base URL without a port. |
http_protocol | str | "http" | "http" or "https". |
timeout | float | 30.0 | Request timeout in seconds. |
Coolipy 1.0.0 covers the full token-gated Coolify API surface — applications, databases, services, servers, projects, environments, teams, deployments, tags, S3 storages, private keys, shared envs, and the system endpoints — in both sync and async flavours. The suite is verified against a live Coolify instance via the smoke tests in tests/smoke/.
uv sync --extra dev
uv run pytest
uv run ruff check . && uv run ruff format --check .
uv run mypy coolipy
Run the real-world smoke tests against a live instance (no secrets committed — provided via env vars):
COOLIPY_API_KEY=... COOLIPY_ENDPOINT=... uv run pytest -m smoke
Regenerate the API documentation (rendered with pdoc3):
uv run python docs/build_docs.py
docs/ is the source for the documentation site; html/coolipy/ is the generated output that nginx serves. The build script runs pdoc with the docs/templates overrides, then writes the search/LLM discovery assets into html/coolipy/:
| Source — edit these | Output — generated, don't hand-edit |
|---|---|
docs/templates/head.mako — SEO head (meta, Open Graph, Twitter, JSON-LD, APM) | html/coolipy/**/*.html |
docs/templates/html.mako — page heading + titles | html/coolipy/**/*.html |
docs/templates/config.mako — markdown extensions + highlight theme | html/coolipy/**/*.html |
docs/llms.txt / docs/llms-full.txt — LLM/AI-agent guides | html/coolipy/llms.txt, html/coolipy/llms-full.txt |
docs/robots.txt | html/coolipy/robots.txt |
docs/assets/ — favicon.ico, apple-touch-icon.png | html/coolipy/favicon.ico, html/coolipy/apple-touch-icon.png |
| (generated from the actual pages) | html/coolipy/sitemap.xml |
To change how the site looks or is discovered, edit the docs/ source and re-run the build — editing html/ directly will be lost on the next regeneration.
docs/nginx/nginx.conf is the complete server block for coolipydocs.gabrielbocchini.com.br — self-contained, with no include. Paste it into Coolify's nginx config; the build does not copy it anywhere.
It carries two things beyond the stock static-file routing:
/constants.html → /enums.html and /services/coolify_api/* → /resources/* keep working instead of 404ing.PetalBot traffic — requests whose User-Agent claims (HTML, like Gecko), which no real browser or crawler ever sends. This traffic was generating ~98% of the 404s in the access log.Apache License 2.0 — see LICENSE.