Python SDK for the Northflank platform API
Project description
Northflank Python SDK
The official Python client for the Northflank API. It provides typed endpoint methods for every Northflank resource — services, sandboxes, GPU workloads, jobs, addons, secrets, volumes, domains, metrics, and more — plus exec and logs (range fetch and live tailing).
Installation
pip install northflank
Requires Python 3.9–3.13.
Quick start
from northflank import ApiClient
client = ApiClient(api_token="your-api-token")
# List the services in a project
services = client.list.services(project_id="my-project")
for service in services.data["services"]:
print(service["name"])
Every call returns an ApiCallResponse with .data, .status, and .error.
Method arguments are keyword-only and fully typed — request bodies and
response data are described by generated TypedDicts, so editors and type
checkers can validate payloads.
Error handling
By default the client raises ApiCallError on any non-2xx response —
this matches the convention of requests, httpx, boto3, and most Python
SDKs:
from northflank import ApiCallError
try:
client.get.service(project_id="my-project", service_id="does-not-exist")
except ApiCallError as exc:
print(exc.status, exc.message)
Pass throw_on_http_error=False to instead surface the error on the response
object and never raise:
client = ApiClient(api_token="...", throw_on_http_error=False)
resp = client.get.service(project_id="my-project", service_id="does-not-exist")
if resp.error is not None:
print(resp.error.status, resp.error.message)
Working with services
Create a service
client.create.service.deployment(
project_id="my-project",
data={
"name": "my-service",
"billing": {"deploymentPlan": "nf-compute-20"},
"deployment": {
"instances": 1,
"external": {"imagePath": "nginx:latest"},
"docker": {"configType": "default"},
},
"ports": [
{"name": "http", "internalPort": 80, "public": True, "protocol": "HTTP"},
],
},
)
Wait for it to be running
import time
while True:
service = client.get.service(project_id="my-project", service_id="my-service")
status = service.data["status"]["deployment"]["status"]
if status == "COMPLETED":
break
if status == "FAILED":
raise RuntimeError("deployment failed")
time.sleep(3)
Pause, resume, and delete
client.pause.service(project_id="my-project", service_id="my-service")
client.resume.service(project_id="my-project", service_id="my-service", data={})
client.delete.service(project_id="my-project", service_id="my-service")
Exec
One-shot commands
run_service_command runs a command, waits for it to finish, and returns an
ExecResult (exit_code, stdout, stderr, status):
result = client.exec.run_service_command(
project_id="my-project",
service_id="my-service",
command="cat /etc/os-release",
)
print(result.exit_code, result.stdout)
# Commands that need a shell (pipes, &&, redirection) — pass `shell`:
client.exec.run_service_command(
project_id="my-project",
service_id="my-service",
command="echo hello && uname -a",
shell="bash -c",
)
The same one-shot API works for jobs and addons:
client.exec.run_job_command(project_id="my-project", job_id="my-job", command="ls")
client.exec.run_addon_command(
project_id="my-project", addon_id="my-redis", command="redis-cli ping", shell="bash -c"
)
Interactive sessions
open_service_session opens a persistent connection: write to the process's
stdin, iterate output as it streams, resize the terminal. Use it as a context
manager so the WebSocket is always closed. Send input, read the output it
produces, then send_eof — closing stdin immediately after a send can
race the proxy.
with client.exec.open_service_session(
project_id="my-project", service_id="my-service", command="cat"
) as session:
session.resize(rows=40, columns=120)
session.send("hello\n")
for chunk in session: # chunk.stream is "stdout" / "stderr"
print(chunk.data, end="")
if "hello" in chunk.data:
break
session.send_eof() # close stdin
result = session.wait() # ExecResult once the process exits
open_job_session and open_addon_session cover jobs and addons. The async
client exposes aopen_service_session / aopen_job_session /
aopen_addon_session, returning an AsyncExecSession driven with
async with and async for.
Logs
Fetch a recent range
lines = client.logs.fetch_service_logs(
project_id="my-project",
service_id="my-service",
line_limit=100,
text_includes="error",
)
for line in lines:
print(line.ts, line.log)
Live tail
tail_service_logs opens a WebSocket and returns a LogTail you iterate for
LogLine items as they arrive:
with client.logs.tail_service_logs(
project_id="my-project", service_id="my-service", recv_timeout=30.0
) as tail:
for line in tail:
print(line.ts, line.log)
tail_job_logs and tail_addon_logs cover jobs and addons; the async client
provides atail_service_logs / atail_job_logs / atail_addon_logs
(AsyncLogTail, driven with async for).
Metrics
client.get.{service,job,addon}.metrics fetches resource metrics. query_type
selects a time range or a single snapshot, metric_types takes a list, and
time-range arguments accept native datetime objects:
from datetime import datetime, timedelta, timezone
from northflank import METRIC_TYPES
metrics = client.get.service.metrics(
project_id="my-project",
service_id="my-service",
query_type="range",
metric_types=["cpu", "memory"],
start_time=datetime.now(timezone.utc) - timedelta(hours=1),
)
print(metrics.data)
MetricType (a Literal of every valid metric) and METRIC_TYPES (all of
them, as a tuple) are exported from northflank for annotation and reuse.
Async usage
AsyncApiClient mirrors ApiClient with awaitable calls:
import asyncio
from northflank import AsyncApiClient
async def main():
client = AsyncApiClient(api_token="your-api-token")
services = await client.list.services(project_id="my-project")
print(services.data)
asyncio.run(main())
Pagination
Paginated list endpoints return one page by default. Use .all() to drain
every page into a single response, or .pagination.get_next_page() to step
through them:
# All pages at once
every_service = client.list.services.all(project_id="my-project")
# One page at a time
page = client.list.services(project_id="my-project")
while page.pagination and page.pagination.has_next_page:
page = page.pagination.get_next_page()
Configuration
ApiClient reads configuration from arguments or environment variables:
| Environment variable | Purpose |
|---|---|
NF_API_TOKEN |
API token (also NORTHFLANK_API_TOKEN) |
NF_API_HOST / NF_HOST |
API base URL (default https://api.northflank.com) |
# Explicit
client = ApiClient(api_token="...", base_url="https://api.northflank.com")
# From the environment
client = ApiClient()
More examples
The examples/ directory has runnable scripts covering
sandboxes, interactive exec sessions, log fetching and tailing, GPU workloads,
networking, persistent volumes, and parallel sandbox pools. Each one reads
NF_API_TOKEN and NF_PROJECT_ID from the environment.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file northflank-1.0.0.tar.gz.
File metadata
- Download URL: northflank-1.0.0.tar.gz
- Upload date:
- Size: 224.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b8c229cfaa66269a7181c5767d740f7dad91ecf474750f4058b1c0007e09247
|
|
| MD5 |
be8d6119b8988fccee467c24f3b8755c
|
|
| BLAKE2b-256 |
d3e50ffc18c9dea511e647e7609bb3412d671320c87361bf4eebeb4913dba791
|
File details
Details for the file northflank-1.0.0-py3-none-any.whl.
File metadata
- Download URL: northflank-1.0.0-py3-none-any.whl
- Upload date:
- Size: 450.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c674ea189246e7995f31b15be93706df20b58b6e8b1b83b176a1660b7cb31aa
|
|
| MD5 |
f32803d52ba74aeb7e13ccdc2d928463
|
|
| BLAKE2b-256 |
5df33bdfc4427d8011256a096977294289d53ac898f4edad76e4cdcc2f34e38b
|