Python Package Repository HTTP Routes
Nitro Repo implements a PyPI-compatible API for Python packages for both hosted and proxy repositories. This reference details every HTTP route, its purpose, and example usage. Replace placeholders with your own hostname, storage name, repository name, package name, and authentication credentials.
<host>– Nitro Repo base URL (e.g.nitro.example.com)<storage>– Storage identifier that backs the repository<repository>– Repository name<package>– Python package name (e.g.my-package,my_company_package)<version>– Package version (e.g.1.0.0,1.0.0rc1)<filename>– Package distribution filename<username>– Username for basic authentication<password>– Password for basic authentication<token>– Bearer token for API authentication
Repository Information
Root Endpoint
Get repository information:
curl -u "<username>:<password>" \
"https://<host>/repositories/<storage>/<repository>/"Response:
{
"repository": "python-repo",
"title": "Python Package Repository",
"description": "Private Python package repository",
"packages_count": 234,
"downloads_count": 5678,
"last_updated": "2023-12-15T10:00:00Z"
}Simple Package Index
Simple Index Root
Get the simple package index root:
curl -u "<username>:<password>" \
"https://<host>/repositories/<storage>/<repository>/simple/"Response:
<!DOCTYPE html>
<html>
<head>
<title>Simple Index</title>
</head>
<body>
<h1>Simple Index</h1>
<a href="my-package/">my-package</a><br>
<a href="my_company_package/">my_company_package</a><br>
<a href="another-package/">another-package</a><br>
</body>
</html>Package Versions List
List available versions for a specific package:
curl -u "<username>:<password>" \
"https://<host>/repositories/<storage>/<repository>/simple/<package>/"Response:
<!DOCTYPE html>
<html>
<head>
<title>Links for my-package</title>
</head>
<body>
<h1>Links for my-package</h1>
<a href="https://<host>/repositories/<storage>/<repository>/packages/my-package-1.0.0.tar.gz#sha256=abc123...">my-package-1.0.0.tar.gz</a><br>
<a href="https://<host>/repositories/<storage>/<repository>/packages/my-package-1.0.0-py3-none-any.whl#sha256=def456...">my-package-1.0.0-py3-none-any.whl</a><br>
<a href="https://<host>/repositories/<storage>/<repository>/packages/my-package-1.1.0.tar.gz#sha256=ghi789...">my-package-1.1.0.tar.gz</a><br>
</body>
</html>Package Metadata Operations
Get Package Metadata (JSON API)
Retrieve detailed package information in JSON format:
curl -u "<username>:<password>" \
"https://<host>/repositories/<storage>/<repository>/pypi/<package>/<version>/json"Response:
{
"info": {
"name": "my-package",
"version": "1.0.0",
"author": "Author Name",
"author_email": "author@example.com",
"maintainer": "Maintainer Name",
"maintainer_email": "maintainer@example.com",
"home_page": "https://github.com/username/my-package",
"license": "MIT",
"summary": "My awesome Python package",
"description": "Long description of the package with markdown...",
"keywords": "awesome,package,python",
"platform": ["any"],
"requires_python": ">=3.7",
"yanked": false,
"classifiers": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11"
],
"requires_dist": [
"requests>=2.25.0",
"click>=8.0.0",
"pydantic>=1.8.0"
],
"provides_extra": ["dev", "test"],
"project_urls": {
"Bug Reports": "https://github.com/username/my-package/issues",
"Source": "https://github.com/username/my-package",
"Documentation": "https://my-package.readthedocs.io/"
}
},
"last_serial": 123456,
"releases": {
"1.0.0": [
{
"filename": "my-package-1.0.0.tar.gz",
"python_version": "source",
"packagetype": "sdist",
"comment_text": "",
"digests": {
"md5": "abc123...",
"sha256": "def456...",
"blake2b_256": "ghi789..."
},
"downloads": -1,
"upload_time": "2023-12-15T10:00:00",
"upload_time_iso_8601": "2023-12-15T10:00:00.000000Z",
"url": "https://<host>/repositories/<storage>/<repository>/packages/my-package-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
},
{
"filename": "my_package-1.0.0-py3-none-any.whl",
"python_version": "py3",
"packagetype": "bdist_wheel",
"comment_text": "",
"digests": {
"md5": "jkl012...",
"sha256": "mno345...",
"blake2b_256": "pqr678..."
},
"downloads": -1,
"upload_time": "2023-12-15T10:05:00",
"upload_time_iso_8601": "2023-12-15T10:05:00.000000Z",
"url": "https://<host>/repositories/<storage>/<repository>/packages/my_package-1.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
}
]
},
"urls": [
{
"filename": "my-package-1.0.0.tar.gz",
"python_version": "source",
"packagetype": "sdist",
"url": "https://<host>/repositories/<storage>/<repository>/packages/my-package-1.0.0.tar.gz",
"digests": {
"sha256": "def456..."
},
"yanked": false
},
{
"filename": "my_package-1.0.0-py3-none-any.whl",
"python_version": "py3",
"packagetype": "bdist_wheel",
"url": "https://<host>/repositories/<storage>/<repository>/packages/my_package-1.0.0-py3-none-any.whl",
"digests": {
"sha256": "mno345..."
},
"yanked": false
}
]
}Get All Package Versions
List all available versions for a package:
curl -u "<username>:<password>" \
"https://<host>/repositories/<storage>/<repository>/pypi/<package>/json"Get Package Index Data
Get package index information:
curl -u "<username>:<password>" \
"https://<host>/repositories/<storage>/<repository>/pypi-data/<package>/json"Package Distribution Operations
Download Package
Download a specific package distribution file:
curl -u "<username>:<password>" -O \
"https://<host>/repositories/<storage>/<repository>/packages/<filename>"Examples:
# Download source distribution
curl -u "username:password" -O \
"https://nitro.example.com/repositories/storage/python-repo/packages/my-package-1.0.0.tar.gz"
# Download wheel distribution
curl -u "username:password" -O \
"https://nitro.example.com/repositories/storage/python-repo/packages/my_package-1.0.0-py3-none-any.whl"Check Package Exists
Verify if a package file exists without downloading:
curl -I -u "<username>:<password>" \
"https://<host>/repositories/<storage>/<repository>/packages/<filename>"Upload Operations
Upload Package File
Upload a new package distribution:
curl -X POST \
-u "<username>:<password>" \
-F "content=@my-package-1.0.0.tar.gz" \
-F ":action=file_upload" \
"https://<host>/repositories/<storage>/<repository>/pypi/<package>/upload/"Upload Package with Metadata
Upload package with additional metadata:
curl -X POST \
-u "<username>:<password>" \
-F "content=@my_package-1.0.0-py3-none-any.whl" \
-F ":action=file_upload" \
-F "metadata_version=2.1" \
-F "name=my-package" \
-F "version=1.0.0" \
-F "filetype=bdist_wheel" \
-F "pyversion=py3" \
-F "metadata_2_name=my-package" \
-F "metadata_2_version=1.0.0" \
-F "metadata_2_summary=My awesome package" \
-F "metadata_2_author=Author Name" \
-F "metadata_2_author_email=author@example.com" \
-F "metadata_2_license=MIT" \
-F "metadata_2_home_page=https://github.com/username/my-package" \
"https://<host>/repositories/<storage>/<repository>/pypi/<package>/upload/"Upload with Form Fields
Complete upload with all form fields:
curl -X POST \
-u "<username>:<password>" \
-F ":action=file_upload" \
-F "content=@my-package-1.0.0.tar.gz" \
-F "name=my-package" \
-F "version=1.0.0" \
-F "filetype=sdist" \
-F "pyversion=source" \
-F "metadata_version=2.1" \
-F "summary=My awesome package" \
-F "description=Long description of the package..." \
-F "author=Author Name" \
-F "author_email=author@example.com" \
-F "maintainer=Maintainer Name" \
-F "maintainer_email=maintainer@example.com" \
-F "license=MIT" \
-F "home_page=https://github.com/username/my-package" \
-F "keywords=awesome,package,python" \
-F "platform=any" \
-F "requires_python=>=3.7" \
-F "classifiers=Development Status :: 5 - Production/Stable" \
-F "classifiers=Intended Audience :: Developers" \
-F "classifiers=License :: OSI Approved :: MIT License" \
-F "classifiers=Programming Language :: Python :: 3" \
-F "requires_dist=requests>=2.25.0" \
-F "requires_dist=click>=8.0.0" \
-F "provides_extra=dev" \
-F "project_url=Bug Reports,https://github.com/username/my-package/issues" \
-F "project_url=Source,https://github.com/username/my-package" \
-F "project_url=Documentation,https://my-package.readthedocs.io/" \
"https://<host>/repositories/<storage>/<repository>/pypi/<package>/upload/"Search Operations
Search Packages
Search for packages by name, description, or keywords:
curl -u "<username>:<password>" \
"https://<host>/repositories/<storage>/<repository>/search?q=my-search-term&page=1&per_page=20"Response:
{
"results": [
{
"name": "my-package",
"version": "1.0.0",
"description": "My awesome package",
"author": "Author Name",
"author_email": "author@example.com",
"maintainer": "Maintainer Name",
"maintainer_email": "maintainer@example.com",
"license": "MIT",
"keywords": ["awesome", "package", "python"],
"home_page": "https://github.com/username/my-package",
"requires_python": ">=3.7",
"downloads": {
"last_day": 10,
"last_week": 50,
"last_month": 200
},
"release_urls": [
{
"packagetype": "sdist",
"filename": "my-package-1.0.0.tar.gz",
"python_version": "source",
"url": "https://<host>/repositories/<storage>/<repository>/packages/my-package-1.0.0.tar.gz"
},
{
"packagetype": "bdist_wheel",
"filename": "my_package-1.0.0-py3-none-any.whl",
"python_version": "py3",
"url": "https://<host>/repositories/<storage>/<repository>/packages/my_package-1.0.0-py3-none-any.whl"
}
]
}
],
"info": {
"total": 1,
"page": 1,
"per_page": 20,
"pages": 1
}
}Complete Upload Workflow
Python Package Upload with Twine Equivalent
#!/bin/bash
HOST="https://nitro.example.com"
STORAGE="storage"
REPO="python-repo"
PACKAGE="my-package"
VERSION="1.0.0"
USER="username"
PASS="password"
# Upload source distribution
echo "Uploading source distribution..."
curl -X POST \
-u "${USER}:${PASS}" \
-F ":action=file_upload" \
-F "content@=dist/${PACKAGE}-${VERSION}.tar.gz" \
-F "name=${PACKAGE}" \
-F "version=${VERSION}" \
-F "filetype=sdist" \
-F "pyversion=source" \
-F "metadata_version=2.1" \
-F "summary=My awesome package" \
-F "author=Author Name" \
-F "author_email=author@example.com" \
-F "license=MIT" \
-F "requires_python=>=3.7" \
-F "requires_dist=requests>=2.25.0" \
"${HOST}/repositories/${STORAGE}/${REPO}/pypi/${PACKAGE}/upload/"
# Upload wheel distribution
echo "Uploading wheel distribution..."
curl -X POST \
-u "${USER}:${PASS}" \
-F ":action=file_upload" \
-F "content@=dist/${PACKAGE//-/_}-${VERSION}-py3-none-any.whl" \
-F "name=${PACKAGE}" \
-F "version=${VERSION}" \
-F "filetype=bdist_wheel" \
-F "pyversion=py3" \
-F "metadata_version=2.1" \
-F "summary=My awesome package" \
-F "author=Author Name" \
-F "author_email=author@example.com" \
-F "license=MIT" \
-F "requires_python=>=3.7" \
-F "requires_dist=requests>=2.25.0" \
"${HOST}/repositories/${STORAGE}/${REPO}/pypi/${PACKAGE}/upload/"
echo "Package ${PACKAGE} version ${VERSION} uploaded successfully!"Repository Management API
List Cached Packages
List cached Python packages:
curl -H "Authorization: Bearer <token>" \
"https://<host>/api/repository/<repository-id>/packages?page=1&per_page=50"Delete Cached Packages
Remove cached packages (requires repository edit permission):
curl -X DELETE \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"paths": ["packages/my-package-1.0.0.tar.gz", "packages/my_package-1.0.0-py3-none-any.whl"]}' \
"https://<host>/api/repository/<repository-id>/packages"Repository Configuration
Get Python repository configuration:
curl -H "Authorization: Bearer <token>" \
"https://<host>/api/repository/<repository-id>/config/python"Update repository configuration:
curl -X PUT \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d @config.json \
"https://<host>/api/repository/<repository-id>/config/python"Repository Statistics
Get repository usage statistics:
curl -H "Authorization: Bearer <token>" \
"https://<host>/api/repository/<repository-id>/stats"Response:
{
"total_packages": 123,
"total_versions": 456,
"total_downloads": 7890,
"storage_used": "1.2GB",
"last_activity": "2023-12-15T15:30:00Z",
"top_packages": [
{"name": "my-package", "download_count": 1234},
{"name": "my_company_package", "download_count": 987}
],
"package_types": {
"sdist": 234,
"bdist_wheel": 567
}
}Proxy Repository Operations
Proxy Route Configuration
Configure upstream PyPI repositories for proxy mode:
{
"type": "Proxy",
"proxy": {
"routes": [
{
"url": "https://pypi.org",
"name": "PyPI Official",
"priority": 10
},
{
"url": "https://pypi.python.org/simple",
"name": "PyPI Simple",
"priority": 5
}
]
}
}Proxy Cache Bypass
Force refresh from upstream repositories:
curl -H "Authorization: Bearer <token>" \
-H "Cache-Control: no-cache" \
"https://<host>/repositories/<storage>/<repository>/pypi/<package>/<version>/json"pip Client Integration
Typical pip Workflow
When pip installs from the repository:
- **GET
/simple/**- Discover available packages - **GET
/simple/<package>/**- Get list of versions for package - **GET
/packages/<filename>**- Download specific package file - Hash verification - Verify downloaded file integrity
pip Configuration
# ~/.pip/pip.conf
[global]
index-url = https://<host>/repositories/<storage>/<repository>/simple
extra-index-url = https://pypi.org/simple
[install]
trusted-host = <host>Environment Variables
export PIP_INDEX_URL="https://<host>/repositories/<storage>/<repository>/simple"
export PIP_EXTRA_INDEX_URL="https://pypi.org/simple"
export PIP_TRUSTED_HOST="<host>"Authentication
Basic Authentication
All operations typically require authentication:
curl -u "username:password" \
"https://<host>/repositories/<storage>/<repository>/simple/"API Token Authentication
Management endpoints use Bearer tokens:
curl -H "Authorization: Bearer <token>" \
"https://<host>/api/repository/<repository-id>/stats"Error Responses
Common Error Codes
401 Unauthorized- Authentication required or invalid credentials403 Forbidden- Insufficient permissions404 Not Found- Package, version, or file does not exist409 Conflict- Package version already exists422 Unprocessable Entity- Invalid package metadata or file500 Internal Server Error- Server-side error during upload
Error Response Format
{
"error": "Not Found",
"message": "Package 'nonexistent-package' not found",
"details": {
"package": "nonexistent-package",
"repository": "python-repo"
}
}Storage Layout
repositories/
└── storage/
└── python-repo/
├── simple/
│ ├── index.html
│ ├── my-package/
│ │ └── index.html
│ └── my_company_package/
│ └── index.html
├── packages/
│ ├── my-package-1.0.0.tar.gz
│ ├── my_package-1.0.0-py3-none-any.whl
│ └── another-package-2.1.0-py3-none-any.whl
└── pypi/
└── my-package/
├── 1.0.0/
│ └── json
└── jsonPackage Types Supported
Source Distribution (sdist)
- File extension:
.tar.gz - Python version:
source - Content type:
application/x-tar
Wheel Distribution (bdist_wheel)
- File extension:
.whl - Python version:
py3,cp39, etc. - Content type:
application/zip
Use these endpoints as a foundation for pip client integration, build tool configuration, or custom tooling when working with Nitro Repo Python repositories.
Complete reference for Python package repository HTTP routes. See Python Quick Reference for usage examples and configuration.