Skip to main content

Python Package Repositories API

Python Package Repositories provide private PyPI-compatible package indexes inside Qarion spaces. Use these APIs to manage space-wide or project-scoped repositories, upload and yank distribution files, expose pip and twine client endpoints, import exact releases from public PyPI, and configure vulnerability scanning.

info

All endpoints are mounted under /api/v1.0. Paths below omit that prefix for readability.

The API requires the packages.pypi_registry feature key.

Authentication

Management endpoints use normal Qarion API authentication.

PyPI-compatible client endpoints support Basic auth for pip and twine:

  • Username: __token__
  • Password: a Qarion API key, or a package-fetch validation token where that flow is being used.

Package-client endpoints intentionally return 404 Not Found for missing, blocked, or inaccessible repositories and files so private package names and scope details are not leaked.

Scope And Access

Repositories can be either space-wide or project-scoped.

ScopeBehavior
Space-wide repositoryUses python_package_repository permissions in the space.
Project-scoped repositoryInherits the project scope access model or direct project grants.

Project scope access levels are view, publish, edit, and admin. Space-member defaults can be role_based, none, view, publish, or edit. Direct grants can target users or teams.

Endpoint Overview

MethodEndpointDescription
GET/spaces/{slug}/package-repositories/project-scopesList Python package project scopes.
POST/spaces/{slug}/package-repositories/project-scopesCreate a project scope.
PATCH/spaces/{slug}/package-repositories/project-scopes/{project_scope_id}Update a project scope.
GET/spaces/{slug}/package-repositories/project-scopes/{project_scope_id}/accessRead project-scope access settings.
PUT/spaces/{slug}/package-repositories/project-scopes/{project_scope_id}/accessReplace project-scope access settings.
GET/spaces/{slug}/package-repositoriesList repositories.
POST/spaces/{slug}/package-repositoriesCreate a repository.
GET/spaces/{slug}/package-repositories/{repository_id}Read repository detail with packages.
PATCH/spaces/{slug}/package-repositories/{repository_id}Update repository metadata or active/archive flags.
POST/spaces/{slug}/package-repositories/{repository_id}/archiveArchive a repository.
DELETE/spaces/{slug}/package-repositories/{repository_id}Permanently delete a repository.
GET/spaces/{slug}/package-repositories/{repository_id}/packagesList packages in a repository.
GET/spaces/{slug}/package-repositories/{repository_id}/packages/{package_name}Read one package and its versions/files.
POST/spaces/{slug}/package-repositories/{repository_id}/filesUpload a package distribution file through the management API.
PATCH/spaces/{slug}/package-repositories/files/{file_id}/yankYank or unyank a distribution file.
DELETE/spaces/{slug}/package-repositories/files/{file_id}Delete a distribution file.
GET/pypi/spaces/{space_slug}/repositories/{repository_slug}/simple/PyPA Simple repository index for a space-wide repository.
GET/pypi/spaces/{space_slug}/projects/{project_slug}/repositories/{repository_slug}/simple/PyPA Simple repository index for a project repository.
GET/pypi/spaces/{space_slug}/repositories/{repository_slug}/simple/{package_name}/PyPA Simple package index for a space-wide repository.
GET/pypi/spaces/{space_slug}/projects/{project_slug}/repositories/{repository_slug}/simple/{package_name}/PyPA Simple package index for a project repository.
POST/pypi/spaces/{space_slug}/repositories/{repository_slug}/legacy/Legacy upload endpoint for twine.
POST/pypi/spaces/{space_slug}/projects/{project_slug}/repositories/{repository_slug}/legacy/Project-scoped legacy upload endpoint for twine.
GET/pypi/files/{file_id}/{filename}Download a package file.
GET/spaces/{slug}/package-repositories/{repository_id}/pypi/packages/{package_name}Preview public PyPI package versions.
GET/spaces/{slug}/package-repositories/{repository_id}/pypi/packages/{package_name}/versions/{version}Preview files for one public PyPI release.
POST/spaces/{slug}/package-repositories/{repository_id}/imports/pypi/dependencies/previewPreview dependency artifacts for a public PyPI import.
POST/spaces/{slug}/package-repositories/{repository_id}/imports/pypiQueue a public PyPI release import.
GET/spaces/{slug}/package-repositories/{repository_id}/imports/pypi/jobs/{job_id}Read PyPI import job status.
GET/spaces/{slug}/package-repositories/{repository_id}/scan-policyRead vulnerability scan policy.
PATCH/spaces/{slug}/package-repositories/{repository_id}/scan-policyUpdate vulnerability scan policy.
POST/spaces/{slug}/package-repositories/{repository_id}/scansQueue scans for repository files.
POST/spaces/{slug}/package-repositories/files/{file_id}/scanQueue a scan for one file.
GET/spaces/{slug}/package-repositories/files/{file_id}/scanRead the latest scan for one file.

Project Scopes

Project scopes isolate repositories inside a space and provide a reusable access boundary for related packages.

Create Project Scope

