openapi: 3.2.0 info: title: Latchkey Jobs API version: 1.0.0 summary: Run a single command on a fresh Latchkey runner, with no GitHub in the path. description: The Jobs API gives a coding agent or CI system direct access to an ephemeral Latchkey runner. contact: name: Latchkey Support url: https://latchkey.dev/support email: support@latchkey.dev license: name: Proprietary url: https://latchkey.dev/terms servers: - url: https://api.latchkey.dev description: Production security: - latchkeyApiKey: [] tags: - name: Jobs description: Create, submit, observe and cancel runner jobs. paths: /jobs: post: operationId: createJob tags: - Jobs summary: Create a job description: Reserves a job in state `created` and returns its id. If `context_bytes` is supplied, the response includes a presigned URL to upload a gzipped tar archive of your working tree before submitting. Creating a job does not start it; call submitJob next. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateJobRequest' responses: '200': description: Job created. content: application/json: schema: $ref: '#/components/schemas/CreateJobResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/RateLimited' get: operationId: listJobs tags: - Jobs summary: List recent jobs description: Returns recent jobs for the organization that owns the API key, newest first. Served from durable storage, so it outlives an individual job record and can answer what was run last week. parameters: - name: limit in: query required: false description: Maximum jobs to return. schema: type: integer minimum: 1 maximum: 100 default: 20 responses: '200': description: A page of jobs. content: application/json: schema: $ref: '#/components/schemas/JobListResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /jobs/{id}: parameters: - $ref: '#/components/parameters/JobId' get: operationId: getJob tags: - Jobs summary: Get job status description: Returns the current state of one job, including exit code and failure reason once terminal. A submitted job that has not started within one hour is reported as `expired`. responses: '200': description: Current job status. content: application/json: schema: $ref: '#/components/schemas/JobStatusResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /jobs/{id}/submit: parameters: - $ref: '#/components/parameters/JobId' post: operationId: submitJob tags: - Jobs summary: Submit a job for execution description: Moves a job from `created` to `queued` and enqueues a runner for it. Only a job in `created` may be submitted, so a failed or cancelled job can never be resubmitted. Upload the context archive first if you created the job with `context_bytes`. responses: '200': description: Job queued. content: application/json: schema: $ref: '#/components/schemas/SubmitJobResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: Job is not in a submittable state. content: application/json: schema: $ref: '#/components/schemas/Error' /jobs/{id}/logs: parameters: - $ref: '#/components/parameters/JobId' get: operationId: getJobLogs tags: - Jobs summary: Read job logs description: Returns ordered log chunks from the given cursor. Poll with the returned `next_cursor` until `complete` is true to stream a running job's output. parameters: - name: cursor in: query required: false description: Chunk index to read from. Omit to start at the beginning. schema: type: integer minimum: 0 default: 0 responses: '200': description: Log chunks. content: application/json: schema: $ref: '#/components/schemas/JobLogsResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' /jobs/{id}/cancel: parameters: - $ref: '#/components/parameters/JobId' post: operationId: cancelJob tags: - Jobs summary: Cancel a job description: Requests cancellation. A job that has not started is cancelled immediately; a running job is signalled and terminalises shortly after. Cancellation is idempotent. responses: '200': description: Cancellation recorded. content: application/json: schema: $ref: '#/components/schemas/CancelJobResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' components: schemas: JobStatusResponse: type: object required: - job_id - state - runner_size - timeout_seconds - created_at properties: job_id: type: string description: Identifier of the job. state: $ref: '#/components/schemas/JobState' runner_size: $ref: '#/components/schemas/RunnerSize' timeout_seconds: type: integer description: Wall-clock limit in seconds. cancel_requested: type: boolean description: Whether cancellation has been requested. exit_code: type: - integer - 'null' description: Process exit code once the command has finished. failure_reason: type: - string - 'null' description: Why the job failed, when it did. created_at: type: string format: date-time submitted_at: type: - string - 'null' format: date-time started_at: type: - string - 'null' format: date-time completed_at: type: - string - 'null' format: date-time JobListResponse: type: object required: - jobs properties: jobs: type: array description: Jobs, newest first. items: $ref: '#/components/schemas/JobListItem' JobListItem: type: object required: - job_id - conclusion - runner_size - command_label - queued_at properties: job_id: type: string description: Identifier of the job. conclusion: type: string description: Terminal outcome, or an empty string while the job is still in flight. runner_size: type: string description: Runner size the job ran on. command_label: type: string description: Binary plus first subcommand, with arguments scrubbed. exit_code: type: - integer - 'null' description: Process exit code. duration_ms: type: integer description: Run duration in milliseconds. queued_at: type: string format: date-time completed_at: type: - string - 'null' format: date-time JobLogChunk: type: object required: - index - content properties: index: type: integer description: Zero-based chunk index. content: type: string description: Raw log text for this chunk. JobLogsResponse: type: object required: - chunks - next_cursor - complete properties: chunks: type: array description: Ordered log chunks from the requested cursor. items: $ref: '#/components/schemas/JobLogChunk' next_cursor: type: integer description: Cursor to pass on the next poll. complete: type: boolean description: True once the job is terminal and all logs have been returned. SubmitJobResponse: type: object required: - job_id - state properties: job_id: type: string description: Identifier of the submitted job. state: $ref: '#/components/schemas/JobState' context_present: type: - boolean - 'null' description: Whether a context archive was found for this job. CreateJobRequest: type: object required: - command - runner_size properties: command: type: string maxLength: 16384 description: The single shell command to run on the runner. examples: - npm ci && npm test runner_size: $ref: '#/components/schemas/RunnerSize' env: type: object additionalProperties: type: string description: Environment variables for the command. At most 64 keys and 32768 bytes total. maxProperties: 64 timeout_seconds: type: integer minimum: 30 maximum: 7200 default: 1800 description: Wall-clock limit for the command. context_bytes: type: integer minimum: 1 maximum: 209715200 description: Byte length of the gzipped tar archive you intend to upload. Supplying it returns a presigned upload URL bound to this exact length. CancelJobResponse: type: object required: - job_id - state - cancel_requested properties: job_id: type: string description: Identifier of the job. state: $ref: '#/components/schemas/JobState' cancel_requested: type: boolean description: Always true once cancellation has been recorded. CreateJobResponse: type: object required: - job_id properties: job_id: type: string description: Identifier for the new job. context_upload_url: type: string format: uri description: Presigned PUT URL for the context archive. Present only when `context_bytes` was supplied. Upload with Content-Type `application/gzip` before calling submitJob. RunnerSize: type: string description: Latchkey runner t-shirt size. Determines vCPU and memory. enum: - small - medium - large - xlarge Error: type: object required: - error properties: error: type: string description: Human-readable description of the problem. JobState: type: string description: Job lifecycle state. `succeeded`, `failed`, `cancelled` and `expired` are terminal. enum: - created - queued - provisioning - running - succeeded - failed - cancelled - expired responses: RateLimited: description: Per-organization job creation quota exceeded. content: application/json: schema: $ref: '#/components/schemas/Error' Forbidden: description: The key is valid but lacks the required scope, or the organization is not entitled. content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Missing or invalid API key. content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: No such job for this organization. content: application/json: schema: $ref: '#/components/schemas/Error' BadRequest: description: The request was malformed or exceeded a documented limit. content: application/json: schema: $ref: '#/components/schemas/Error' parameters: JobId: name: id in: path required: true description: Job id returned by createJob. schema: type: string pattern: ^cli-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ examples: - cli-3f2a1b4c-5d6e-7f80-91a2-b3c4d5e6f708 securitySchemes: latchkeyApiKey: type: http scheme: bearer description: 'A Latchkey API key, sent as `Authorization: Bearer lk_live_...`. Create one in the Latchkey dashboard. Scopes: `jobs:read` for getJob, getJobLogs and listJobs; `jobs:run` for createJob, submitJob and cancelJob.' externalDocs: description: Latchkey CLI and agent documentation url: https://latchkey.dev/documentation/latchkey-cli