Skip to content

API Reference — APT Repo Manager

Table of Contents

  1. Authentication
  2. Standard Response Codes
  3. Endpoints by Module
  4. Auth
  5. Packages
  6. Upload
  7. Import
  8. Security
  9. SBOM
  10. Downloads
  11. Dashboard
  12. Distributions
  13. Settings
  14. Health
  15. Complete Examples
  16. CI/CD Integration
  17. Rate Limiting

Authentication

JWT (interactive sessions)

Most endpoints require a JWT token passed in the Authorization HTTP header.

Obtain a token:

TOKEN=$(curl -s -X POST http://localhost:8000/auth/token \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"Admin1234!"}' | jq -r .access_token)

Use the token:

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/packages/

API Tokens (CI/CD)

For automated pipelines, use a permanent API token in the format repod_xxxxxxxxxx:

Authorization: Bearer repod_xxxxxxxxxx

API tokens are created and managed via the /auth/api-tokens endpoints (admin only).

Public endpoints (no authentication required)

The following endpoints are accessible without a token:

Endpoint Description
POST /auth/token Login / obtain JWT
POST /auth/forgot-password Request password reset email
POST /auth/reset-password Reset password with email token
GET /health Full health check
GET /health/live Liveness probe
GET /health/ready Readiness probe

Standard Response Codes

Code Meaning
200 OK Request processed successfully
201 Created Resource created successfully
204 No Content Success with no response body
400 Bad Request Invalid parameters or request body
401 Unauthorized Token missing, invalid, or expired
403 Forbidden Insufficient role for this action
404 Not Found Resource not found
409 Conflict Resource already exists (duplicate)
422 Unprocessable Entity Data validation error
429 Too Many Requests Rate limit reached
500 Internal Server Error Server-side error

Endpoints by Module

Auth (/auth)

POST /auth/token

Public. Log in and obtain a JWT.

curl -s -X POST http://localhost:8000/auth/token \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"Admin1234!"}'

Response:

{
  "access_token": "eyJhbGci...",
  "token_type": "bearer"
}


GET /auth/me

Get information about the currently authenticated user.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/auth/me

Response:

{
  "username": "admin",
  "email": "[email protected]",
  "roles": ["admin"]
}


POST /auth/change-password

Change your own password.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/auth/change-password \
  -d '{"current_password":"Admin1234!","new_password":"NewPass5678!"}'

GET /auth/roles

Public. List available roles.

curl http://localhost:8000/auth/roles

GET /auth/users

Admin. List all users.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/auth/users

POST /auth/users

Admin. Create a new user.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/auth/users \
  -d '{
    "username": "jsmith",
    "email": "[email protected]",
    "password": "SecurePass1!",
    "roles": ["uploader"]
  }'

PATCH /auth/users/{username}

Admin. Update an existing user.

curl -X PATCH -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/auth/users/jsmith \
  -d '{"roles":["uploader","auditor"]}'

DELETE /auth/users/{username}

Admin. Delete a user.

curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/auth/users/jsmith

POST /auth/users/{username}/reset-password

Admin. Reset a user's password.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/auth/users/jsmith/reset-password \
  -d '{"new_password":"TempPass9!"}'

POST /auth/forgot-password

Public, rate-limited. Request a password reset email.

curl -X POST http://localhost:8000/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'

POST /auth/reset-password

Public, rate-limited. Reset password using the token received by email.

curl -X POST http://localhost:8000/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{"token":"<reset_token>","new_password":"NewPass5678!"}'

GET /auth/api-tokens

Admin. List existing API tokens.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/auth/api-tokens

POST /auth/api-tokens

Admin. Create an API token.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/auth/api-tokens \
  -d '{"name":"gitlab-ci","roles":["uploader"]}'

Response:

{
  "id": "tok_abc123",
  "token": "repod_xxxxxxxxxx",
  "name": "gitlab-ci",
  "roles": ["uploader"]
}