POST /spaces/{slug}/package-repositories/project-scopes
{
"name": "Pipeline Authoring",
"slug": "pipeline-authoring",
"description": "Packages used by pipeline authoring workspaces.",
"source_type": "workspace",
"source_id": "550e8400-e29b-41d4-a716-446655440000",
"source_metadata": {
"owner": "data-platform"
},
"space_member_access_level": "role_based"
}

Create fields are name, optional slug, description, source_type, source_id, source_metadata, and space_member_access_level.

Update Project Scope

PATCH /spaces/{slug}/package-repositories/project-scopes/{project_scope_id}

Update accepts name, slug, description, and space_member_access_level. Changing space_member_access_level requires project admin access.

Project Scope Access

GET /spaces/{slug}/package-repositories/project-scopes/{project_scope_id}/access
PUT /spaces/{slug}/package-repositories/project-scopes/{project_scope_id}/access
{
"space_member_access_level": "none",
"grants": [
{
"principal_type": "team",
"principal_id": "660e8400-e29b-41d4-a716-446655440000",
"access_level": "publish"
}
]
}

The read response includes project_scope_id, space_member_access_level, grants, and effective_permissions.

Repository Management

Create Repository

POST /spaces/{slug}/package-repositories
{
"name": "Developer Tools",
"slug": "developer-tools",
"description": "Internal SDK and CLI packages.",
"project_scope_slug": "pipeline-authoring"
}

Create fields are:

FieldRequiredDescription
nameYesRepository display name.
slugNoURL-friendly repository slug.
descriptionNoRepository description.
project_scope_idNoProject scope UUID for a project-scoped repository.
project_scope_slugNoProject scope slug for a project-scoped repository.

Use either project_scope_id or project_scope_slug, not both. Omit both for a space-wide repository.

List Repositories

GET /spaces/{slug}/package-repositories?include_archived=false

Repository responses include:

Field groupFields
Identityid, space_id, project_scope_id, project_scope, name, slug, description
Lifecycleis_active, archived_at, created_at, updated_at, created_by_id, updated_by_id
Inventorypackage_count, file_count
Client URLssimple_index_url, upload_url
Access and securityeffective_permissions, scan_policy, security_summary

Read, Update, Archive, Delete

GET /spaces/{slug}/package-repositories/{repository_id}
PATCH /spaces/{slug}/package-repositories/{repository_id}
POST /spaces/{slug}/package-repositories/{repository_id}/archive
DELETE /spaces/{slug}/package-repositories/{repository_id}

The detail response includes the repository plus its packages.

Update accepts name, description, is_active, and archived. Archive is a shortcut for setting archived to true. Delete permanently removes the repository, hosted packages, files, scan data, scan policy rows, and stored package blobs on a best-effort basis after the database delete commits.

Packages And Files

List Or Read Packages

GET /spaces/{slug}/package-repositories/{repository_id}/packages
GET /spaces/{slug}/package-repositories/{repository_id}/packages/{package_name}

Package responses include package identity, normalized name, summary, latest version, timestamps, versions, and files. File records include filename, distribution type, content type, size, SHA-256, storage backend, yanked state, scan state, vulnerability summary, blocking state, uploader, upload timestamp, and download_url.

Upload File

POST /spaces/{slug}/package-repositories/{repository_id}/files
Content-Type: multipart/form-data

Upload a wheel or source distribution with form field file.

curl -X POST "https://api.qarion.com/api/v1.0/spaces/acme/package-repositories/550e8400-e29b-41d4-a716-446655440000/files" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@dist/qarion_sdk-0.2.0-py3-none-any.whl"

The response contains package, version, and file.

Yank Or Delete File

PATCH /spaces/{slug}/package-repositories/files/{file_id}/yank
DELETE /spaces/{slug}/package-repositories/files/{file_id}
{
"yanked": true,
"reason": "Superseded by 0.2.1"
}

Yanking preserves the file but marks it as yanked for package clients. Deleting removes the file.

pip And Simple API

Use simple_index_url from repository responses, or build the URL directly.

Space-wide repository:

pip install qarion-sdk \
--index-url "https://__token__:YOUR_API_KEY@api.qarion.com/api/v1.0/pypi/spaces/acme/repositories/developer-tools/simple/" \
--extra-index-url "https://pypi.org/simple"

Project-scoped repository:

pip install qarion-sdk \
--index-url "https://__token__:YOUR_API_KEY@api.qarion.com/api/v1.0/pypi/spaces/acme/projects/pipeline-authoring/repositories/developer-tools/simple/"

Simple API endpoints return HTML by default and return application/vnd.pypi.simple.v1+json when the client requests JSON. Package file links include SHA-256 hashes and yanked metadata. Vulnerability-blocked files are omitted from Simple API indexes.

twine And Legacy Upload API

Use upload_url from repository responses, or configure .pypirc directly:

[distutils]
index-servers =
qarion-space

[qarion-space]
repository = https://api.qarion.com/api/v1.0/pypi/spaces/acme/repositories/developer-tools/legacy/
username = __token__
password = YOUR_API_KEY

