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.
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.
| Scope | Behavior |
|---|---|
| Space-wide repository | Uses python_package_repository permissions in the space. |
| Project-scoped repository | Inherits 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
| Method | Endpoint | Description |
|---|---|---|
GET | /spaces/{slug}/package-repositories/project-scopes | List Python package project scopes. |
POST | /spaces/{slug}/package-repositories/project-scopes | Create 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}/access | Read project-scope access settings. |
PUT | /spaces/{slug}/package-repositories/project-scopes/{project_scope_id}/access | Replace project-scope access settings. |
GET | /spaces/{slug}/package-repositories | List repositories. |
POST | /spaces/{slug}/package-repositories | Create 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}/archive | Archive a repository. |
DELETE | /spaces/{slug}/package-repositories/{repository_id} | Permanently delete a repository. |
GET | /spaces/{slug}/package-repositories/{repository_id}/packages | List 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}/files | Upload a package distribution file through the management API. |
PATCH | /spaces/{slug}/package-repositories/files/{file_id}/yank | Yank 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/preview | Preview dependency artifacts for a public PyPI import. |
POST | /spaces/{slug}/package-repositories/{repository_id}/imports/pypi | Queue 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-policy | Read vulnerability scan policy. |
PATCH | /spaces/{slug}/package-repositories/{repository_id}/scan-policy | Update vulnerability scan policy. |
POST | /spaces/{slug}/package-repositories/{repository_id}/scans | Queue scans for repository files. |
POST | /spaces/{slug}/package-repositories/files/{file_id}/scan | Queue a scan for one file. |
GET | /spaces/{slug}/package-repositories/files/{file_id}/scan | Read 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:
| Field | Required | Description |
|---|---|---|
name | Yes | Repository display name. |
slug | No | URL-friendly repository slug. |
description | No | Repository description. |
project_scope_id | No | Project scope UUID for a project-scoped repository. |
project_scope_slug | No | Project 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 group | Fields |
|---|---|
| Identity | id, space_id, project_scope_id, project_scope, name, slug, description |
| Lifecycle | is_active, archived_at, created_at, updated_at, created_by_id, updated_by_id |
| Inventory | package_count, file_count |
| Client URLs | simple_index_url, upload_url |
| Access and security | effective_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:
| Field | Description |
|---|---|
enabled | Enables repository scanning when platform scanning is also enabled. |
provider | Vulnerability provider. Current value: osv. |
target_python_version | Optional resolver Python target. |
target_platform | Optional resolver platform target. |
include_public_index | Allow resolver access to the public index. |
public_index_url | Public index URL used by the resolver. |
extra_index_urls | Additional unauthenticated package indexes. |
enforcement_mode | report_only or block_by_threshold. |
severity_threshold | low, medium, high, or critical. |
block_pending | Hide files with pending scans from package clients. |
block_failed | Hide 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
| Status | Meaning |
|---|---|
401 | Package-client Basic credentials are missing or invalid. |
403 | The caller lacks create, publish, edit, admin, delete, yank, or scan access. |
404 | Space, project scope, repository, package, file, or job was not found or is inaccessible. Package-client endpoints also use 404 for vulnerability-blocked files. |
422 | Request payload is invalid, such as both project scope selectors being supplied. |
503 | The PyPI import queue is unavailable. |