{ "openapi": "3.1.0", "info": { "title": "Hanzo Cloud API", "description": "Package functions is your serverless code: publish it, call it over HTTP, watch every run and what it cost.", "version": "v1" }, "servers": [ { "url": "https://api.hanzo.ai" } ], "tags": [ { "name": "functions" } ], "paths": { "/v1/functions": { "get": { "operationId": "get_v1_functions", "summary": "Every serverless function the caller's org has published, with its real 7-day rollup", "description": "A row carries the function's runtime, resource limits, deployment target and its invoke endpoint, plus envCount — how many secrets it mounts. The registry holds secret NAMES only; a value never enters this store and is never returned.\n\nThe rollup (invocations7d, errors7d, successRate, avgDurationMs) is counted from real invocation rows over the trailing 7 days and is OMITTED for a function with no calls in that window rather than sent as zero, so a consumer must render absence as unknown, not as an idle function. Ordered most-recently-deployed first.\n\nScoped to the caller's own org — one store per org, with the org column on every query. Requires a validated principal: an org claim with no verified credential behind it is refused, never answered with an empty list.", "tags": [ "functions" ] }, "post": { "operationId": "post_v1_functions", "summary": "Publish a function, or redeploy an existing one under the same name", "description": "Org and name together identify a function, so a second call for a name the org already owns is a REDEPLOY: the spec is replaced, the deploy version advances, and the original creation time is kept. There is no separate update call, and no way to take over a name another org owns.\n\nWhat is accepted is a closed set. runtime is one of node, python, go, deno, bash or container; name must match ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ and may not be one of the reserved static names (metrics, triggers, deployments, secrets); source is capped at 256 KiB. timeoutSec is CLAMPED to the 900s ceiling rather than rejected, and an absent one defaults to 30s with 256Mi of memory. target=fleet runs the function on the org's own linked GPU fleet and is accepted for runtime=python only; everything else runs on the shared sandbox.\n\nenvNames declares which secrets the function mounts BY NAME — values live in KMS and are resolved sandbox-side at run time, so no secret value is sent here or stored here. Scoped to the caller's org; requires a validated principal.", "tags": [ "functions" ] } }, "/v1/functions/deployments": { "get": { "operationId": "get_v1_functions_deployments", "summary": "The live deployment of every function in the caller's org", "description": "A function's current record IS its deployment, so this answers in the same shape the function list does — runtime, resource limits, target, endpoint, and when it was last deployed.\n\nTwo things not to assume. The invocation rollup is never populated here, even for a function that has run: those fields are omitted unconditionally, and the function list is where they are filled in. And this is an inventory of what is live, not a history — there is exactly one entry per function, and a redeploy replaces it rather than appending to it.\n\nScoped to the caller's org; requires a validated principal.", "tags": [ "functions" ] } }, "/v1/functions/metrics": { "get": { "operationId": "get_v1_functions_metrics", "summary": "Invocation chart and status breakdown across every function in the caller's org", "description": "One series per function that actually ran in the window, bucketed, plus a success/timeout/error donut over the same rows. Every point is a COUNT of real invocation rows that fell in that bucket — nothing is interpolated, and a function with no invocations in the window has no series at all.\n\nThe `range` query selects the window and its bucket count: 1H, 6H, 24H, 7D or 30D. An absent or unrecognized value falls back to 24H rather than failing. At most the 5000 newest rows are read, so a very busy org's oldest buckets in a wide range can undercount.\n\ncostCents is always null: this view has no per-invocation cost source, and reports nothing rather than a fabricated figure. Scoped to the caller's org; requires a validated principal.", "tags": [ "functions" ] } }, "/v1/functions/secrets": { "get": { "operationId": "get_v1_functions_secrets", "summary": "The names of the secrets mounted by the caller's org's functions", "description": "NAMES only. A secret's value is not held by this subsystem and is not read on this path — values live in KMS and are resolved sandbox-side when a function runs — so nothing in this answer is a credential.\n\nThe list is derived from the mount declarations on the function records and deduplicated by namespace and name, so a name mounted by several functions appears ONCE: mountedBy names the first function that claimed it in deploy order, not every function that mounts it. Read it as a hint about origin, not as a complete usage map.\n\nScoped to the caller's org; requires a validated principal.", "tags": [ "functions" ] } }, "/v1/functions/triggers": { "get": { "operationId": "get_v1_functions_triggers", "summary": "Every trigger attached to the caller's org's functions", "description": "A function has exactly ONE trigger today and it is derived, not stored: an always-enabled HTTP trigger whose target is that function's own invoke endpoint, listed once per function.\n\nThere is no trigger table behind this and no call that creates, disables or deletes one. The list is a projection of the function registry, so it changes only when a function is published or deleted.\n\nScoped to the caller's org; requires a validated principal.", "tags": [ "functions" ] } }, "/v1/functions/{name}": { "delete": { "operationId": "delete_v1_functions_by_name", "summary": "Delete a function and its entire invocation history", "description": "One transaction removes the function record and every invocation row recorded against its name, so that history also leaves the metrics chart and the invocation list. This is not a soft delete and there is no restore.\n\nDeletion is keyed on (org, name): a name owned by another org is not found here, exactly like a name that never existed, so the call cannot be used to probe for or destroy another tenant's function. A successful delete answers with no body.\n\nRequires a validated principal.", "tags": [ "functions" ], "parameters": [ { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } } ] }, "get": { "operationId": "get_v1_functions_by_name", "summary": "One function in full: spec, trailing-7-day rollup, trigger, latest runs and mounted secret names", "description": "Extends the list row with the function's single derived HTTP trigger, its 20 most recent invocations (newest first, metadata only — no captured output), and `secrets`, the NAMES of the secrets it mounts. No secret value is stored or returned.\n\nLookup is keyed on (org, name), so a function that exists but belongs to another org answers exactly as one that never existed — not found, never a signal that the name is taken elsewhere. The 7-day rollup fields are omitted rather than zeroed when the function has not run in the window.\n\nRequires a validated principal.", "tags": [ "functions" ], "parameters": [ { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } } ] } }, "/v1/functions/{name}/invocations": { "get": { "operationId": "get_v1_functions_by_name_invocations", "summary": "Recent invocation history for one function, newest first", "description": "Each entry is invocation METADATA — id, status, HTTP status code, wall-clock duration and when it ran. The captured stdout/stderr is not on this path; the logs call returns it, for the latest run only.\n\n`limit` defaults to 100 and is clamped: at or below zero, above 500, or not a number at all, it falls back to 100. An unknown function name is NOT an error here — nothing has ever run under it, so the answer is an empty list rather than a not-found, and a caller testing existence must ask for the function itself.\n\nScoped to the caller's org, so it can only ever return the calling tenant's own runs. Requires a validated principal.", "tags": [ "functions" ], "parameters": [ { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } } ] } }, "/v1/functions/{name}/invoke": { "post": { "operationId": "post_v1_functions_by_name_invoke", "summary": "Run a function and get back the recorded invocation", "description": "The body's `input` is handed to the function on stdin. Execution NEVER happens in this process: the runtime and source go to the sandboxed code executor, or, for a function published with target=fleet, to the org's own linked GPU fleet as an fn.run job this call blocks on until it finishes. Either way it is bounded by the function's own timeout, itself capped at 900s.\n\nThe answer is the invocation record — id, status, duration — and its HTTP status is about the RUN, not about this API: a function whose own code fails answers 502 with a recorded `error` invocation, which is a successful invocation of a failing program. The captured output is not in this reply; the logs call returns it.\n\nMONEY. The caller's org ledger is gated BEFORE any compute runs, so an org out of credit or over its spend cap is refused 402 and nothing executes, and a billing plane that cannot answer refuses rather than granting free compute. A run that actually executed is then debited twice — a flat per-invocation fee, and GB-seconds of compute derived from the measured duration and the function's configured memory. A run that never reached its executor (unreachable, or timed out in transport) consumed nothing and is not charged; a run whose code exited non-zero DID consume compute and is. An operator who prices either half at zero makes it a no-op, and a zero request fee removes the balance gate with it.\n\nWhen the sandbox is not configured on this deployment, a non-fleet function fails closed before anything is recorded — no execution and no fabricated output. Scoped to the caller's org; requires a validated principal.", "tags": [ "functions" ], "parameters": [ { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } } ] } }, "/v1/functions/{name}/logs": { "get": { "operationId": "get_v1_functions_by_name_logs", "summary": "The captured output of a function's most recent invocation", "description": "One string, from the LATEST invocation only. This is not a log stream and carries no history; the invocations list is where earlier runs are enumerated.\n\nWhen that run failed, the string is its ERROR text rather than its stdout — the two share one field, so success cannot be told from failure by this value alone and the invocation's status is what answers that. Output was truncated to 64 KiB when the run was recorded, error text to 16 KiB.\n\nA function that has never run — or a name that does not exist in the caller's org — answers with an empty string, not a not-found. Scoped to the caller's org; requires a validated principal.", "tags": [ "functions" ], "parameters": [ { "name": "name", "in": "path", "required": true, "schema": { "type": "string" } } ] } } } }