Strapi Python Client
The unofficial Python client library to easily interface with Strapi from your Python project.
Installation
pip install strapi-py
Quick Start
from strapi_client import strapi
# Create a client instance
client = strapi(base_url="http://localhost:1337/api")
# Fetch data from a custom endpoint
response = client.fetch('/articles')
data = response.json()
print(data)
Authentication
API Token Authentication
from strapi_client import strapi
# Initialize the client with yor API token
client = strapi(
base_url="http://localhost:1337/api",
auth="your-api-token-here"
)
# All requests will include the Authorization header
response = client.fetch('/articles')
Custom Headers
client = strapi(
base_url="http://localhost:1337/api",
headers={
"X-Custom-Header": "value",
"Accept-Language": "en"
}
)
Working with Collection Types
Collection types represent multiple entries (e.g., articles, products, users).
Find All Entries
# Get all articles
articles = client.collection('articles')
response = articles.find()
print(response.json())
Find with Filters and Sorting
articles = client.collection('articles')
# Filter and sort
response = articles.find(params={
'filters': {
'title': {
'$contains': 'Python'
},
'publishedAt': {
'$notNull': True
}
},
'sort': ['createdAt:desc'],
'pagination': {
'page': 1,
'pageSize': 10
},
'populate': '*'
})
Find One Entry
articles = client.collection('articles')
# Get article with documentId j964065dnjrdr4u89weh79xl
response = articles.find_one("j964065dnjrdr4u89weh79xl", params={
'populate': ['author', 'comments']
})
print(response.json())
NOTE
Strapi 5 replaces the numeric
idused in Strapi 4 with a new 24-character alphanumeric identifier calleddocumentId. When working with Strapi 5, usedocumentIdas the primary resource identifier. This library continues to support the legacyidfield for Strapi 4 projects.
Create an Entry
articles = client.collection('articles')
response = articles.create(data={
'title': 'My New Article',
'content': 'This is the article content',
'publishedAt': '2024-01-01T00:00:00.000Z'
})
created_article = response.json()
print(f"Created article with ID: {created_article['data']['documentId']}")
Update an Entry
articles = client.collection('articles')
response = articles.update(1, data={
'title': 'Updated Article Title',
'content': 'Updated content'
})
Delete an Entry
articles = client.collection('articles')
response = articles.delete(1)
Working with Single Types
Single types represent a single entry (e.g., homepage, about page, settings).
Find Single Type
homepage = client.single('homepage')
response = homepage.find(params={'populate': '*'})
print(response.json())
Update Single Type
homepage = client.single('homepage')
response = homepage.update(data={
'title': 'Welcome to My Site',
'description': 'A brief description'
})
Delete Single Type
homepage = client.single('homepage')
response = homepage.delete()
Working with Plugins
Users & Permissions Plugin
# Access users endpoint
users = client.collection(
resource='users',
plugin={'name': 'users-permissions', 'prefix': ''}
)
# Create a new user
response = users.create(data={
'username': 'johndoe',
'email': 'john@example.com',
'password': 'SecurePassword123!'
})
Custom Plugin with Prefix
# Access a custom plugin endpoint
blog_posts = client.collection(
resource='posts',
plugin={'name': 'blog', 'prefix': 'blog'}
)
# This will make requests to /blog/posts
response = blog_posts.find()
Custom Plugin without Prefix
# Disable plugin prefix
custom_content = client.collection(
resource='items',
plugin={'name': 'custom-plugin', 'prefix': ''}
)
# This will make requests to /items
File Management
Upload a File
# Upload from bytes
with open('image.jpg', 'rb') as f:
file_data = f.read()
response = client.files.upload(
file_data=file_data,
filename='image.jpg',
mimetype='image/jpeg',
file_info={
'alternativeText': 'A sample image',
'caption': 'My caption'
}
)
print(response.json())
Upload with File Object
import io
# Upload from file-like object
with open('document.pdf', 'rb') as f:
response = client.files.upload(
file_data=f,
filename='document.pdf',
mimetype='application/pdf'
)
List All Files
# Get all files
response = client.files.find()
files = response.json()
# Filter files
response = client.files.find(params={
'filters': {
'mime': {'$contains': 'image'}
},
'sort': 'createdAt:desc'
})
Get a Specific File
response = client.files.find_one(file_id="clkgylmcc000008lcdd868feh")
file_data = response.json()
print(f"File URL: {file_data['url']}")
Update File Metadata
response = client.files.update(
file_id="clkgylmcc000008lcdd868feh",
file_info={
'name': 'renamed-file.jpg',
'alternativeText': 'Updated alt text',
'caption': 'Updated caption'
}
)
Delete a File
response = client.files.delete(file_id="clkgylmcc000008lcdd868feh")
Custom Paths
You can specify custom API paths for content types:
# Use a custom path instead of the default /articles
custom_articles = client.collection(
resource='articles',
path='/v2/custom-articles'
)
response = custom_articles.find()
# Makes request to /v2/custom-articles
Error Handling
The library provides detailed error messages from Strapi, making debugging much easier. When an error occurs, you'll see:
- Error type/name (e.g., ValidationError, ApplicationError)
- Clear error message from Strapi
- Detailed field-level validation errors with paths
- Access to the original response for debugging
Basic Error Handling
from strapi_client import (
strapi,
StrapiHTTPError,
StrapiHTTPNotFoundError,
StrapiHTTPUnauthorizedError,
StrapiHTTPBadRequestError,
StrapiValidationError
)
try:
client = strapi(base_url="http://localhost:1337/api")
articles = client.collection('articles')
response = articles.find_one(999)
except StrapiHTTPNotFoundError as e:
print(f"Article not found: {e}")
print(f"Status code: {e.response.status_code}")
except StrapiHTTPUnauthorizedError as e:
print(f"Authentication failed: {e}")
except StrapiHTTPBadRequestError as e:
print(f"Bad request: {e}")
print(f"Full error: {e.response.json()}")
except StrapiHTTPError as e:
print(f"HTTP error occurred: {e}")
print(f"Response: {e.response.text}")
except StrapiValidationError as e:
print(f"Validation error: {e}")
Detailed Validation Errors
When you have validation errors (e.g., in dynamic zones or complex fields), the error message will show exactly which fields are problematic:
try:
blog = client.collection('blogs')
response = blog.create(data={
'title': '', # Invalid: too short
'blocks': [
{
# Missing __component field
'body': 'Some content'
}
]
})
except StrapiHTTPBadRequestError as e:
print(e)
# Output:
# Strapi API Error (400): [ValidationError] Invalid data provided
# Validation errors:
# - title: title must be at least 1 characters
# - blocks.0.__component: component is required
Accessing Response Details
All HTTP errors preserve the original response, allowing you to access additional details:
try:
response = client.collection('articles').create(data={...})
except StrapiHTTPBadRequestError as e:
# Get status code
print(f"Status: {e.response.status_code}")
# Get full error details
error_details = e.response.json()
print(f"Error name: {error_details['error']['name']}")
print(f"Error details: {error_details['error']['details']}")
# Get request Url
print(f"Request URL: {e.response.request.url}")
Available Error Types
StrapiError- Base error classStrapiValidationError- Invalid input or configurationStrapiHTTPError- Base HTTP error (non-2xx responses)StrapiHTTPBadRequestError- 400 Bad Request (validation errors, malformed requests)StrapiHTTPUnauthorizedError- 401 Unauthorized (authentication required)StrapiHTTPForbiddenError- 403 Forbidden (insufficient permissions)StrapiHTTPNotFoundError- 404 Not Found (resource doesn't exist)StrapiHTTPTimeoutError- 408 Request TimeoutStrapiHTTPInternalServerError- 500 Internal Server Error
Advanced Usage
Locale Support
articles = client.collection('articles')
# Fetch French content
response = articles.find(params={'locale': 'fr'})
# Fetch specific entry in Spanish
response = articles.find_one(1, params={'locale': 'es'})
Population
articles = client.collection('articles')
# Populate all relations
response = articles.find(params={'populate': '*'})
# Populate specific relations
response = articles.find(params={
'populate': ['author', 'categories', 'cover']
})
# Deep population
response = articles.find(params={
'populate': {
'author': {
'populate': ['avatar']
},
'categories': '*'
}
})
Field Selection
articles = client.collection('articles')
# Select specific fields only
response = articles.find(params={
'fields': ['title', 'description', 'publishedAt']
})
Requirements
- Python >= 3.10
- httpx >= 0.27.0
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details.
Links
Metadata
Release files for strapi-py 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| strapi_py-0.1.1.tar.gz | 17.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| strapi_py-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.6 kB
Release files / strapi_py-0.1.1.tar.gz
| Download URL | strapi_py-0.1.1.tar.gz |
|---|---|
| Size | 17.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
46c21ba374673187dbab530075759603f20fb6b5b065b77bac7387dc4b55da50
|
|
BLAKE2b-256 checksum How to use checksums |
6f6e0d894e78718fa0fa72e1ba0ae1a5fc8f363d36e7621b6d2ddad1b73f4f01
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|
Release files / strapi_py-0.1.1-py3-none-any.whl
| Download URL | strapi_py-0.1.1-py3-none-any.whl |
|---|---|
| Size | 25.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a17a93c7e823ae0b67a9553921984ce817097b1d6955c047c768c9534ca20821
|
|
BLAKE2b-256 checksum How to use checksums |
662a3ad0f6e8df7a55289fe6efcd0f5c14b0358c34d88ba213e0748bdace6bc8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|