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:
| Surface | How you call it | Response | Execution record? |
|---|---|---|---|
| Direct FQDN | plain HTTP to the app’s public URL | the pod’s raw HTTP response | No (gateway counts it for billing) |
Invoke RPC | POST /v1/ignite/app/{org}/{app}/invoke (or gRPC stream) | streamed head → chunks → trailer | No — execution_id is for log correlation only |
InvokeAsync RPC | POST /v1/ignite/app/{org}/{app}/invoke-async | execution_id only | Yes — 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.
| RPC | HTTP |
|---|---|
Invoke (server-streaming) | POST /v1/ignite/app/{organization_name}/{app_name}/invoke |
InvokeAsync | POST /v1/ignite/app/{organization_name}/{app_name}/invoke-async |
GetExecution | GET /v1/ignite/executions/{execution_id} |
WatchExecution (server-streaming) | GET /v1/ignite/executions/{execution_id}/watch |
GetExecutionStats | GET /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), inportsorder —public_urls[0]is the primary URL. - Auth: still required — direct FQDNs validate the
Authorization: Bearertoken and enforce org match. They are not anonymous. The exception is apps in thepublicorg, 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
GetExecutionafterward — useInvokeAsyncif 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:
head— once, first:status_code,response_headers, and anexecution_id.chunk— 0..N frames carrying the response body bytes, in order.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.
Invokedoes not create a queryable execution record. Theexecution_idin theheadframe is for log correlation only (echoed to the pod asX-Ignite-Execution-Id) —GetExecutionwill not find it. If you need a record to poll or audit later, useInvokeAsync.
Request
HTTP
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
HTTP
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.
HTTP
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.
HTTP
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.
HTTP
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 executionGetExecutionStats
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
HTTP
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. Thetrailer.duration_mson anInvokeincludes 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.
| Field | Meaning | Default |
|---|---|---|
reserved_capacity | Minimum warm pods (0 = scale to zero allowed) | 0 |
max_replicas | Maximum pods | 10 (capped by a platform max — typically 5) |
scale_metric | REQUEST_RATE (target req/s per pod) or CONCURRENCY (target in-flight per pod) | REQUEST_RATE |
scale_threshold | The per-pod target for the chosen metric | 0 → platform default (10) |
scaledown_period_secs | Stay scaled-up this long after traffic drops | 300 (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.
CONCURRENCYsuits variable-duration work (LLM inference, image processing, long jobs);REQUEST_RATEsuits short, uniform requests. The effectivemax_replicasismin(your value, platform cap)— raise the platform cap with ops if you need more.
See also
- Apps — app metadata, config, and state
- Drafts — attach code, compile, publish
- Observability — logs, events, and replicas
- Core Concepts → Execution · → Resources & scaling
grpcurlreference — flags + reflection fallbacks