API code samples
Working examples for the operations that show up in 90% of CI integrations. Each block is paired across curl, Python (requests), Go (net/http), and JavaScript (fetch) so you can pick the language your pipeline already speaks and stay there.
The full per-endpoint schema lives in the API explorer; this page is the cookbook on top.
Conventions used in every sample
export ORBITAL_HOST="https://orbitalreg.example.com"
export ORBITAL_TOKEN="orbsa_…" # service-account or PATThe Python snippets assume requests (pip install requests); the Go snippets assume the standard library only; the JavaScript snippets target Node 18+ or any modern browser (both ship fetch natively).
All endpoints return JSON unless explicitly noted. Errors share a single envelope; the snippets below check r.ok / resp.StatusCode rather than wrapping each call in framework-specific exception machinery.
1. Read the public config
The unauthenticated probe used by the Login page and any health script that just wants to confirm the deployment is reachable.
curl "$ORBITAL_HOST/api/public-config" | jq .import os, requests
r = requests.get(f"{os.environ['ORBITAL_HOST']}/api/public-config",
timeout=10)
r.raise_for_status()
print(r.json())resp, err := http.Get(host + "/api/public-config")
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
var cfg map[string]any
json.NewDecoder(resp.Body).Decode(&cfg)
fmt.Println(cfg["version"], cfg["help_url"])const r = await fetch(`${process.env.ORBITAL_HOST}/api/public-config`)
if (!r.ok) throw new Error(`HTTP ${r.status}`)
const cfg = await r.json()
console.log(cfg.version, cfg.help_url)2. Verify the token (whoami)
Every CI script should run this first. A 200 with a non-null email means the token is alive and not yet revoked; a 401 means rotate.
curl -fsS "$ORBITAL_HOST/api/auth/whoami" \
-H "Authorization: Bearer $ORBITAL_TOKEN" | jq .r = requests.get(
f"{os.environ['ORBITAL_HOST']}/api/auth/whoami",
headers={"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"},
timeout=10,
)
r.raise_for_status()
print(r.json()["email"])req, _ := http.NewRequest("GET", host+"/api/auth/whoami", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
if resp.StatusCode != 200 {
log.Fatalf("whoami failed: %s", resp.Status)
}const r = await fetch(`${host}/api/auth/whoami`, {
headers: { Authorization: `Bearer ${token}` },
})
if (!r.ok) throw new Error(`whoami HTTP ${r.status}`)
const me = await r.json()
console.log(me.email)3. List projects
Returns every project the calling principal can see. Cursor pagination is supported via ?cursor=…&limit=….
curl -fsS "$ORBITAL_HOST/api/v1/projects?limit=100" \
-H "Authorization: Bearer $ORBITAL_TOKEN" | jq '.[] | {id, slug, name}'r = requests.get(
f"{os.environ['ORBITAL_HOST']}/api/v1/projects",
headers={"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"},
params={"limit": 100},
timeout=10,
)
r.raise_for_status()
for p in r.json():
print(p["slug"], p["id"])req, _ := http.NewRequest("GET", host+"/api/v1/projects?limit=100", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var projects []struct {
ID, Slug, Name string
}
json.NewDecoder(resp.Body).Decode(&projects)
for _, p := range projects {
fmt.Printf("%s\t%s\n", p.Slug, p.ID)
}const r = await fetch(`${host}/api/v1/projects?limit=100`, {
headers: { Authorization: `Bearer ${token}` },
})
const projects = await r.json()
for (const p of projects) console.log(p.slug, p.id)4. Look up a project by slug
The same endpoint but with the ?slug= filter — saves a round-trip when your CI knows the slug but not the UUID.
curl -fsS "$ORBITAL_HOST/api/v1/projects?slug=platform" \
-H "Authorization: Bearer $ORBITAL_TOKEN" | jq '.[0].id'r = requests.get(
f"{os.environ['ORBITAL_HOST']}/api/v1/projects",
headers={"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"},
params={"slug": "platform"},
)
r.raise_for_status()
project_id = r.json()[0]["id"]u, _ := url.Parse(host + "/api/v1/projects")
q := u.Query(); q.Set("slug", "platform"); u.RawQuery = q.Encode()
req, _ := http.NewRequest("GET", u.String(), nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var rows []struct{ ID string }
json.NewDecoder(resp.Body).Decode(&rows)
projectID := rows[0].IDconst r = await fetch(
`${host}/api/v1/projects?slug=${encodeURIComponent('platform')}`,
{ headers: { Authorization: `Bearer ${token}` } },
)
const [project] = await r.json()
const projectId = project.id5. List repositories in a project
curl -fsS "$ORBITAL_HOST/api/v1/repos?project_id=$PROJECT_ID" \
-H "Authorization: Bearer $ORBITAL_TOKEN" | jq '.[] | {slug, format, kind}'r = requests.get(
f"{os.environ['ORBITAL_HOST']}/api/v1/repos",
headers={"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"},
params={"project_id": project_id},
)
for repo in r.json():
print(repo["slug"], repo["format"], repo["kind"])u, _ := url.Parse(host + "/api/v1/repos")
q := u.Query(); q.Set("project_id", projectID); u.RawQuery = q.Encode()
req, _ := http.NewRequest("GET", u.String(), nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()const r = await fetch(
`${host}/api/v1/repos?project_id=${projectId}`,
{ headers: { Authorization: `Bearer ${token}` } },
)
const repos = await r.json()6. Issue a personal access token
Bundle-driven scope picker — pull for read-only CI, publish for upload-from-CI, admin for the rare case you need governance writes. The plaintext token is returned once; copy it on the spot.
curl -fsS -X POST "$ORBITAL_HOST/api/v1/tokens" \
-H "Authorization: Bearer $ORBITAL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-publish-maven",
"bundle": "publish",
"format_filter": ["maven"],
"expires_in_days": 90
}' | jq .payload = {
"name": "ci-publish-maven",
"bundle": "publish",
"format_filter": ["maven"],
"expires_in_days": 90,
}
r = requests.post(
f"{os.environ['ORBITAL_HOST']}/api/v1/tokens",
headers={"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"},
json=payload,
)
r.raise_for_status()
result = r.json()
print("plaintext (save now):", result["plaintext"])body, _ := json.Marshal(map[string]any{
"name": "ci-publish-maven",
"bundle": "publish",
"format_filter": []string{"maven"},
"expires_in_days": 90,
})
req, _ := http.NewRequest("POST", host+"/api/v1/tokens", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var out struct {
ID, Plaintext string
}
json.NewDecoder(resp.Body).Decode(&out)
fmt.Println("plaintext (save now):", out.Plaintext)const r = await fetch(`${host}/api/v1/tokens`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'ci-publish-maven',
bundle: 'publish',
format_filter: ['maven'],
expires_in_days: 90,
}),
})
const { id, plaintext } = await r.json()
console.log('plaintext (save now):', plaintext)7. Push an artifact
The generic upload endpoint that backs the orbital push CLI verb. Body is the raw file bytes; path is the address inside the repo.
curl -fsS -X PUT \
"$ORBITAL_HOST/api/v1/artifacts/$REPO_ID/com/example/sample/1.0.0/sample-1.0.0.jar" \
-H "Authorization: Bearer $ORBITAL_TOKEN" \
-H "Content-Type: application/java-archive" \
--data-binary @./sample-1.0.0.jarwith open("sample-1.0.0.jar", "rb") as fh:
r = requests.put(
f"{os.environ['ORBITAL_HOST']}/api/v1/artifacts/"
f"{repo_id}/com/example/sample/1.0.0/sample-1.0.0.jar",
headers={
"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}",
"Content-Type": "application/java-archive",
},
data=fh,
)
r.raise_for_status()
print("artifact id:", r.json()["id"])fh, err := os.Open("sample-1.0.0.jar")
if err != nil { log.Fatal(err) }
defer fh.Close()
url := fmt.Sprintf("%s/api/v1/artifacts/%s/com/example/sample/1.0.0/sample-1.0.0.jar",
host, repoID)
req, _ := http.NewRequest("PUT", url, fh)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/java-archive")
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
if resp.StatusCode/100 != 2 {
log.Fatalf("push failed: %s", resp.Status)
}import { readFile } from 'node:fs/promises'
const body = await readFile('sample-1.0.0.jar')
const r = await fetch(
`${host}/api/v1/artifacts/${repoId}/com/example/sample/1.0.0/sample-1.0.0.jar`,
{
method: 'PUT',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/java-archive',
},
body,
},
)
if (!r.ok) throw new Error(`push HTTP ${r.status}`)8. Pull an artifact
Same shape, the GET verb. The server may answer a 302 to a presigned S3 URL when the deployment's S3 endpoint is browser-reachable; the snippets below all follow redirects by default.
A 403 here carries the why-blocked envelope — print X-Orbital-Block-Reason from the response headers when surfacing the failure to a developer.
curl -fsSL -o sample-1.0.0.jar \
"$ORBITAL_HOST/api/v1/artifacts/$REPO_ID/com/example/sample/1.0.0/sample-1.0.0.jar" \
-H "Authorization: Bearer $ORBITAL_TOKEN"url = (f"{os.environ['ORBITAL_HOST']}/api/v1/artifacts/{repo_id}/"
"com/example/sample/1.0.0/sample-1.0.0.jar")
with requests.get(
url,
headers={"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"},
stream=True,
) as r:
if r.status_code == 403:
print("blocked:", r.headers.get("X-Orbital-Block-Reason"))
return
r.raise_for_status()
with open("sample-1.0.0.jar", "wb") as out:
for chunk in r.iter_content(64 * 1024):
out.write(chunk)url := fmt.Sprintf("%s/api/v1/artifacts/%s/com/example/sample/1.0.0/sample-1.0.0.jar",
host, repoID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()
if resp.StatusCode == 403 {
log.Fatalf("blocked: %s", resp.Header.Get("X-Orbital-Block-Reason"))
}
out, _ := os.Create("sample-1.0.0.jar")
defer out.Close()
io.Copy(out, resp.Body)import { writeFile } from 'node:fs/promises'
const r = await fetch(
`${host}/api/v1/artifacts/${repoId}/com/example/sample/1.0.0/sample-1.0.0.jar`,
{ headers: { Authorization: `Bearer ${token}` } },
)
if (r.status === 403) {
throw new Error(`blocked: ${r.headers.get('x-orbital-block-reason')}`)
}
if (!r.ok) throw new Error(`pull HTTP ${r.status}`)
await writeFile('sample-1.0.0.jar', Buffer.from(await r.arrayBuffer()))9. Search for an artifact
Trigram-indexed search across artifact paths and repo names — same backend the topbar search field uses.
curl -fsS "$ORBITAL_HOST/api/v1/search?q=log4j&limit=20" \
-H "Authorization: Bearer $ORBITAL_TOKEN" | jq '.[] | {kind, path, repo}'r = requests.get(
f"{os.environ['ORBITAL_HOST']}/api/v1/search",
headers={"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"},
params={"q": "log4j", "limit": 20},
)
for hit in r.json():
print(hit["kind"], hit.get("path") or hit.get("repo"))u, _ := url.Parse(host + "/api/v1/search")
q := u.Query(); q.Set("q", "log4j"); q.Set("limit", "20"); u.RawQuery = q.Encode()
req, _ := http.NewRequest("GET", u.String(), nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()const r = await fetch(
`${host}/api/v1/search?q=${encodeURIComponent('log4j')}&limit=20`,
{ headers: { Authorization: `Bearer ${token}` } },
)
const hits = await r.json()10. List scan findings for an artifact
Used by promotion gates and SOC dashboards. Filter by minimum severity to skip noise.
curl -fsS \
"$ORBITAL_HOST/api/v1/detection/findings?artifact_id=$ARTIFACT_ID&severity=high&status=open" \
-H "Authorization: Bearer $ORBITAL_TOKEN" | jq '.[] | {cve, severity, scanner}'r = requests.get(
f"{os.environ['ORBITAL_HOST']}/api/v1/detection/findings",
headers={"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"},
params={"artifact_id": artifact_id,
"severity": "high", "status": "open"},
)
findings = r.json()
if findings:
print(f"{len(findings)} high+ findings; not promoting")u, _ := url.Parse(host + "/api/v1/detection/findings")
q := u.Query()
q.Set("artifact_id", artifactID)
q.Set("severity", "high")
q.Set("status", "open")
u.RawQuery = q.Encode()
req, _ := http.NewRequest("GET", u.String(), nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()const params = new URLSearchParams({
artifact_id: artifactId,
severity: 'high',
status: 'open',
})
const r = await fetch(`${host}/api/v1/detection/findings?${params}`, {
headers: { Authorization: `Bearer ${token}` },
})
const findings = await r.json()11. Create and execute a promotion
Two-step pattern: create the row (pending if require_approval is true), then execute it. The execute call runs the CVE / license / signing gates and returns 422 with a structured reason if any gate blocks.
PROMOTION_ID=$(curl -fsS -X POST "$ORBITAL_HOST/api/v1/promotions" \
-H "Authorization: Bearer $ORBITAL_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"src_repo_id\": \"$STAGING_REPO_ID\",
\"dst_repo_id\": \"$RELEASE_REPO_ID\",
\"artifact_id\": \"$ARTIFACT_ID\",
\"note\": \"promoted by ci\"
}" | jq -r .id)
curl -fsS -X POST "$ORBITAL_HOST/api/v1/promotions/$PROMOTION_ID/execute" \
-H "Authorization: Bearer $ORBITAL_TOKEN"hdr = {"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"}
host = os.environ["ORBITAL_HOST"]
create = requests.post(
f"{host}/api/v1/promotions",
headers=hdr,
json={
"src_repo_id": staging_repo_id,
"dst_repo_id": release_repo_id,
"artifact_id": artifact_id,
"note": "promoted by ci",
},
)
create.raise_for_status()
pid = create.json()["id"]
exec_resp = requests.post(
f"{host}/api/v1/promotions/{pid}/execute", headers=hdr,
)
if exec_resp.status_code == 422:
raise SystemExit(f"gate blocked: {exec_resp.json()['error']['message']}")
exec_resp.raise_for_status()type promo struct {
SrcRepoID, DstRepoID, ArtifactID, Note string
ID string `json:"id,omitempty"`
}
body, _ := json.Marshal(promo{
SrcRepoID: stagingRepoID, DstRepoID: releaseRepoID,
ArtifactID: artifactID, Note: "promoted by ci",
})
req, _ := http.NewRequest("POST", host+"/api/v1/promotions",
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var created promo
json.NewDecoder(resp.Body).Decode(&created)
req2, _ := http.NewRequest("POST",
host+"/api/v1/promotions/"+created.ID+"/execute", nil)
req2.Header.Set("Authorization", "Bearer "+token)
exec, _ := http.DefaultClient.Do(req2)
defer exec.Body.Close()
if exec.StatusCode == 422 {
log.Fatalf("gate blocked: %s", exec.Status)
}const hdr = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
}
const create = await fetch(`${host}/api/v1/promotions`, {
method: 'POST',
headers: hdr,
body: JSON.stringify({
src_repo_id: stagingRepoId,
dst_repo_id: releaseRepoId,
artifact_id: artifactId,
note: 'promoted by ci',
}),
})
const { id } = await create.json()
const exec = await fetch(`${host}/api/v1/promotions/${id}/execute`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
})
if (exec.status === 422) {
const { error } = await exec.json()
throw new Error(`gate blocked: ${error.message}`)
}12. Reverse-dep audit (who pulled what)
The SOC's first stop after a CVE drops: which developers / build hosts pulled this artifact in the last N days?
curl -fsS \
"$ORBITAL_HOST/api/v1/security/who-pulled?artifact_id=$ARTIFACT_ID&since_days=30" \
-H "Authorization: Bearer $ORBITAL_TOKEN" | jq .r = requests.get(
f"{os.environ['ORBITAL_HOST']}/api/v1/security/who-pulled",
headers={"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"},
params={"artifact_id": artifact_id, "since_days": 30},
)
for row in r.json():
print(row["pulled_at"], row["principal"], row["client_ip"])u, _ := url.Parse(host + "/api/v1/security/who-pulled")
q := u.Query()
q.Set("artifact_id", artifactID)
q.Set("since_days", "30")
u.RawQuery = q.Encode()
req, _ := http.NewRequest("GET", u.String(), nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()const params = new URLSearchParams({
artifact_id: artifactId,
since_days: '30',
})
const r = await fetch(
`${host}/api/v1/security/who-pulled?${params}`,
{ headers: { Authorization: `Bearer ${token}` } },
)
const audits = await r.json()13. Submit build info
Standard build-info envelope used by orbital build-info publish and any CI lane that already emits the same shape.
curl -fsS -X PUT "$ORBITAL_HOST/api/build/" \
-H "Authorization: Bearer $ORBITAL_TOKEN" \
-H "Content-Type: application/json" \
-d @./build-info.jsonwith open("build-info.json", "rb") as fh:
r = requests.put(
f"{os.environ['ORBITAL_HOST']}/api/build/",
headers={
"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}",
"Content-Type": "application/json",
},
data=fh,
)
r.raise_for_status()fh, err := os.Open("build-info.json")
if err != nil { log.Fatal(err) }
defer fh.Close()
req, _ := http.NewRequest("PUT", host+"/api/build/", fh)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer resp.Body.Close()import { readFile } from 'node:fs/promises'
const body = await readFile('build-info.json')
const r = await fetch(`${host}/api/build/`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body,
})
if (!r.ok) throw new Error(`build-info HTTP ${r.status}`)Recipes that bundle several calls
Promote-on-clean-scan
The full promotion gate that most release pipelines want: pull the findings, decide, then promote.
import os, sys, requests
host = os.environ["ORBITAL_HOST"]
hdr = {"Authorization": f"Bearer {os.environ['ORBITAL_TOKEN']}"}
artifact = sys.argv[1]
src, dst = sys.argv[2], sys.argv[3]
bad = requests.get(
f"{host}/api/v1/detection/findings",
headers=hdr,
params={"artifact_id": artifact, "severity": "high", "status": "open"},
).json()
if bad:
sys.exit(f"refusing to promote — {len(bad)} unresolved high+ findings")
p = requests.post(
f"{host}/api/v1/promotions", headers=hdr,
json={"src_repo_id": src, "dst_repo_id": dst, "artifact_id": artifact},
).json()
ex = requests.post(
f"{host}/api/v1/promotions/{p['id']}/execute", headers=hdr,
)
if ex.status_code == 422:
sys.exit(f"gate blocked: {ex.json()['error']['message']}")
ex.raise_for_status()
print("promoted", p["id"])Block-on-CVE notifier
Watch the findings stream during a release, abort on first critical CVE.
const host = process.env.ORBITAL_HOST
const token = process.env.ORBITAL_TOKEN
const params = new URLSearchParams({
severity: 'critical', status: 'open',
})
const r = await fetch(`${host}/api/v1/detection/findings?${params}`, {
headers: { Authorization: `Bearer ${token}` },
})
const open = await r.json()
if (open.length > 0) {
console.error(`abort release: ${open.length} critical findings open`)
process.exit(1)
}Where to go next
- The Getting started with the API guide walks one full flow end-to-end (sign in, mint a token, push, pull, promote) instead of cataloguing operations.
- The API explorer renders every operation, including the ones not covered here, with full request/response schemas.
- For shell-friendly versions of all of the above, the
orbitalCLI already wraps every operation in this page.