Jobs and the API
Jobs and the API. The Hangar API lets a program start jobs, follow them and download the results. Each job that a request starts uses your prepaid credit, and each request key has its own spend limit.
The OpenAPI document docs/api/openapi.json lists every route, request and answer. docs/api/errors.json lists the three parts of every error (what failed, the next step and what to do if that fails) and says whether a client may retry the request. Every error body also contains reference, the id of the request. When support is the next step, the third part of the error shows reference.
Three pages go further. Releases and jobs explains packages, releases, channels, the envelope and the return. Pools explains where a job executes, and the customer-controlled form. The Hangar SDK explains request keys, tenants and the Python package gethangar.
Calling the API
- The CLI and the apps send a bearer token with a
Hangar-Proofheader on every request. The page "Proof of possession: the Hangar-Proof header" explains it. - The website sends its session cookie.
- Your own server, a vendor's or any organisation's, sends
Authorization: Hangar-Key hgk_...with aHangar-Signaturemade with its request key, andHangar-Tenantfor a tenant. The routes markedx-hangar-sdkin the OpenAPI document take a request key as well as a session. The Hangar SDK page explains the signature, the capabilities and the tenants. - Every request that changes state needs an
Idempotency-Keyheader with a new UUID. To retry, send the same key again. Hangar returns the first answer and does the work once. - Times are Melbourne local, as an object with
at(RFC 3339 with the offset) andlabel(for exampleSat 24 Oct 22:50 AEDT).
Jobs
A job is one container command inside a booking. hangar run submits one job. A booking can contain several jobs. The jobs of a booking use one instance, in the order of their seq numbers.
| Call | What it does |
|---|---|
POST /v1/slots/{id}/jobs | Submits a job into your confirmed booking. |
GET /v1/jobs?limit=&cursor=&status= | Lists every job in your organisation, newest first, 50 to a page by default and at most 100. Pass next_cursor as cursor for the next page. |
GET /v1/slots/{id}/jobs | Lists the jobs in one booking, in order. |
GET /v1/jobs/{id} | Gets one job: the image and digest, the command, the state, the times the job was submitted, started and ended, the exit code (after the job finishes), the time Hangar signed the certificate, and the position of the job. |
POST /v1/jobs/{id}/cancel | Cancels a job that has not started. |
Jobs started by a request key
A request key with the jobs capability starts a GPU now, as a person does. A CI job or an agent can start a GPU with no person signed in.
| Call | What a jobs key may do |
|---|---|
GET /v1/instant, POST /v1/instant | Read what can start now, and start an H100, a Trainium2, an L4 or a fractional L4. |
GET /v1/instant/{id}, POST /v1/instant/{id}/stop | Read a session, and stop any session of your organisation, whoever started it. |
GET /v1/jobs, GET /v1/jobs/{id}, /logs, /outputs, /events, /certificate | Read the jobs of your organisation: the position, logs, outputs and certificate of each job. |
GET /v1/slots, GET /v1/slots/{id}, /jobs, /certificate, GET /v1/certificates/{id} | Read the bookings and sessions of your organisation: the jobs and certificates of each. |
POST /v1/objects/{digest}/download, GET /v1/objects/{digest}/content | Fetch an output that a job of your organisation wrote. Any other object needs uploads. |
The rules:
- A key spends credit only through the sessions that the key starts. Hangar meters those sessions as it meters every session, inside the limits of your organisation: the sign-up checks, the current terms, the new-account limit of one live GPU at a time, and credit for the first ten minutes. A key of an organisation with a held or declined sign-up starts no session (
account_not_active). - A key may also have its own spend limit, in Australian cents including GST (
spend_limitonPOST /v1/keys, orPUT /v1/keys/{id}/spend-limitby an owner). Each time the key starts a session, Hangar takes two amounts from the limit: what the jobs of the key were charged (less refunds), and what the running sessions of the key may still be charged. If less than one minute at the price of the GPU is left, Hangar refuses the start withkey_spend_limit_reached(402). Hangar starts nothing and charges nothing. The error detail containslimit_cents,spent_centsandheld_cents. Otherwiseends_byof the session is the time when the rest of the limit runs out. The stop afterends_bytakes up to the interval of the meter, the three-minute grace period and the wipe. For each session, the spend of the key can pass the limit by the cost of that time. GET /v1/keysandGET /v1/keys/{id}show thespend_limitof each key,spent_cents,jobs_startedandimages_pushed.- A key never books, holds, confirms or cancels a booking, tops up, approves, invites, changes a policy or makes a key. Those routes answer a key with
not_signed_in. - The start record, the session, the booking and each job show which key started them. The job and the session contain
started_by:{"kind": "key", "id": ...}with the key's id asGET /v1/keyslists it, or{"kind": "person", "id": ...}. - A revoked key is refused from its next request (
key_revoked). A session that the key started continues until the session ends, or until a person or another key stops the session. - These routes cover the jobs of your organisation. Hangar refuses a key that belongs to a tenant on these routes (
tenant_mismatch), and a key withoutjobsgetskey_capability_missing.
Images pushed by a request key
A request key with the uploads capability sends the image that the jobs of the key use. A CI job can then build, send and start with no person signed in.
| Call | What an uploads key may do |
|---|---|
POST /v1/images/push | Get a 15-minute credential to push one repository to your organisation's registry in Sydney. The CLI then sends the layers and the manifest to /v2/ with that credential. |
POST /v1/images/push/complete, POST /v1/images/{name}/tag | Give the pushed manifest its tag, and point another tag at a digest that you hold. |
POST /v1/images/pull | Register a one-time pull of a public image by its digest. |
GET /v1/images, GET /v1/images/resolve | List your images and resolve a name and tag to a digest. A jobs key reads these too. |
The rules:
- The push credential, the records and the image show who pushed the image. Each image has
pushed_by:{"kind": "key", "id": ...}or{"kind": "person", "id": ...}, the person or key that last pushed, registered or tagged the image. - Hangar checks an image that a key pushes before the image runs, as Hangar checks every image. Only a person can approve a job of an image that your policy does not list, change the policy, delete an image or do anything that pays. A key gets
not_signed_infor these actions. - The registry is your organisation's and has no tenant, so a key that belongs to a tenant is refused these routes (
tenant_mismatch). A key withoutuploadsgetskey_capability_missing. - Hangar refuses a revoked key from the next request. The push credentials of the key stop working.
- The spend limit applies to jobs, never to uploads or images.
Uploads
hangar upload PATH and the Python package send a file in chunks. Each chunk is checked, sealed under the file's own key and stored in Sydney. Hangar then checks the digest of the whole file and stores the file once.
| Call | What it does |
|---|---|
POST /v1/uploads | Begins an upload. The body gives size_bytes and the sha384: digest of the whole file. The answer gives upload, chunk_bytes and chunk_count. |
PUT /v1/uploads/{id}/chunks/{n} | Sends chunk n (from 0), with its own digest in Hangar-Chunk-Digest. |
POST /v1/uploads/{id}/complete | Asks Hangar to check the whole digest and store the file. |
GET /v1/uploads/{id} | Gets the state of the upload and, when the file is stored, the object. |
The chunk size is per upload. The size is 8 MiB while a file needs at most 9,000 chunks of 8 MiB (70.3 GiB). A larger file gets larger chunks, in whole MiB, so the file stays at 9,000 chunks or fewer: 43 MiB for 400 GB, and 59 MiB for 512 GiB. Use the chunk_bytes that POST /v1/uploads gives.
Hangar adds each chunk to the digest of the whole file as the chunk arrives in order. When the chunks arrive out of order, the chunk that fills a gap adds the chunks after the gap. So when you complete the upload, Hangar has usually checked every byte already.
POST /v1/uploads/{id}/complete answers in one of these ways:
- When a chunk is missing, Hangar answers
upload_incomplete(409) at once.detail.missing_chunkslists the chunk numbers. Send those chunks, then complete again with the sameIdempotency-Key. - With the header
Prefer: respond-async, Hangar answers202at once with the state of the upload, and the headerPreference-Applied: respond-async. Follow the upload withGET /v1/uploads/{id}. - Without the header, Hangar holds the request while it checks and stores the file, for up to 300 seconds (
limits.completion_hold_secs). When the file is stored in that time, the answer is201with the object. When it is not, the answer isidempotency_in_progress(409): send the same request with the sameIdempotency-Key, and the final answer for that key is the201object. - When the file is already stored, the answer is
201with the object. A second request, with any key, never starts a second check.
GET /v1/uploads/{id} gives:
| Field | What it is |
|---|---|
id | The upload. |
status | open, completing, done or failed. |
completion_state | queued, checking, composing, done or failed, or null before you complete. |
chunks_total, chunks_hashed | The chunks of the file, and the chunks that the digest covers so far. |
result | When done: the object, the same body as the 201 of complete, with received_record. |
error | When failed: an error body with code, sentence, next_step, if_that_fails and reference. |
The errors at the end:
upload_digest_mismatch(422): the bytes did not match the digest. Hangar removes the chunks and stores nothing. Upload the file again.upload_completion_failed(503): Hangar could not store the file after three tries. Hangar keeps the chunks. Complete the upload again, with a newIdempotency-Key. Hangar checks and stores the file again, and you send no chunk twice.hangar uploaddoes this when you run the same command again.tenant_suspendedortenant_closed: the tenant of the upload changed state before Hangar stored the file. Hangar removes the chunks and stores nothing.
A person, or a request key with uploads, calls these routes. A key that belongs to a tenant sees the uploads of its tenant only. The upload of another organisation or another tenant is upload_not_found. The movement record of the receipt names the person or the key that completed the upload.
Jobs of a release
A job of a release runs a vendor's accepted application release for one tenant, on the CPU pool hosted-cpu-syd. Before the job, TALOS Warrant (the part of Hangar that decides what a job may do) compiles the envelope, the set of limits that the job cannot leave. hangar run --release or hangar run --package --channel starts the job. hangar status, hangar logs, hangar pull, hangar stop and hangar verify work on a job of a release as they work on any job. The Releases and jobs page explains the whole contract.
| Call | What it does |
|---|---|
POST /v1/release-jobs | Starts a job of a release, with the release id or a package and channel, the tenant, the inputs, an optional narrowing and your client_ref. |
GET /v1/release-jobs?tenant=&release=&state=&prefix=&limit=&cursor= | Lists your jobs of releases, newest first. |
GET /v1/release-jobs/{id} | Gets one job: the release as resolved, the tenant, the pool, the state and outcome, the compiled envelope, each attempt, the inputs and the artefacts. |
GET /v1/release-jobs/{id}/events?after=&wait= | Gives the events of the job after an offset, as newline-delimited JSON. The call waits up to 15 seconds for the next event. |
POST /v1/release-jobs/{id}/cancel | Cancels the job. Cancellation is final. |
GET /v1/release-jobs/{id}/artefacts | Lists the artefacts of the job, each with a content link. |
GET /v1/release-jobs/{id}/return | Gets the return of the job, as CBOR. The return is the job certificate of the release. |
Following a job
GET /v1/jobs/{id}/events?after=&wait= follows a job as it prepares and runs. Each answer gives the position of the job now and the events since your last call:
preparation: a step of the preparation of the booking, and the time of the step. The steps are 0 the fresh instance, 1 the measured boot, 2 the reset and GPU checks, 3 the image and data staged, and 4 the job started.status: the state of the job, each time the state changes.log: one chunk of the log, with the lines of the chunk, the stream and the time Hangar received the chunk. The stream isstdoutorstderrfor the output of the job, andsystemfor the lines that Hangar writes about the job: the start of each attempt, a stop signal and why, each checkpoint sealed and stored, the exit code and the outputs sealed.
The position shows the phase of the job, the preparation step and the elapsed time. It also shows what the worker reports:
checkpoint: the last checkpoint sealed in the booking, with its time, SHA-384 digest and size, andcheckpoint_count. A checkpoint is sealed and stored in Sydney when an attempt ends.gpu_util_percent,gpu_memory_bytesandgpu_memory_total_bytes: the GPUs' use at the last heartbeat, withgpu_reported, the time the worker read them.envelope: the egress decisions of the booking's proxy so far, asrefusalsandconnections, with the time of the last refusal. Each refusal is also a signed record on the certificate.
GET /v1/jobs/{id} shows the same parts in position, with exit_code: 0 for a job that finished by itself, and the code of the process for a job that ended with another code or was stopped. certificate_signed is the time Hangar signed the certificate of the booking. That certificate covers the job.
Pass next from the answer as after in the next call. With wait (up to 25 seconds), Hangar delays the answer until new data exists or the job ends. When complete is true, the job has ended and you have the whole log.
Logs and outputs
GET /v1/jobs/{id}/logs?after=&max=gives the log lines of the job in order. Each line has the number of its chunk (seq), its place in the chunk (n) and its stream:stdout,stderr, orsystemfor lines from Hangar. The three streams share one sequence, so the log shows the lines in the order that they were written. To read the next lines, passnext_seqasafter. Whentruncatedis true, Hangar holds more lines than the call returned.GET /v1/jobs/{id}/outputslists what the booking of the job wrote, by SHA-384 digest. Each object shows its class, size, retention date and the time of writing. Download an output withPOST /v1/objects/{digest}/download.
Hangar stores each log chunk in Sydney, sealed under the key of the job.
Certificates
Every booking ends with a job certificate, signed by Hangar and anchored in the public log. One certificate covers every job in the booking.
| Call | What it does |
|---|---|
GET /v1/certificates?kind=&limit=&cursor= | Lists every certificate that your organisation holds, newest first. Each entry shows the kind (slot, sandbox_session, data or release_run), the booking or session, the jobs that the certificate covers and the public page. |
GET /v1/certificates/{id} | Gets the certificate, as CBOR. |
GET /v1/jobs/{id}/certificate | Gets the certificate of the booking of the job. |
GET /v1/certificates/{id}/report | Gets the report of the verifier on the certificate: the verdict, each check and the facts. |
The report comes from the same verifier as hangar verify --json. The verifier checks the certificate bytes against the published key set and tree heads. To check a certificate yourself, download the certificate and run the offline verifier. The offline verifier uses only the file and the published keys.
A certificate's public link
Each certificate has a public page at {public_origin}/c/{page_key}. The list shows the address as page. The download of a stored certificate shows the address in the hangar-certificate-url header. The page key is 32 hexadecimal characters, chosen at random when Hangar signs the certificate. The link is the permission. Anyone who has the link can read the certificate without a sign-in. To show a certificate to an assessor, give the assessor the link.
| Call | What it does |
|---|---|
GET /v1/public/certificates/{page_key} | Gets the certificate that the link points to, as CBOR. The bytes equal the bytes from GET /v1/certificates/{id}. |
GET /v1/public/certificates/{page_key}/report | Gets the report of the verifier on the certificate. The JSON equals the JSON from GET /v1/certificates/{id}/report. |
These two routes find a certificate only by the page key, never by the id. A page key that matches no certificate gets not_found. Each answer has the headers Cache-Control: private, no-store and X-Robots-Tag: noindex. No shared cache keeps a copy, and no search engine lists the certificate.
Your account
| Call | What it does |
|---|---|
GET /v1/me | The signed-in person, the role of that person, and the legal name and ABN or ACN of the organisation. |
GET /v1/passkeys | Your passkeys: the label of each passkey, and when you added it and last used it. |
DELETE /v1/passkeys/{id} | Removes one of your passkeys. You must keep at least one passkey, so you can always sign in. |
POST /v1/passkeys/add/start, POST /v1/passkeys/add/finish | Adds another passkey, such as a phone beside a hardware key. Add it within 15 minutes of signing in with the browser. With two passkeys, losing one device does not lock you out. |
GET /v1/sessions | Your signed-in sessions, with the client of each session. One entry shows the session that you use now. |
POST /v1/sessions/{id}/revoke | Ends a session. You can end your own session. An owner can end any session in the organisation, for example after a lost laptop. |
GET /v1/organisation/members | The people in your organisation, the role of each person and whether the person has a passkey. |
POST /v1/organisation/invitations | An owner invites a person with the email, name and role of the person. Hangar emails the address. The answer is "Invitation sent" for every address. The person signs in at gethangar.au/login with the address, gets a code, and creates a passkey within seven days. When you invite the same address again, Hangar opens a new invitation and closes the old invitation. |
POST /v1/organisation/members/{user}/reset | An owner resets a member who lost the passkeys of the member. Every passkey and session of the member ends, and a new invitation opens. The member signs in at gethangar.au/login and creates a new passkey. Every owner and the member get an email. An owner cannot use this route on the account of that owner. TALOS Defence support recovers the last owner. |
GET /v1/organisation/recoveries, POST /v1/organisation/recoveries/{id}/cancel | The open recovery cases that TALOS Defence support started for a person in your organisation, and the cancellation of a case. Any member can cancel a recovery case during the 72-hour wait. |
GET /v1/organisation/seal | The mode of the organisation: standard, or sealed. In a sealed organisation, TALOS Defence support cannot recover the accounts, and the owners hold a recovery kit instead. Hangar cannot help you recover a sealed organisation. |
POST /v1/organisation/seal/requests, .../{id}/approve/start, .../{id}/approve/finish, .../{id}/cancel | An owner asks to seal or unseal the organisation. Every owner approves with a passkey. The last approval makes the change, and Hangar emails every member. Every owner needs two passkeys while the organisation is sealed. With two or more owners, the kit has three shares, and any two shares recover an owner. With one owner, the kit is one code, and the organisation gets only that code. |
GET /v1/organisation/seal/shares, POST /v1/organisation/seal/shares/{id}/stored | Your shares of the kit. Each share is sealed to a key that your browser made, so Hangar stores no share that Hangar can read. When you confirm that you stored a share, Hangar removes the copy of the share. Hangar shows a share for 24 hours after Hangar makes the share, and no longer. |
POST /v1/auth/login/kit | After the code at gethangar.au/login, an owner of a sealed organisation presents two shares, or the single code, and creates a new passkey. Every earlier passkey of that owner ends. A split kit that opened a recovery is spent. The shares of a spent kit open nothing again, and the organisation stays sealed. GET /v1/organisation/seal shows kit_reissue_due until the owners approve a new kit with a seal request. kit_reissue_due also shows when an owner did not collect a share within 24 hours. A new kit voids every share of the old kit. |
POST /v1/organisation/wipe | An owner asks Hangar to wipe and close the organisation. The owner types the legal name of the organisation and must have signed in within the last 15 minutes. Hangar answers 202 with wipe: processing and the message that every wipe shows: "We are processing this. It may take up to 7 days. We will notify you when it is complete." Hangar emails the owners when the wipe is complete. Hangar keeps the tax invoices and the payment ledger, which the ATO requires for five years, the legal name and ABN on those records, and the record of the wipe. Hangar removes everything else: stored files, outputs, images, job logs, secrets, keys, passkeys, and the name and address of each person. Hangar returns the credit that is left to the card that paid for the credit. |
Status and open nights
These two routes need no sign-in.
GET /v1/statusgives the service status: the state and release of the API, the latest status line of each component, the incidents of the last 14 days by reference, and the latest signed tree head of the public log.GET /v1/public/nightslists the open nights. Each night shows the bookable window, the free minutes, whether the night is open, filling or full, and the price for each hour including GST.
Records
Every change that you make through the API becomes a signed record in the record chain of your organisation, before Hangar changes a row in the database. A removed passkey, an ended session and an invitation are records too.