The raw token repod_xxxxxxxxxx is shown only once. Save it immediately.


DELETE /auth/api-tokens/{token_id}

Admin. Revoke an API token.

curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/auth/api-tokens/tok_abc123

Packages (/packages, /artifacts)

GET /packages/

List packages with search and filters.

curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/packages/?q=nginx&distribution=jammy"

GET /artifacts/

List all artifacts.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/artifacts/

GET /artifacts/{name}

Get package information.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/artifacts/nginx

GET /artifacts/{name}/dependencies

Get package dependencies.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/artifacts/nginx/dependencies

GET /artifacts/{name}/install

Get the install command for a package.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/artifacts/nginx/install

GET /artifacts/{name}/{version}

Get information on a specific version.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/artifacts/nginx/1.24.0

GET /artifacts/audit/logs

Auditor+. Retrieve audit logs.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/artifacts/audit/logs

POST /artifacts/admin/sync-index

Maintainer+. Sync the package index.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/artifacts/admin/sync-index

Upload (/upload)

POST /upload/

Uploader+, rate-limited (20/min). Publish a .deb file.

Form field Type Description
file file The .deb file to publish
distribution string Target codename (e.g. jammy)
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -F "file=@mypackage_1.0.0_amd64.deb" \
  -F "distribution=jammy" \
  http://localhost:8000/upload/

Response (201):

{
  "name": "mypackage",
  "version": "1.0.0",
  "arch": "amd64",
  "distribution": "jammy",
  "status": "indexed"
}


Import (/import)

GET /import/search

Search for a package in the APT index.

Parameter Type Description
q string Search term
limit int Maximum number of results
source_id string Filter by APT source
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/import/search?q=nginx&limit=10"

GET /import/resolve/{package_name}

Resolve dependencies online for a package.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/import/resolve/nginx

POST /import/fetch

Uploader+, rate-limited (10/min). Import a package from the internet.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/import/fetch \
  -d '{
    "name": "nginx",
    "version": "1.24.0",
    "arch": "amd64",
    "distribution": "jammy"
  }'

POST /import/batch

Uploader+, rate-limited (5/min). Batch import packages.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/import/batch \
  -d '{
    "packages": [
      {"name":"nginx","version":"1.24.0","arch":"amd64","distribution":"jammy"},
      {"name":"curl","version":"7.81.0","arch":"amd64","distribution":"jammy"}
    ]
  }'

GET /import/sync-status

Get the sync status of all APT sources.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/import/sync-status

POST /import/sync

Maintainer+, rate-limited (3/min). Sync all APT sources.

curl -X POST -H "Authorization: Bearer $TOKEN" http://localhost:8000/import/sync

GET /import/groups

List import groups.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/import/groups

DELETE /import/groups/{group_name}

Delete an import group.

curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/import/groups/my-group

POST /import/sync/{source_id}

Maintainer+. Sync a specific APT source.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/import/sync/ubuntu-jammy

POST /import/sync-security

Maintainer+. Sync security APT sources.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/import/sync-security

GET /import/sync-schedule

Get the next scheduled sync time.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/import/sync-schedule

Security (/security)

GET /security/vulnerabilities

List CVEs across all packages.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/security/vulnerabilities

GET /security/packages-posture

Get the overall security posture summary.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/security/packages-posture

GET /security/packages/{name}/{version}/cve

Get CVEs for a specific package version.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/security/packages/nginx/1.24.0/cve

GET /security/review-queue

Auditor+. Get packages pending security review.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/security/review-queue

POST /security/packages/{name}/{version}/decide

Maintainer+. Make a CVE decision for a package.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/security/packages/nginx/1.24.0/decide \
  -d '{"decision":"accepted","comment":"Risk accepted after analysis."}'

POST /security/packages/{name}/{version}/rescan

Maintainer+. Trigger a new vulnerability scan.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/security/packages/nginx/1.24.0/rescan

