Quickstart
This page leads to a first successful run: get a token, create a project, upload code, create a job, start it, read its logs. Everything goes through the API — the same one the console uses, there is no privileged API reserved for the interface.
Prerequisites: an installation that answers (see installation), an
account in your tenant’s authentication realm, and the Project Owner or Tenant Administrator
role.
0. The two headers that never change
Section titled “0. The two headers that never change”Every request carries:
Authorization: Bearer <token>X-Tenant: <your-tenant>X-Tenant is what selects the authentication realm, not the URL. A request without that header
is not “for the default tenant”: it is incomplete.
1. Get a token
Section titled “1. Get a token”GRAAL_API=https://app.example.com/api/v1TENANT=energie-demo
TOKEN=$(curl -s -X POST \ "https://identity.example.com/realms/$TENANT/protocol/openid-connect/token" \ -d grant_type=password -d client_id=graal-ui \ -d "username=$USER_EMAIL" -d "password=$USER_PASSWORD" \ | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')The password flow is convenient for a try-out. For anything durable — a script, an integration, an agent — create an application and use its machine credentials rather than your own (see roles and permissions).
2. Create a project
Section titled “2. Create a project”The media type carries the resource name. This is the first trap:
curl -s -X POST "$GRAAL_API/projects" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant: $TENANT" \ -H 'Content-Type: application/vnd.graal.systems.v1.project+json' \ -d '{"name":"conso-energie","description":"First project"}'3. Create the execution identity
Section titled “3. Create the execution identity”A job runs under an identity, which is not you.
curl -s -X POST "$GRAAL_API/identities" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant: $TENANT" \ -H 'Content-Type: application/vnd.graal.systems.v1.identity+json' \ -d '{"name":"exec-conso","description":"Execution identity for the project jobs"}'4. Upload the code
Section titled “4. Upload the code”A job’s code is not pasted into its definition: it arrives through a library. You upload the
file, the API returns a key, and the scheduler copies it into /workspace before start-up.
cat > first_job.py <<'PY'import osprint("bucket:", os.environ.get("GRAAL_BUCKET"))print("hello from graal")PY
KEY=$(curl -s -X POST "$GRAAL_API/libraries" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant: $TENANT" \ -F file=@first_job.py \ | python3 -c 'import sys,json; print(json.load(sys.stdin)[0]["key"])')5. Create the job
Section titled “5. Create the job”JOB=$(curl -s -X POST "$GRAAL_API/projects/$PROJECT_ID/jobs" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant: $TENANT" \ -H 'Content-Type: application/vnd.graal.systems.v1.job+json' \ -d "{ \"name\": \"first-job\", \"identity_id\": \"$IDENTITY_ID\", \"timeout_seconds\": 900, \"max_retries\": 0, \"libraries\": [{\"type\": \"file\", \"key\": \"$KEY\"}], \"options\": { \"type\": \"python\", \"file\": \"first_job.py\", \"instance_type\": \"Standard_Development_D1_v1\" } }" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])')This first job is of type python. The other types (bash, spark, lowcode and the
specialised types) are described on the jobs and workflows page.
6. Start a run
Section titled “6. Start a run”curl -s -X POST "$GRAAL_API/jobs/$JOB/runs" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant: $TENANT" \ -H 'Content-Type: application/vnd.graal.systems.v1.run+json' \ -d "{\"infrastructure_id\": \"$INFRA_ID\"}"7. Read the logs
Section titled “7. Read the logs”Logs are paginated by cursor, not by page: you call again with the cursor returned by the previous call and you get the continuation. That is what makes it possible to follow a run live without re-reading from the beginning.
curl -s "$GRAAL_API/jobs/$JOB/runs/$RUN/logs" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant: $TENANT" -D -The response’s X-Cursor header is what you send back on the next request.
If nothing happens
Section titled “If nothing happens”A run that starts and never completes, with no message, almost always has the same cause: the internal URL of the API is not configured, so the workload cannot call the platform back to report progress. It is the most expensive failure on the list because every visible signal is green — see troubleshooting.
What next
Section titled “What next”- Concepts and vocabulary — if a word on this page was unclear
- Secrets and libraries — let your job reach a database
- Jobs and workflows — schedule, chain
- REST API — pagination, errors, edits through JSON Patch
- AI agents (MCP) — have an agent do all of the above