Submit a lockfile analysis job
Submit uploaded manifests and lockfiles for asynchronous dependency analysis. Authenticate with a GitHits Bearer credential for an active account that has accepted the required terms. Supply kind=sbom either as a query parameter or multipart field; when both are supplied, the multipart field wins. Supply format in either location or both; the multipart field wins, and omission selects cyclonedx. Repeated values within one location and unknown query or multipart fields are rejected. The gateway reports a provisional cost of one unit for submission; backend job admission currently charges 100 daily units. This endpoint returns HTTP 202 when the job is queued. The existing /v1/experimental/sbom endpoint remains synchronous and returns an HTTP 200 document.
curl --request POST \
--url https://api.githits.dev/v1/experimental/lockfile-jobs \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form 'files[]=@example-file;type=application/octet-stream' \
--form 'format=cyclonedx;type=text/plain' \
--form 'kind=sbom;type=text/plain'import requests
url = "https://api.githits.dev/v1/experimental/lockfile-jobs"
files = { "files[]": ("example-file", open("example-file", "rb"), "application/octet-stream") }
files["format"] = (None, "cyclonedx", "text/plain")
files["kind"] = (None, "sbom", "text/plain")
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('files[]', '<string>');
form.append('format', 'cyclonedx');
form.append('kind', 'sbom');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.githits.dev/v1/experimental/lockfile-jobs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.githits.dev/v1/experimental/lockfile-jobs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"format\"\r\nContent-Type: text/plain\r\n\r\ncyclonedx\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"kind\"\r\nContent-Type: text/plain\r\n\r\nsbom\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.githits.dev/v1/experimental/lockfile-jobs"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"format\"\r\nContent-Type: text/plain\r\n\r\ncyclonedx\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"kind\"\r\nContent-Type: text/plain\r\n\r\nsbom\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.githits.dev/v1/experimental/lockfile-jobs")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"format\"\r\nContent-Type: text/plain\r\n\r\ncyclonedx\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"kind\"\r\nContent-Type: text/plain\r\n\r\nsbom\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.githits.dev/v1/experimental/lockfile-jobs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"format\"\r\nContent-Type: text/plain\r\n\r\ncyclonedx\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"kind\"\r\nContent-Type: text/plain\r\n\r\nsbom\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"events_url": "/v1/experimental/lockfile-jobs/01900000-0000-7000-8000-000000000001/events",
"expires_at": "2026-10-10T12:00:00Z",
"id": "01900000-0000-7000-8000-000000000001",
"result_url": "/v1/experimental/lockfile-jobs/01900000-0000-7000-8000-000000000001/result",
"state": "queued",
"status_url": "/v1/experimental/lockfile-jobs/01900000-0000-7000-8000-000000000001"
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
Optional client attribution: trimmed printable ASCII, at most 80 bytes. Invalid optional values are dropped.
Optional client-version attribution: trimmed printable ASCII, at most 80 bytes. Invalid optional values are dropped.
Optional agent attribution: trimmed printable ASCII, at most 160 bytes. Invalid optional values are dropped.
Optional caller-defined session ID: one to 64 ASCII letters, digits, underscores or hyphens, preserved exactly. Supply the header at most once. Invalid supplied IDs return 400 INVALID_SESSION_ID; no session is created.
1 - 64^[A-Za-z0-9_-]{1,64}$Query Parameters
Job kind. Provide sbom in this query parameter or the multipart body; at least one location is required. If both locations provide it, the body wins. Repeated values in this location are rejected.
sbom Requested result format. A multipart format field overrides this value. Omit both locations to select cyclonedx. Repeated values in one location are rejected; unknown query fields are rejected. Accept does not select the result format. cyclonedx returns CycloneDX 1.7 JSON (application/vnd.cyclonedx+json); spdx returns SPDX 2.3 tag-value (text/spdx); text returns a human-readable report (text/plain). All outputs use UTF-8. Accept does not select the successful output format.
cyclonedx, spdx, text Body
Send multipart/form-data. The complete request, including multipart headers and boundaries, must fit within 8,000,000 bytes. Supply kind=sbom either as a query parameter or the multipart kind field; if both locations supply it, the body field wins. The selected format also accepts query or body values, with the body winning when both are present. Repeated values within one location and unknown fields are rejected.
Repeat the literal field name files[] once per file, with raw UTF-8 file contents rather than base64. Set each part's filename to its path relative to a common repository root, for example app/package.json and app/package-lock.json. Preserve directory structure so manifests can be associated with their lockfiles. Send 1–256 files, each at most 5,242,880 bytes. Each filename must be at most 1,024 UTF-8 bytes and must be relative, with no empty, dot (.) or parent (..) path segments, backslashes or control characters. Upload manifests and/or lockfiles supported by the GitHits inventory service; their contents are analyzed asynchronously.
1 - 256 elementsOptional result format. This multipart value overrides the query parameter. Omit both to receive CycloneDX JSON; duplicate values in one location are rejected.
cyclonedx, spdx, text Optional multipart job kind. At least one sbom value is required across this field and the query parameter; when both are present, this multipart value wins. Duplicate values in this location are rejected.
sbom Response
The upload was admitted and queued for asynchronous analysis.
Canonical relative URL for the job's server-sent event stream.
^/v1/experimental/lockfile-jobs/[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}/events$"/v1/experimental/lockfile-jobs/01900000-0000-7000-8000-000000000001/events"
Time after which the job and its result are no longer available.
"2026-10-10T12:00:00Z"
Opaque identifier for this analysis job.
^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$"01900000-0000-7000-8000-000000000001"
Canonical relative URL for retrieving the completed analysis document.
^/v1/experimental/lockfile-jobs/[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}/result$"/v1/experimental/lockfile-jobs/01900000-0000-7000-8000-000000000001/result"
Initial state when the accepted job is placed in the queue.
queued "queued"
Canonical relative URL for the job status snapshot.
^/v1/experimental/lockfile-jobs/[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$"/v1/experimental/lockfile-jobs/01900000-0000-7000-8000-000000000001"
curl --request POST \
--url https://api.githits.dev/v1/experimental/lockfile-jobs \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form 'files[]=@example-file;type=application/octet-stream' \
--form 'format=cyclonedx;type=text/plain' \
--form 'kind=sbom;type=text/plain'import requests
url = "https://api.githits.dev/v1/experimental/lockfile-jobs"
files = { "files[]": ("example-file", open("example-file", "rb"), "application/octet-stream") }
files["format"] = (None, "cyclonedx", "text/plain")
files["kind"] = (None, "sbom", "text/plain")
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('files[]', '<string>');
form.append('format', 'cyclonedx');
form.append('kind', 'sbom');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.githits.dev/v1/experimental/lockfile-jobs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.githits.dev/v1/experimental/lockfile-jobs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"format\"\r\nContent-Type: text/plain\r\n\r\ncyclonedx\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"kind\"\r\nContent-Type: text/plain\r\n\r\nsbom\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.githits.dev/v1/experimental/lockfile-jobs"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"format\"\r\nContent-Type: text/plain\r\n\r\ncyclonedx\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"kind\"\r\nContent-Type: text/plain\r\n\r\nsbom\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.githits.dev/v1/experimental/lockfile-jobs")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"format\"\r\nContent-Type: text/plain\r\n\r\ncyclonedx\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"kind\"\r\nContent-Type: text/plain\r\n\r\nsbom\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.githits.dev/v1/experimental/lockfile-jobs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"format\"\r\nContent-Type: text/plain\r\n\r\ncyclonedx\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"kind\"\r\nContent-Type: text/plain\r\n\r\nsbom\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"events_url": "/v1/experimental/lockfile-jobs/01900000-0000-7000-8000-000000000001/events",
"expires_at": "2026-10-10T12:00:00Z",
"id": "01900000-0000-7000-8000-000000000001",
"result_url": "/v1/experimental/lockfile-jobs/01900000-0000-7000-8000-000000000001/result",
"state": "queued",
"status_url": "/v1/experimental/lockfile-jobs/01900000-0000-7000-8000-000000000001"
}