Skip to Content
We are live but in Staging 🎉
ComputeAPI ReferenceInvocation & Executions

Invocation & Executions — API Reference

Package: dodil.ignite.v1 · Service: ComputeService

Invoking an app runs its active version in a pod and returns the pod’s HTTP response. The request carries an invocation envelope — an HttpContext (verb, path, query, headers) plus a body. If the app is scaled to zero, the first call wakes it (see Cold starts).

gRPC reaches every method at dodil.ignite.v1.ComputeService/<Method> on $IGNITE_GRPC; the HTTP gateway mirrors each one. See Conventions → Using gRPC for setup. gRPC requests use the proto field names (snake_case); HTTP uses pbjson (camelCase). int64/uint64 are JSON strings, enums are wire-name strings; responses render camelCase.

Invocation surfaces

Three ways to reach an app, differing in framing, auth, and whether a queryable Execution record is created:

SurfaceHow you call itResponseExecution record?
Direct FQDNplain HTTP to the app’s public URLthe pod’s raw HTTP responseNo (gateway counts it for billing)
Invoke RPCPOST /v1/ignite/app/{org}/{app}/invoke (or gRPC stream)streamed head → chunks → trailerNo — execution_id is for log correlation only
InvokeAsync RPCPOST /v1/ignite/app/{org}/{app}/invoke-asyncexecution_id onlyYes — poll with GetExecution

Use a direct FQDN for the lowest latency from browsers/webhooks/standard HTTP clients; the Invoke RPC when you want structured framing + an execution_id to correlate logs; InvokeAsync for fire-and-forget work you’ll poll or watch.

RPCHTTP
Invoke (server-streaming)POST /v1/ignite/app/{organization_name}/{app_name}/invoke
InvokeAsyncPOST /v1/ignite/app/{organization_name}/{app_name}/invoke-async
GetExecutionGET /v1/ignite/executions/{execution_id}
WatchExecution (server-streaming)GET /v1/ignite/executions/{execution_id}/watch
GetExecutionStatsGET /v1/ignite/app/{organization_name}/{app_name}/stats/executions

Direct FQDN invocation

Every published app gets one public URL per declared port, returned on AppMeta.public_urls. This is the fastest surface — a plain HTTP call straight to the pod, with no API hop or response framing.

  • Format: `<app>-<org>.ignite.dodil.cloud` for the default port 80, or `<app>-<org>-<port>.ignite.dodil.cloud` for any other port. A . in the app name is encoded as -dot- to keep a single DNS label.
  • When populated: only after the first publish (active_version > 0), in ports order — public_urls[0] is the primary URL.
  • Auth: still required — direct FQDNs validate the Authorization: Bearer token and enforce org match. They are not anonymous. The exception is apps in the public org, which any authenticated caller may invoke.
  • No execution record: a direct call is proxied straight to the pod (the gateway still counts it for billing), so there’s nothing to fetch with GetExecution afterward — use InvokeAsync if you need a record.
# the pod's raw response — call any path/verb your app serves curl -sS -X POST "https://my-data-processor-acme-corp.ignite.dodil.cloud/" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "key": "value" }'

Invoke

Server-streaming. The gateway forwards the envelope to the pod and streams back the response in frames, in a fixed order:

  1. head — once, first: status_code, response_headers, and an execution_id.
  2. chunk — 0..N frames carrying the response body bytes, in order.
  3. trailer — once, last: total_body_bytes, duration_ms, and any HTTP trailers.

Non-streaming apps emit one chunk + the trailer. The stream may end with a gRPC error instead of a trailer if the pod fails mid-flight; chunks already delivered are kept. Dropping the stream disconnects the pod.

Invoke does not create a queryable execution record. The execution_id in the head frame is for log correlation only (echoed to the pod as X-Ignite-Execution-Id) — GetExecution will not find it. If you need a record to poll or audit later, use InvokeAsync.

Request

curl -sS -X POST "https://api.dev.dodil.io/v1/ignite/app/acme-corp/my-data-processor/invoke" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "http": { "method": "HTTP_METHOD_POST", "path": "/", "headers": { "content-type": "application/json" } }, "body": "eyJrZXkiOiAidmFsdWUifQ==" }'

Response

Over HTTP the gateway delivers the same head → chunks → trailer sequence as a single chunked HTTP response: the pod’s status/headers become the response status/headers (with X-Ignite-Execution-Id carrying the id), the chunk frames stream as the body, and total_body_bytes / duration_ms arrive as HTTP trailers.

Async invocation & tracking executions

For fire-and-forget work, InvokeAsync returns an execution_id immediately and creates an Execution record — then you GetExecution to poll it or WatchExecution to stream its progress to a terminal state. (Invoke, by contrast, creates no record.)

