Skip to content

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 ​

bash
export ORBITAL_HOST="https://orbitalreg.example.com"
export ORBITAL_TOKEN="orbsa_…"     # service-account or PAT

The 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.

bash
curl "$ORBITAL_HOST/api/public-config" | jq .
python
import os, requests

r = requests.get(f"{os.environ['ORBITAL_HOST']}/api/public-config",
                 timeout=10)
r.raise_for_status()
print(r.json())
go
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"])
javascript
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.

bash
curl -fsS "$ORBITAL_HOST/api/auth/whoami" \
  -H "Authorization: Bearer $ORBITAL_TOKEN" | jq .
python
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"])
go
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)
}
javascript
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=….

bash
curl -fsS "$ORBITAL_HOST/api/v1/projects?limit=100" \
  -H "Authorization: Bearer $ORBITAL_TOKEN" | jq '.[] | {id, slug, name}'
python
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"])
go
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)
}
javascript
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.

bash
curl -fsS "$ORBITAL_HOST/api/v1/projects?slug=platform" \
  -H "Authorization: Bearer $ORBITAL_TOKEN" | jq '.[0].id'
python
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"]
go
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].ID
javascript
const r = await fetch(
  `${host}/api/v1/projects?slug=${encodeURIComponent('platform')}`,
  { headers: { Authorization: `Bearer ${token}` } },
)
const [project] = await r.json()
const projectId = project.id

5. List repositories in a project ​

bash
curl -fsS "$ORBITAL_HOST/api/v1/repos?project_id=$PROJECT_ID" \
  -H "Authorization: Bearer $ORBITAL_TOKEN" | jq '.[] | {slug, format, kind}'
python
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"])
go
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()
javascript
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.

bash
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 .
python
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"])
go
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)
javascript
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.

bash
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.jar
python
with 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"])
go
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)
}
javascript
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.

bash
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"
python
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)
go
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)
javascript
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.

bash
curl -fsS "$ORBITAL_HOST/api/v1/search?q=log4j&limit=20" \
  -H "Authorization: Bearer $ORBITAL_TOKEN" | jq '.[] | {kind, path, repo}'
python
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"))
go
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()
javascript
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.

bash
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}'
python
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")
go
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()
javascript
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.

bash
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"
python
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()
go
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)
}
javascript
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?

bash
curl -fsS \
  "$ORBITAL_HOST/api/v1/security/who-pulled?artifact_id=$ARTIFACT_ID&since_days=30" \
  -H "Authorization: Bearer $ORBITAL_TOKEN" | jq .
python
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"])
go
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()
javascript
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.

bash
curl -fsS -X PUT "$ORBITAL_HOST/api/build/" \
  -H "Authorization: Bearer $ORBITAL_TOKEN" \
  -H "Content-Type: application/json" \
  -d @./build-info.json
python
with 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()
go
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()
javascript
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.

python
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.

javascript
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 orbital CLI already wraps every operation in this page.

OrbitalReg ships under the OrbitalReg EULA with a Free Forever tier.