POST /security/packages/{name}/{version}/quarantine

Maintainer+. Quarantine a package.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/security/packages/nginx/1.24.0/quarantine

GET /security/clamav/status

Get ClamAV antivirus engine status.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/security/clamav/status

POST /security/clamav/update

Maintainer+. Update the ClamAV signature database.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/security/clamav/update

SBOM (/sbom)

GET /sbom/export

Export the full SBOM.

Parameter Values Description
format cyclonedx, spdx Output format
distribution codename Filter by distribution
# CycloneDX
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/sbom/export?format=cyclonedx&distribution=jammy" \
  -o sbom.cdx.json

# SPDX
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/sbom/export?format=spdx&distribution=jammy" \
  -o sbom.spdx.json

GET /sbom/{name}/{version}

Get the SBOM for a specific package.

Parameter Values Description
format cyclonedx, spdx Output format
arch e.g. amd64 Architecture
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/sbom/nginx/1.24.0?format=cyclonedx&arch=amd64" \
  -o nginx-sbom.cdx.json

GET /sbom/preview

Preview the SBOM (limited results).

curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/sbom/preview?format=cyclonedx&distribution=jammy&limit=5"

Downloads (/downloads)

GET /downloads/stats

Get download statistics.

Parameter Values Description
days 7, 30, 90 Time period
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/downloads/stats?days=30"

Dashboard (/dashboard)

GET /dashboard/stats

Get global repository statistics.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/dashboard/stats

GET /dashboard/history

Get event history data.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/dashboard/history

Distributions (/distributions)

GET /distributions/

List available distributions.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/distributions/

GET /distributions/{codename}/packages

List packages present in a distribution.

curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/distributions/jammy/packages

POST /distributions/promote

Promote a package to another distribution.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/distributions/promote \
  -d '{
    "name": "nginx",
    "version": "1.24.0",
    "arch": "amd64",
    "from_distribution": "jammy",
    "to_distribution": "focal"
  }'

POST /distributions/migrate

Migrate packages between distributions.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/distributions/migrate \
  -d '{
    "from_distribution": "focal",
    "to_distribution": "jammy",
    "packages": ["nginx","curl"]
  }'

POST /distributions/init

Initialize a new distribution.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/distributions/init \
  -d '{"codename":"noble","description":"Ubuntu 24.04 LTS"}'

Settings (/settings)

All /settings endpoints are restricted to admins.

GET /settings/

Read the full configuration (passwords masked).

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/settings/

PATCH /settings/

Update configuration settings.

curl -X PATCH -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/settings/ \
  -d '{"retention_days": 90}'

POST /settings/test-webhook

Test the configured webhook.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/settings/test-webhook

GET /settings/gpg

Get the repository GPG key information.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/settings/gpg

POST /settings/gpg/generate

Generate a new GPG key.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/settings/gpg/generate

GET /settings/next-sync

Get the next scheduled sync time.

curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/settings/next-sync

POST /settings/test-ldap

Test the LDAP connection.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/settings/test-ldap

POST /settings/run-retention

Manually trigger the retention policy.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/settings/run-retention

POST /settings/test-email

Send a test email.

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/settings/test-email

Health (/health)

GET /health

Public. Full health check with status of all subsystems.

curl http://localhost:8000/health

Response:

{
  "status": "healthy",
  "subsystems": {
    "database": "ok",
    "storage": "ok",
    "clamav": "ok"
  }
}


GET /health/live

Public. Liveness probe — always returns 200 if the service is running.

curl http://localhost:8000/health/live

GET /health/ready

Public. Readiness probe — indicates whether the service is ready to accept requests.

curl http://localhost:8000/health/ready

Complete Examples

Create a user and assign a role