Then upload:

python -m build
twine upload --repository qarion-space dist/*

The legacy upload endpoint accepts standard twine multipart form fields, including :action, protocol_version, name, version, and content.

File Download

GET /pypi/files/{file_id}/{filename}

Downloads a hosted file, returns a storage redirect, or returns inline bytes depending on the storage backend. The filename in the path must match the stored filename. Blocked or inaccessible files return 404.

Public PyPI Import

Repository admins can preview and import exact package releases from pypi.org.

Preview Package Versions

GET /spaces/{slug}/package-repositories/{repository_id}/pypi/packages/{package_name}

Returns name, normalized_name, summary, latest_version, and version summaries with file counts, yanked file counts, and unsupported file counts.

Preview Release Files

GET /spaces/{slug}/package-repositories/{repository_id}/pypi/packages/{package_name}/versions/{version}

Returns supported release files with filename, distribution type, package type, Python tags, ABI tags, platform tags, size, SHA-256, upload time, and yanked state.

Preview Dependencies

POST /spaces/{slug}/package-repositories/{repository_id}/imports/pypi/dependencies/preview
{
"package_name": "pandas",
"version": "2.2.2",
"selected_filenames": ["pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl"],
"python_tags": ["cp311"],
"platform_tags": ["macosx_11_0_arm64"],
"include_sdists": true,
"include_universal_wheels": true
}

The preview returns root filenames, resolved target Python/platform values, dependency counts, already-imported counts, and dependency artifact selections.

Queue Import

POST /spaces/{slug}/package-repositories/{repository_id}/imports/pypi
{
"package_name": "pandas",
"version": "2.2.2",
"selected_filenames": ["pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl"],
"python_tags": ["cp311"],
"platform_tags": ["macosx_11_0_arm64"],
"include_sdists": true,
"include_universal_wheels": true,
"include_dependencies": true,
"include_all_dependencies": false,
"selected_dependency_artifacts": [
{
"normalized_name": "numpy",
"version": "1.26.4",
"filename": "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl"
}
]
}

The create response is:

{
"job_id": "770e8400-e29b-41d4-a716-446655440000"
}

Imports run asynchronously on the arq:bulk queue through import_python_package_from_pypi. External worker deployments must run a bulk worker or the priority worker supervisor. If the queue is unavailable, the API returns 503 Service Unavailable.

Import Job Status

GET /spaces/{slug}/package-repositories/{repository_id}/imports/pypi/jobs/{job_id}

The status response includes job_id, status, percent, phase, message, progress_events, optional result, and optional error_message. Completed results include imported and skipped root files plus imported and skipped dependency files.

Vulnerability Scanning

Scanning is enabled only when the platform setting PYTHON_PACKAGE_VULN_SCANNING_ENABLED=true and the repository scan policy is enabled.

Scan Policy

GET /spaces/{slug}/package-repositories/{repository_id}/scan-policy
PATCH /spaces/{slug}/package-repositories/{repository_id}/scan-policy
{
"enabled": true,
"provider": "osv",
"target_python_version": "3.11",
"target_platform": "manylinux_2_17_x86_64",
"include_public_index": true,
"public_index_url": "https://pypi.org/simple",
"extra_index_urls": ["https://packages.example.com/simple"],
"enforcement_mode": "block_by_threshold",
"severity_threshold": "high",
"block_pending": false,
"block_failed": false
}

Policy fields include:

FieldDescription
enabledEnables repository scanning when platform scanning is also enabled.
providerVulnerability provider. Current value: osv.
target_python_versionOptional resolver Python target.
target_platformOptional resolver platform target.
include_public_indexAllow resolver access to the public index.
public_index_urlPublic index URL used by the resolver.
extra_index_urlsAdditional unauthenticated package indexes.
enforcement_modereport_only or block_by_threshold.
severity_thresholdlow, medium, high, or critical.
block_pendingHide files with pending scans from package clients.
block_failedHide files with failed scans from package clients.

Queue Scans

POST /spaces/{slug}/package-repositories/{repository_id}/scans
POST /spaces/{slug}/package-repositories/files/{file_id}/scan

File scan request body:

{
"trigger": "manual"
}

Scan triggers are upload, manual, repository, and retry.

Read File Scan

GET /spaces/{slug}/package-repositories/files/{file_id}/scan

Scan responses include execution state, resolver targets, enforcement settings, dependency count, finding count, max severity, blocked state, dependencies, and findings. Execution statuses are pending, running, completed, failed, and skipped.

Install blocking applies only to package-client access. Management APIs and the admin UI still show blocked files, scan findings, and policy state.

Error Notes

StatusMeaning
401Package-client Basic credentials are missing or invalid.
403The caller lacks create, publish, edit, admin, delete, yank, or scan access.
404Space, project scope, repository, package, file, or job was not found or is inaccessible. Package-client endpoints also use 404 for vulnerability-blocked files.
422Request payload is invalid, such as both project scope selectors being supplied.
503The PyPI import queue is unavailable.