01
Overview
Jungle Grid executes GPU-backed AI workloads from API clients, agents, the portal, CLI, and MCP hosts. The current production path supports inference, batch processing, training, fine-tuning, asynchronous jobs, uploaded files and scripts, resource-aware routing, lifecycle monitoring, workload logs, terminal callbacks, cancellation, and managed output artifacts. CPU-only execution is rejected by the current production routes unless the workload also supplies GPU requirements.
- •Estimate before submit when users need cost, capacity, route, queue wait, or screening feedback.
- •Upload inputs and scripts before submit when the command needs files larger than the command/args limit.
- •Use lifecycle events when the job is queued, scheduling, provisioning, or preparing and workload logs are still empty.
- •Use runtime details for exit code, stdout/stderr tails, timed-out state, and runtime-field availability.
Workflow diagram
flowchart TD
A[Client or Agent] --> B[Estimate Workload]
B --> C[Upload Inputs or Scripts]
C --> D[Submit Job]
D --> E[Scheduling and Provisioning Events]
E --> F[Workload Execution and Logs]
F --> G[Completion]
G --> H[Artifacts]
02
Authentication
Public API routes use Bearer authentication. Server-side integrations should create scoped API keys in the portal and send them as Authorization: Bearer jg_placeholder. Browser-issued sessions also work for portal-owned flows, but external services should keep API keys on the server.
- •A missing or invalid token returns 401 UNAUTHORIZED.
- •A valid token without a required scope returns 403 FORBIDDEN.
- •Workspace-bound API keys can access only their authorized workspace.
- •Never place API keys, OAuth tokens, callback tokens, signed upload URLs, or signed artifact URLs in browser bundles, source control, screenshots, or logs.
Environment
JUNGLE_GRID_API=https://api.junglegrid.dev
JUNGLE_GRID_API_KEY=jg_placeholder
Headers
Authorization: Bearer jg_placeholder
Accept: application/json
Content-Type: application/json
Scopes
jobs:estimate estimate workloads
jobs:submit submit jobs, upload inputs, cancel jobs
jobs:write broader submit/cancel compatibility scope
jobs:read list jobs, read status, runtime, events, artifacts
logs:read read persisted job logs
MCP local config
{
"mcpServers": {
"junglegrid": {
"command": "npx",
"args": ["-y", "@jungle-grid/mcp"],
"env": {
"JUNGLE_GRID_API_KEY": "jg_placeholder",
"JUNGLEGRID_API_BASE": "https://api.junglegrid.dev"
}
}
}
}
03
Quickstart
This minimal flow estimates a workload, submits it, reads the job ID, polls status and events, reads persisted logs, then lists and downloads artifacts. The command writes /workspace/artifacts/transcript.txt so the artifact path is visible end to end.
cURL
export JUNGLE_GRID_API="https://api.junglegrid.dev"
export JUNGLE_GRID_API_KEY="jg_placeholder"
curl -sS -X POST "$JUNGLE_GRID_API/v1/jobs/estimate" \
-H "Authorization: Bearer $JUNGLE_GRID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "quickstart-estimate",
"image": "pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime",
"workload_type": "batch",
"model_size_gb": 1,
"command": ["python", "-c", "from pathlib import Path; Path('/workspace/artifacts/transcript.txt').write_text('done')"],
"expected_artifacts": ["/workspace/artifacts/transcript.txt"]
}'
JOB_ID=$(curl -sS -X POST "$JUNGLE_GRID_API/v1/jobs" \
-H "Authorization: Bearer $JUNGLE_GRID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "quickstart-job",
"image": "pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime",
"workload_type": "batch",
"model_size_gb": 1,
"command": ["python", "-c", "from pathlib import Path; Path('/workspace/artifacts/transcript.txt').write_text('done')"],
"expected_artifacts": ["/workspace/artifacts/transcript.txt"]
}' | jq -r .job_id)
curl -sS -H "Authorization: Bearer $JUNGLE_GRID_API_KEY" "$JUNGLE_GRID_API/v1/jobs/$JOB_ID"
curl -sS -H "Authorization: Bearer $JUNGLE_GRID_API_KEY" "$JUNGLE_GRID_API/v1/jobs/$JOB_ID/events"
curl -sS -H "Authorization: Bearer $JUNGLE_GRID_API_KEY" "$JUNGLE_GRID_API/v1/jobs/$JOB_ID/logs?tail=100"
curl -sS -H "Authorization: Bearer $JUNGLE_GRID_API_KEY" "$JUNGLE_GRID_API/v1/jobs/$JOB_ID/artifacts"
TypeScript
const API_URL = (process.env.JUNGLE_GRID_API ?? "https://api.junglegrid.dev").replace(/\/+$/, "");
const API_KEY = process.env.JUNGLE_GRID_API_KEY;
async function jungle<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(API_URL + path, {
...init,
headers: {
Authorization: "Bearer " + API_KEY,
"Content-Type": "application/json",
Accept: "application/json",
...init.headers,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data?.error?.message ?? data?.message ?? "Jungle Grid request failed");
return data as T;
}
const payload = {
name: "ts-quickstart",
image: "pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime",
workload_type: "batch",
model_size_gb: 1,
command: ["python", "-c", "from pathlib import Path; Path('/workspace/artifacts/transcript.txt').write_text('done')"],
expected_artifacts: ["/workspace/artifacts/transcript.txt"],
};
const estimate = await jungle("/v1/jobs/estimate", { method: "POST", body: JSON.stringify(payload) });
const submitted = await jungle<{ job_id: string; status: string }>("/v1/jobs", { method: "POST", body: JSON.stringify(payload) });
const status = await jungle("/v1/jobs/" + submitted.job_id);
const events = await jungle("/v1/jobs/" + submitted.job_id + "/events");
const logs = await jungle("/v1/jobs/" + submitted.job_id + "/logs?tail=100");
const artifacts = await jungle("/v1/jobs/" + submitted.job_id + "/artifacts");
The quickstart uses a CUDA-enabled image because production routes reject workloads that look CPU-only or ambiguous. Use your own image and command once your workload contract is clear.
04
Estimate a Workload
POST /v1/jobs/estimate is read-only. It accepts the same draft job fields as submit, runs validation and screening, classifies whether the workload needs acceleration, evaluates routing and capacity, and returns cost and queue estimates where available. It is not a reservation unless a launch plan is explicitly compiled by supported ReadyPath flows.
- •Use estimates before user confirmation, budget checks, or agent-submitted jobs.
- •Capacity status can be available, limited, unavailable, or unknown depending on route and provider data.
- •A supported or provisionable estimate can still wait during scheduling or fail during runtime startup.
- •Validation failures can return a 200 estimate response with screening details rather than creating work.
Request
POST /v1/jobs/estimate
{
"name": "estimate-demo",
"image": "pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime",
"workload_type": "inference",
"model_size_gb": 7,
"command": ["python", "infer.py"],
"optimize_for": "balanced",
"constraints": {
"max_price_per_hour": 2.5,
"region_preference": "us-east",
"region_mode": "prefer"
}
}
Response fields
{
"available": true,
"routed_gpu_tier": "mid",
"likely_gpu_type": "NVIDIA L4",
"estimated_hourly_rate_usd": 1.2,
"estimated_cost_min_usd": 0.1,
"estimated_cost_max_usd": 0.3,
"estimated_queue_wait_min_sec": 0,
"estimated_queue_wait_max_sec": 120,
"estimated_start_time_min": "2026-06-11T12:00:00Z",
"classification": {
"workload_type": "inference",
"requires_gpu": true,
"acceleration_requirement": "gpu_required"
},
"routing": {
"route_status": "available",
"selected_route_source": "live_capacity"
},
"capacity": {
"live_capacity_available": true,
"live_candidate_count": 1,
"managed_capacity_available": true,
"managed_profile_count": 40,
"estimate_source": "live_capacity"
},
"capacity_status": {
"availability": "available",
"immediate_capacity_confirmed": true
},
"screening": {
"can_submit": true
}
}
05
Upload Inputs and Scripts
Inputs and scripts use managed upload slots. First create a slot, upload bytes to the signed URL, then complete the upload. Completed inputs mount under /workspace/inputs/<filename>; completed scripts mount under /workspace/scripts/<filename>. Submit jobs reference uploaded files by input_id.
- •POST /v1/job-inputs requires jobs:submit or jobs:write.
- •GET /v1/job-inputs requires jobs:read or jobs:write.
- •filename and content_type are preserved and returned in input metadata.
- •kind is accepted as a string. Use input and script consistently for predictable mount paths.
- •Submit returns JOB_INPUT_NOT_FOUND for missing IDs and JOB_INPUT_NOT_READY for incomplete uploads.
- •The current submit implementation supports one uploaded script reference in script_files.
Create upload slot
POST /v1/job-inputs
{
"filename": "transcribe.py",
"content_type": "text/x-python",
"kind": "script"
}
201 response:
{
"upload": {
"input_id": "inp_script123",
"filename": "transcribe.py",
"method": "PUT",
"upload_url": "https://signed-upload.example/...",
"headers": {},
"token": "upload_token",
"expires_at": "2026-06-11T12:15:00Z",
"complete_url": "https://api.junglegrid.dev/v1/job-inputs/inp_script123/complete"
}
}
Upload and complete
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: text/x-python" \
--data-binary @transcribe.py
curl -X POST "$COMPLETE_URL" \
-H "Authorization: Bearer $JUNGLE_GRID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"token": "upload_token",
"filename": "transcribe.py",
"content_type": "text/x-python",
"size_bytes": 1234,
"etag": "optional-etag"
}'
List uploaded inputs
GET /v1/job-inputs
{
"inputs": [
{
"input_id": "inp_script123",
"filename": "transcribe.py",
"content_type": "text/x-python",
"kind": "script",
"status": "uploaded",
"ready": true,
"mount_path": "/workspace/scripts/transcribe.py"
}
]
}
Submit with uploaded script
{
"name": "uploaded-transcription",
"image": "pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime",
"workload_type": "inference",
"command": ["python", "/workspace/scripts/transcribe.py", "/workspace/inputs/audio.ogg", "/workspace/artifacts/transcript.txt"],
"script_files": [{ "input_id": "inp_script123" }],
"input_files": [{ "input_id": "inp_audio123" }],
"expected_artifacts": ["/workspace/artifacts/transcript.txt"]
}
06
Submit a Job
POST /v1/jobs creates an asynchronous workload and can reserve or consume credits. Required fields are name, image, and workload_type unless a template fills them. The preferred command representation is an array of strings; the API normalizes it into a command plus args. Legacy string command handling remains supported.
- •Command plus args are limited by validation; large scripts should use upload slots and script_files.
- •expected_artifacts paths are normalized under /workspace/artifacts and do not disable automatic collection.
- •environment keys and values are validated and platform-managed JUNGLEGRID_* variables may be injected or overwritten.
- •Region, GPU family, class, and priority fields are constraints or preferences. Arbitrary region strings are accepted and normalized where possible.
- •The API currently documents no public CPU, memory, provider, timeout, or retries submit field. Retry count is reported on job status when retries occur.
Fields
Required:
name: string
image: string
workload_type: inference | training | fine-tuning | batch
Common optional:
id: string
template_slug: string
command: string | string[]
args: string[]
environment: Record<string, string>
input_files: Array<{ input_id: string }>
script_files: Array<{ input_id: string }>
expected_artifacts: string[]
callback_url: string
callback_auth_token: string
callback_metadata: Record<string, string>
Resource and routing:
model_size_gb: number
batch_size: number
precision: fp32 | fp16 | bf16 | int8
optimize_for: cost | speed | balanced
disk_gb: number
gpu_type: string
gpu_count: number
gpu_class: consumer | datacenter
preferred_gpu_family: string
avoid_gpu_families: string[]
region_preference: string
region_mode: prefer | strict
priority: low_latency | low_cost | balanced | high_reliability
latency_priority: low | balanced | high
cost_priority: low | balanced | high
constraints: object with routing aliases
Submit request
POST /v1/jobs
{
"name": "artifact-demo",
"image": "pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime",
"workload_type": "batch",
"model_size_gb": 1,
"command": ["python", "-c", "from pathlib import Path; Path('/workspace/artifacts/transcript.txt').write_text('done')"],
"expected_artifacts": ["/workspace/artifacts/transcript.txt"],
"optimize_for": "balanced",
"constraints": {
"max_price_per_hour": 2.5,
"latency_priority": "balanced",
"cost_priority": "high"
}
}
202 response
{
"job_id": "job_123",
"status": "queued",
"queued_at": "2026-06-11T12:00:00Z",
"execution_route": "standard_managed",
"route_reason": "Selected managed GPU route.",
"artifact_contract": {
"expected_artifacts": [
{
"path": "/workspace/artifacts/transcript.txt",
"filename": "transcript.txt",
"required": true
}
],
"automatic_collection_dir": "/workspace/artifacts"
}
}
07
Monitor Status and Events
Status and lifecycle events answer different questions. GET /v1/jobs/{job_id} returns the current state snapshot. GET /v1/jobs/{job_id}/events returns ordered lifecycle activity that can exist before workload logs begin.
- •High-level status values include pending, queued, assigned, running, completed, failed, rejected, and cancelled.
- •Execution phases include submitted, queued, waiting_for_capacity, provider_provisioning, worker_assigned, container_starting, runtime_preparation, image_download, workload_execution, running, cleanup, and terminal when emitted.
- •Events are ordered by sequence and timestamp. The current endpoint does not expose pagination or filters.
- •Use events to diagnose queueing, capacity lookup, scheduling, provisioning, input preparation, startup, retries, failures, and cancellation.
Status
GET /v1/jobs/{job_id}
{
"job_id": "job_123",
"status": "queued",
"phase": "provider_provisioning",
"execution_phase": "provider_provisioning",
"status_message": "Waiting for compatible capacity.",
"phase_started_at": "2026-06-11T12:00:00Z",
"phase_last_updated_at": "2026-06-11T12:01:30Z",
"wait_duration_seconds": 90,
"delayed_start": true,
"delay_reason": {
"code": "waiting_for_capacity",
"message": "Compatible capacity is still being prepared."
},
"scheduling": {
"state": "provider_provisioning",
"worker_assigned": false,
"delayed_start": true
},
"artifacts_ready": false
}
Lifecycle events
GET /v1/jobs/{job_id}/events
{
"job_id": "job_123",
"status": "queued",
"phase": "provider_provisioning",
"items": [
{
"id": "job_123:queued",
"job_id": "job_123",
"type": "job.queued",
"phase": "queued",
"title": "Job queued",
"message": "The job is eligible for scheduling.",
"source": "platform",
"level": "info",
"created_at": "2026-06-11T12:00:00Z",
"sequence": 20
}
],
"generated_at": "2026-06-11T12:01:30Z"
}
08
Read Logs and Runtime
Workload logs are persisted runtime log entries. Runtime is a separate summary surface for exit code, timeout, tails, and availability diagnostics. Empty log responses are normal before the process starts; use lifecycle events during queueing and startup.
- •The logs endpoint is paginated with next_cursor and has_more.
- •cursor fetches entries after the cursor; tail fetches the latest entries.
- •The persisted logs endpoint is not true streaming. Live log SSE exists separately at /v1/jobs/{job_id}/logs/live for clients that implement it.
- •Do not write secrets to stdout or stderr; logs are persisted and can be surfaced in the portal.
Logs
GET /v1/jobs/{job_id}/logs?tail=100&stream=all
Query:
tail=1..500
limit=1..500
cursor=<next_cursor>
stream=stdout|stderr|all
{
"job_id": "job_123",
"status": "running",
"items": [
{
"entry_id": 42,
"source": "app-stdout",
"category": "workload_stdout",
"stream": "stdout",
"message": "processing",
"created_at": "2026-06-11T12:02:00Z"
}
],
"next_cursor": 42,
"has_more": false,
"usage_hint": ""
}
Runtime
GET /v1/jobs/{job_id}/runtime
{
"job_id": "job_123",
"status": "completed",
"execution_mode": "batch_command",
"image": "pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime",
"command": "python",
"args": ["script.py"],
"exit_code": 0,
"timed_out": false,
"started_at": "2026-06-11T12:02:00Z",
"finished_at": "2026-06-11T12:03:00Z",
"stdout_tail": "done",
"stderr_tail": "",
"runtime_availability": {
"exit_code": { "state": "available" }
},
"diagnostics": []
}
09
Retrieve Artifacts
Managed jobs automatically collect regular files written under /workspace/artifacts. expected_artifacts declares user-meaningful expected paths such as /workspace/artifacts/transcript.txt, while artifact listing shows what is actually available.
- •Download URLs are temporary bearer secrets and should not be logged.
- •Missing artifacts return NOT_FOUND; artifacts that exist but are not uploaded return ARTIFACT_NOT_READY.
- •Failed jobs can have no artifacts, partial artifacts, or artifacts uploaded before failure.
- •Artifact storage maintenance returns MAINTENANCE_ACTIVE; disabled storage returns ARTIFACTS_UNAVAILABLE.
Produce transcript
{
"name": "transcript-artifact",
"image": "pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime",
"workload_type": "batch",
"command": ["python", "-c", "from pathlib import Path; Path('/workspace/artifacts/transcript.txt').write_text('hello')"],
"expected_artifacts": ["/workspace/artifacts/transcript.txt"]
}
List artifacts
GET /v1/jobs/{job_id}/artifacts
{
"artifacts": [
{
"artifact_id": "art_123",
"job_id": "job_123",
"filename": "transcript.txt",
"content_type": "text/plain",
"size_bytes": 5,
"status": "ready",
"ready": true,
"created_at": "2026-06-11T12:03:00Z",
"uploaded_at": "2026-06-11T12:03:10Z"
}
]
}
Create download URL
POST /v1/jobs/{job_id}/artifacts/{artifact_id}/download
{
"artifact": {
"artifact_id": "art_123",
"job_id": "job_123",
"filename": "transcript.txt",
"status": "ready",
"ready": true
},
"url": "https://signed-download.example/...",
"expires_at": "2026-06-11T12:18:10Z"
}
10
Cancel a Job
POST /v1/jobs/{job_id}/cancel requests cancellation for a non-terminal job. Jungle Grid marks the job cancelled, handles billing reservation release or final billing as appropriate, and triggers managed teardown paths where available.
- •Cancellation requires jobs:submit or jobs:write.
- •Completed, failed, rejected, or already cancelled jobs return 409 CONFLICT.
- •Lifecycle events and status show cancellation state after the request succeeds.
- •Do not assume immediate provider infrastructure shutdown; teardown is requested through the managed route when available.
Cancel
POST /v1/jobs/{job_id}/cancel
{
"reason": "user requested stop"
}
200 response:
{
"job_id": "job_123",
"status": "cancelled",
"status_reason": "user requested stop"
}
11
Callbacks
Submit can attach a terminal callback. Jungle Grid dispatches callbacks for terminal job states, includes configured metadata as correlation data, sends the optional callback auth token as Authorization: Bearer <token>, and retries retryable failures according to server configuration.
- •callback_url must use HTTPS unless it targets localhost.
- •callback_auth_token is optional and limited by validation; treat it as a secret.
- •callback_metadata supports up to 16 string entries with validated key and value lengths.
- •Callback requests include X-JungleGrid-Callback-Version, X-JungleGrid-Callback-Timestamp, X-JungleGrid-Job-ID, and X-JungleGrid-Job-Status headers.
- •A callback signature header is sent only when the server has a callback signing secret configured. Do not require signatures unless your environment has verified that support.
- •Handle callbacks idempotently because retries can deliver the same terminal state more than once.
Submit with callback
{
"callback_url": "https://api.example.com/jungle/callback",
"callback_auth_token": "downstream_callback_token",
"callback_metadata": {
"request_id": "req_123",
"customer_id": "cus_456"
}
}
Payload fields
{
"version": "v1",
"job_id": "job_123",
"status": "completed",
"status_reason": "",
"occurred_at": "2026-06-11T12:03:00Z",
"created_at": "2026-06-11T12:00:00Z",
"queued_at": "2026-06-11T12:00:01Z",
"started_at": "2026-06-11T12:02:00Z",
"finished_at": "2026-06-11T12:03:00Z",
"image": "pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime",
"command": "python",
"args": ["script.py"],
"exit_code": 0,
"timed_out": false,
"assigned_node_id": "node_123",
"provider_name": "runpod",
"provider_node_id": "provider_node",
"correlation_data": {
"request_id": "req_123"
}
}
12
MCP Server
The MCP server wraps the production workflow for agents. It supports local stdio with JUNGLE_GRID_API_KEY and hosted Streamable HTTP at https://mcp.junglegrid.dev/mcp with OAuth protected-resource discovery. MCP submit exposes the core workload fields, uploaded input/script references, expected artifacts, routing mode, template, and metadata; it does not currently expose callback fields.
Tools
estimate_job
submit_job
upload_job_input
list_job_inputs
list_jobs
get_job
get_job_events
get_job_logs
cancel_job
list_artifacts
get_artifact
13
Error Handling
API errors use JSON with code and message, sometimes nested under error and sometimes with details for screening or validation. MCP routes wrap successful responses as { ok: true, data } and errors as { ok: false, error }.
- •Malformed JSON returns INVALID_REQUEST.
- •Invalid workload types, invalid precision, invalid optimize_for, invalid callback URL, and oversized environment or command payloads return INVALID_REQUEST.
- •Unavailable capacity can return a conflict for strict-region submissions or an estimate with available=false.
- •Failed and cancelled jobs should be handled as terminal states, not transport failures.
- •Rate-limit style errors are emitted only by configured admission control and include Retry-After when available.
Error shape
{
"error": {
"code": "INVALID_REQUEST",
"message": "name, image, and workload_type are required",
"details": {
"screening": {
"can_submit": false
}
}
}
}
Common errors
401 UNAUTHORIZED
403 FORBIDDEN
400 INVALID_REQUEST
400 INVALID_EXPECTED_ARTIFACT
402 INSUFFICIENT_FUNDS
404 NOT_FOUND
404 JOB_INPUT_NOT_FOUND
409 CONFLICT
409 JOB_INPUT_NOT_READY
409 ARTIFACT_NOT_READY
422 CPU_ONLY_WORKLOAD_UNSUPPORTED
422 ACCELERATOR_REQUIREMENT_UNSPECIFIED
429 USER_CONCURRENT_LIMIT | JOB_RATE_LIMITED | QUEUE_SATURATED
503 MAINTENANCE_ACTIVE
503 JOB_INPUTS_UNAVAILABLE
503 ARTIFACTS_UNAVAILABLE
500 INTERNAL_ERROR