/submission.md. The full docset is at /llms-full.md and the index is at /llms.md.Project Configuration
C3 projects are configured with a .c3 YAML file at the project root. Run c3 deploy from anywhere in the project to submit a job.
Environment: Every job runs a Bash script. Use python: or docker: when you want C3 to prepare the environment; omit both for Bash-only jobs where setup happens inside the script each time. See Environment.
Data mounting: Your data lives in C3's centralised storage. You tell C3 which datasets to mount and where using datasets:. Your script reads from that path as if the files were local. See Data Mounting.
Artifact output: Files on the job machine are discarded when the job ends. Write results to $C3_ARTIFACTS_DIR or a directory listed in output:. Only these are collected. See Artifact Output.
Configuration reference
| Field | Type | Description |
|---|---|---|
project | string | Project name (auto-generated if not set). Lowercase alphanumeric + hyphens. |
script | string | Required. Path to a bash script with your execution commands (e.g. run.sh), relative to .c3 |
hardware | string | Optional C3 hardware selector. Omit it or set a primary GPU class (l40, a100, h100) to let C3 pick an enabled exact provider offering inside that class and charge the selected provider/profile rate. Run c3 list for public class ranges and c3 list -al for provider/profile rows. Exact profiles such as l40s-d-32x192, a100-80gb-pcie, h100-80gb, cpu-d3-96vcpu-384gb, or cpu-e2-48vcpu-192gb pin that concrete hardware profile. cpu is a display group, not a selectable profile. |
gpu | string | Compatibility alias for GPU profiles. Do not set both gpu and hardware unless they name the same profile. |
provider | string | Optional compute provider pin. Omit it to let C3 auto-route across available marketplace providers. Discover public provider IDs and their hardware with c3 list --all, or filter with c3 list --provider <provider-id> --all. |
capacity.on_unavailable | string | What to do when C3 receives an authoritative provider out-of-stock response: wait (default) keeps an accepted job pending on its selected pool, while fail rejects or terminates it immediately. When provider and hardware are both pinned, wait also permits an otherwise eligible out-of-stock route to be accepted as blocked. |
capacity.max_wait_minutes | integer | Maximum duration of a continuous PROVISIONING_BLOCKED episode. Defaults to 60; valid values are 1–360. This also bounds ambiguous/unknown and repeated-failure capacity blocks. |
time | string | Maximum runtime in HH:MM:SS format. You're only charged for actual usage |
job_name | string | Job display name |
python.project | string | Path to Python project dir with pyproject.toml + uv.lock. Mutually exclusive with docker: |
docker.image | string | Public Docker Hub image reference to pull on the job machine. Mutually exclusive with python: |
docker.requires_accelerator | string | Optional Docker accelerator requirement: cuda for CUDA/NVIDIA images, or none for CPU/no-accelerator images. C3 rejects mismatches before the script runs. |
datasets | list | Datasets to mount (ref + optional mount). See Data Mounting |
output | list | Directories to collect as artifacts. See Artifact Output |
Full example
All job configuration goes in .c3. The script field points to a bash script containing your execution commands — no #SBATCH or #C3 directives needed.
# .c3
project: my-experiment
script: run.sh
hardware: l40
time: "04:00:00"
job_name: train-model
python:
project: ./
datasets:
- ref: /datasets/imagenet
mount: /data/imagenet
output:
- ./checkpoints
- ./results
To pin a provider, add provider: or pass c3 deploy -p <provider>.
Pinned providers still pass through the same route-preview, stock, inventory,
and spend guard checks as auto-routed jobs.
Capacity waiting policy
By default, an accepted job waits for up to 60 minutes when its selected provider pool becomes unavailable. You can choose a longer bounded wait or restore fail-fast behavior:
capacity:
on_unavailable: wait # wait | fail; default: wait
max_wait_minutes: 180 # 1–360; default: 60
on_unavailable applies specifically to authoritative out-of-stock or
capacity-unavailable signals. With wait, the job remains PENDING with a
PROVISIONING_BLOCKED reason and C3 continues reevaluating the same selected
provider, region, and hardware pool. With fail, that signal immediately ends
the job with PROVIDER_CAPACITY_UNAVAILABLE. If both provider and hardware
are explicitly set, an otherwise eligible route that is blocked only by
current stock can be accepted directly into this waiting state. Route preview
reports available: false and capacity_status: waiting; job creation returns
the new PENDING job instead of a GPU_OUT_OF_STOCK 409. Setting
on_unavailable: fail preserves the immediate 409 and does not create a job.
max_wait_minutes applies to every continuous capacity-blocked episode,
including ambiguous create outcomes, unknown availability, and the existing
repeated-provisioning-failure threshold. Those non-authoritative signals wait
even when on_unavailable: fail, because C3 cannot safely conclude that the
provider rejected the create. A usable registered machine clears the blocked
episode. Reaching the configured deadline fails and refunds the job if no
compute ran.
Submission-time waiting is deliberately narrow: it requires both an explicit provider pin and an explicit C3 hardware class/profile. Provider-only, hardware-only, and fully automatic requests keep the existing routing and admission behavior. The policy does not enable provider, region, or hardware failover. Disabled/manual-target-zero pools, disabled offerings, unavailable provider configuration, stale pricing, and spend-guard blocks are not admitted through this stock-only exception. The independent six-hour queue ceiling remains the absolute backstop.
CPU jobs
CPU profiles are selected with hardware:, not gpu:. C3 derives
hardware_kind=cpu and accelerator_kind=none from canonical hardware
metadata; users do not need to declare a separate hardware kind.
The cpu class shown by c3 list is a display group rather than a selectable
profile. Choose an exact row from c3 list --class cpu --all. If a config uses
hardware: cpu, submission fails before routing with:
`cpu` is a hardware group, not a selectable profile.
Choose an exact CPU profile:
c3 list --class cpu --all
CPU availability is currently experimental. The profile is still usable
and billed normally, but capacity and C3-managed images may change. Route
preview emits a non-blocking warning when c3 deploy selects CPU hardware;
JSON mode keeps stdout parseable and writes that warning to stderr.
# .c3
project: cpu-simulation
script: run.sh
hardware: cpu-d3-96vcpu-384gb
time: "00:20:00"
docker:
image: ubuntu:24.04
requires_accelerator: none
output:
- ./results
# run.sh
#!/bin/bash
set -euo pipefail
mkdir -p results
python3 - <<'PY'
import json
import os
result = {
"cpus": os.cpu_count(),
"hardware": os.environ.get("C3_HARDWARE_PROFILE", "unknown"),
}
with open("results/cpu.json", "w") as f:
json.dump(result, f)
PY
CPU Docker jobs run without CUDA, NVIDIA drivers, or Docker --gpus flags. Use
docker.requires_accelerator: none for containers that should only run on
CPU/no-accelerator hardware. Use docker.requires_accelerator: cuda for images
that require CUDA so C3 rejects a CPU route before your script starts.
Machine-readable deploy output
Use c3 deploy --json when an automation script needs to submit a job and parse the result. --json cannot be combined with -f or --follow; submit first, then use c3 squeue --json or c3 deploy -f without --json for live logs.
In JSON mode, stdout contains one JSON object after successful submission. Human progress output is suppressed, and warnings are suppressed or written to stderr so stdout remains parseable.
c3 deploy --json
The response includes:
| Field | Description |
|---|---|
id | Submitted job ID |
status | Initial job status returned by the API |
provider | Selected provider ID, if the API assigned one at submission time |
region | Selected provider region; may be null when no region has been assigned yet |
gpu_profile | Legacy compatibility field containing the selected concrete C3 hardware profile, if assigned |
hardware_profile | Selected concrete C3 hardware profile, when returned by the API |
hardware_kind | Selected hardware kind, such as gpu or cpu, when returned by the API |
accelerator_kind | Selected accelerator kind, such as cuda or none, when returned by the API |
route.pool | Provider/region/hardware route string using the fields available on the created job |
route.warm_pool_hit | null until the API reports an explicit warm/cold dispatch signal; reserved for future use |
dashboard_url | Dashboard URL for the submitted job |
pull_command | c3 pull <job_id> command for downloading artifacts after the job completes |
# run.sh
#!/bin/bash
python3 train.py
See Environment for Python, Docker, and Bash-only examples.
Creating a new project
cd my-project
c3 init
This creates a .c3 file with sensible defaults. Edit it and deploy with c3 deploy.
API keys
For team billing or CI/CD, add an API key to .c3.local (keep this file out of version control):
# .c3.local — secrets (add to .gitignore)
api_key: c3_key_abc123def456...
Create keys with c3 apikey create my-team-key. API keys are scoped to the org that created them, and deploy uploads are stored under that same org. Create and use the key from the org that should own the project and job.
The API key takes priority over c3 login credentials. You can also set it with:
export C3_API_KEY=c3_key_abc123def456...
Use c3 whoami to verify the active credential and org before deploying. If a key was pasted into a shared shell, CI log, or support thread, revoke it with c3 apikey revoke <key-id> and create a replacement.