InvokeAsync

Takes the same InvokeRequest as Invoke, returns just the id.

curl -sS -X POST "https://api.dev.dodil.io/v1/ignite/app/acme-corp/my-data-processor/invoke-async" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "body": "eyJrZXkiOiAidmFsdWUifQ==" }' # → { "executionId": "exec_01HZX9..." }

GetExecution

Fetch the status and result of one execution by ID. Works for records created by InvokeAsync (and model / API-proxy paths) — not for streaming Invoke calls, which create no record.

curl -sS "https://api.dev.dodil.io/v1/ignite/executions/exec_01HZX9..." \ -H "Authorization: Bearer $DODIL_TOKEN"
{ "execution": { "id": "exec_01HZX9...", "appId": "acme-corp/my-data-processor", "status": "EXECUTION_STATUS_COMPLETED", "result": "eyJvayI6IHRydWV9", "metrics": { "executionMs": "142", "resultSizeBytes": "11", "resourceTier": "Medium" }, "functionVersion": 3, "appName": "my-data-processor" } }

WatchExecution

Server-streaming. Sends the current Execution immediately, then every update until a terminal state (COMPLETED / FAILED / TIMEOUT). For streaming apps, output arrives as data-only frames (execution absent, output_chunk set) before the terminal response.

curl -sN "https://api.dev.dodil.io/v1/ignite/executions/exec_01HZX9.../watch" \ -H "Authorization: Bearer $DODIL_TOKEN" # chunked response — one JSON object per update, ending with the terminal execution

GetExecutionStats

Per-app execution counts bucketed over time (the Stats-tab graph). Backed by Prometheus; the range is capped at 90 days and clamped server-side.

Request

curl -sS "https://api.dev.dodil.io/v1/ignite/app/acme-corp/my-data-processor/stats/executions?bucketSecs=86400" \ -H "Authorization: Bearer $DODIL_TOKEN"

Response

message GetExecutionStatsResponse { repeated ExecutionBucket buckets = 1; int32 bucket_secs = 2; // width actually used (server may clamp) repeated string partial_clusters = 3; // clusters whose Prometheus didn't respond; empty = complete } message ExecutionBucket { int64 timestamp_ms = 1; // bucket start (inclusive) int32 completed = 2; int32 failed = 3; int32 timeout = 4; }

Cold starts vs warm pods

Ignite apps scale to zero when idle, so an invocation is either warm (a pod is already running) or cold (none is — one has to start).

  • Cold path. The first request to a scaled-to-zero app is caught by the platform’s KEDA HTTP interceptor, which holds the connection open while a pod is started (0→1) and becomes ready, then proxies the request through. The call isn’t rejected — it just waits. The gateway waits up to the cold-start budget (default 600s, IGNITE_COLD_START_BUDGET_SECS) for the pod; the total per-request deadline is your app timeout + the cold-start budget. The trailer.duration_ms on an Invoke includes this wait.
  • Warm path. Once running, requests skip straight to the pod. An app stays up for scaledown_period_secs (default 300s) after traffic drops, then KEDA scales it back toward zero. Long-idle apps may have their underlying resources torn down; the next request re-provisions them (a bit slower than a plain wake-up).
  • Keeping pods warm. Set ScalingConfig.reserved_capacity > 0 to keep that many pods always running — those serve the first N concurrent requests with no cold start, and the app is exempt from idle teardown. There is no separate “pre-warm” call.

Scaling configuration

An app scales horizontally on demand. The knobs live on ScalingConfig, set at CreateApp or UpdateAppConfig; the platform maps them onto KEDA’s HTTP autoscaler.

FieldMeaningDefault
reserved_capacityMinimum warm pods (0 = scale to zero allowed)0
max_replicasMaximum pods10 (capped by a platform max — typically 5)
scale_metricREQUEST_RATE (target req/s per pod) or CONCURRENCY (target in-flight per pod)REQUEST_RATE
scale_thresholdThe per-pod target for the chosen metric0 → platform default (10)
scaledown_period_secsStay scaled-up this long after traffic drops300 (KEDA min 30)

How a replica gets added. The autoscaler computes desired_replicas = ceil(observed_per_pod / scale_threshold), clamped to [reserved_capacity, max_replicas]. With REQUEST_RATE, observed_per_pod is average requests/second per pod (1-minute window); with CONCURRENCY, it’s in-flight requests per pod. Activation from zero is binary — any inbound request triggers it.

Pick the metric to match the workload. CONCURRENCY suits variable-duration work (LLM inference, image processing, long jobs); REQUEST_RATE suits short, uniform requests. The effective max_replicas is min(your value, platform cap) — raise the platform cap with ops if you need more.


See also