Skip to content

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.

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.

Fenêtre de terminal
GRAAL_API=https://app.example.com/api/v1
TENANT=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).

The media type carries the resource name. This is the first trap:

Fenêtre de terminal
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"}'

A job runs under an identity, which is not you.

Fenêtre de terminal
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"}'

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.

Fenêtre de terminal
cat > first_job.py <<'PY'
import os
print("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"])')
Fenêtre de terminal
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.

Fenêtre de terminal
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\"}"

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.

Fenêtre de terminal
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.

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.