# 1. Obtain an admin token
TOKEN=$(curl -s -X POST http://localhost:8000/auth/token \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"Admin1234!"}' | jq -r .access_token)

# 2. Create the user
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/auth/users \
  -d '{
    "username": "deployer",
    "email": "[email protected]",
    "password": "Deploy9876!",
    "roles": ["uploader"]
  }'

# 3. Create an API token for CI pipelines
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/auth/api-tokens \
  -d '{"name":"pipeline-deployer","roles":["uploader"]}' | jq -r .token

Upload and verify a package

# Upload
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -F "file=@myapp_2.0.0_amd64.deb" \
  -F "distribution=jammy" \
  http://localhost:8000/upload/

# Verify the package is indexed
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/artifacts/myapp/2.0.0

# Check for CVEs
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/security/packages/myapp/2.0.0/cve

Import a package from upstream repositories

# Search upstream
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/import/search?q=curl&limit=5" | jq

# Resolve dependencies
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/import/resolve/curl | jq

# Import
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/import/fetch \
  -d '{"name":"curl","version":"7.81.0","arch":"amd64","distribution":"jammy"}'

Export the full SBOM

# CycloneDX
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/sbom/export?format=cyclonedx&distribution=jammy" \
  -o sbom-jammy.cdx.json

# SPDX
curl -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/sbom/export?format=spdx&distribution=jammy" \
  -o sbom-jammy.spdx.json

echo "SBOM exported successfully."

CI/CD Integration

GitHub Actions — Publish a package

name: Publish package

on:
  push:
    tags:
      - 'v*'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build .deb
        run: make deb

      - name: Upload to repod
        env:
          REPOD_TOKEN: ${{ secrets.REPOD_TOKEN }}
          REPOD_URL: https://repo.example.com
        run: |
          curl -X POST "$REPOD_URL/upload/" \
            -H "Authorization: Bearer $REPOD_TOKEN" \
            -F "file=@mypackage_1.0.0_amd64.deb" \
            -F "distribution=jammy"

GitLab CI — Publish a package

stages:
  - build
  - deploy

build:
  stage: build
  script:
    - make deb

deploy:
  stage: deploy
  script:
    - |
      curl -X POST "$REPOD_URL/upload/" \
        -H "Authorization: Bearer $REPOD_TOKEN" \
        -F "file=@mypackage_1.0.0_amd64.deb" \
        -F "distribution=jammy"
  only:
    - tags

Add REPOD_TOKEN and REPOD_URL to your GitLab project's CI/CD variables.


Automated import from upstream

#!/usr/bin/env bash
set -euo pipefail

REPOD_URL="https://repo.example.com"
REPOD_TOKEN="${REPOD_TOKEN}"
DISTRIBUTION="jammy"

PACKAGES=("nginx" "curl" "openssl")

for pkg in "${PACKAGES[@]}"; do
  echo "Importing $pkg..."
  curl -s -X POST -H "Authorization: Bearer $REPOD_TOKEN" \
    -H "Content-Type: application/json" \
    "$REPOD_URL/import/fetch" \
    -d "{\"name\":\"$pkg\",\"arch\":\"amd64\",\"distribution\":\"$DISTRIBUTION\"}"
done

Rate Limiting

Certain endpoints are protected by rate limits to prevent abuse.

Endpoint Limit
POST /upload/ 20 requests / minute
POST /import/fetch 10 requests / minute
POST /import/batch 5 requests / minute
POST /import/sync 3 requests / minute
POST /auth/forgot-password Rate-limited (undisclosed)
POST /auth/reset-password Rate-limited (undisclosed)

When the limit is exceeded, the server responds with 429 Too Many Requests. Wait for the duration indicated in the Retry-After response header before retrying.

Role levels

Roles are cumulative — a higher role includes all permissions of lower roles:

Role Permissions
viewer Read-only access
uploader Viewer + publish and import packages
auditor Uploader + access to audit logs and security review queue
maintainer Auditor + sync sources, quarantine packages, make CVE decisions
admin Full access, user management, and configuration