Skip to main content
Dreep

Dreep Python SDK

The official Python client for Dreep, the universal media API.
Upload, store, transform and deliver images, videos and documents from Python.

PyPI Python CI Typed License: MIT

Documentation · API reference · Report a bug · Request a feature


Why this SDK

Sync and async Dreep and AsyncDreep share one API. Async runs on asyncio and trio.
Fully typed Ships py.typed. Transforms are a TypedDict with Literal values, so fit="squish" fails type-checking before it ships.
Signed URLs that work The SDK rounds sizes the same way the API does before it checks a signature, so a transformed signed link doesn't fail with a 401.
Large files without the memory upload_presigned() streams from disk straight to storage.
Safe by default Only reads are retried. Your API key goes only to the API, and never appears in logs or reprs.
Lightweight One dependency, httpx.

Installation

pip install dreep

Requires Python 3.10 or later. Create an API key on the API Keys page of the Dreep dashboard and make it available to your app:

export DREEP_API_KEY="drp_live_..."

Quickstart

from pathlib import Path
from dreep import Dreep

client = Dreep()  # reads DREEP_API_KEY

# 1. Upload: the original is stored once
asset = client.upload(Path("hero.jpg"), folder="marketing/2026")

# 2. Deliver any size or format from that one original. No network call.
thumbnail = client.url(asset, {"width": 400, "format": "webp"})
banner = client.url(asset, {"width": 1600, "height": 600, "fit": "cover"})

print(asset.url)   # https://cdn.dreep.cloud/api/v1/fetch/2f928a3f….jpg
print(thumbnail)   # …/fetch/2f928a3f….jpg?format=webp&width=400

The async client works the same way:

from dreep import AsyncDreep

async with AsyncDreep() as client:
    asset = await client.upload(Path("hero.jpg"))
    async for item in client.list_all_media(folder="marketing"):
        print(item.filename, item.size_bytes)

How it works

One original, many variants

Dreep stores each file once. Every resized, cropped or converted version is produced on request from a URL, then cached on the CDN. You don't have to transform files on upload: keep the original and ask for what you need when you deliver it.

flowchart LR
    A[Your app] -- "client.upload(Path('hero.jpg'))" --> B[(Dreep storage<br/>hero.jpg, stored once)]
    B --> C{{"CDN fetch URL<br/>…/fetch/&lt;id&gt;"}}
    C -- "?width=400&format=webp" --> D[400px WebP]
    C -- "?width=1200&format=avif" --> E[1200px AVIF]
    C -- "?format=txt" --> F[OCR text]
    C -- "?format=pdf" --> G[PDF conversion]

client.url(asset, {...}) builds those URLs locally without any network call, so it's safe to call while rendering a page.

What happens when you call a method

sequenceDiagram
    participant You as Your code
    participant SDK as Dreep client
    participant API as api.dreep.cloud
    You->>SDK: client.list_media(folder="avatars")
    SDK->>SDK: validate arguments, build the request<br/>(folder_id → folderId, Bearer auth)
    SDK->>API: GET /api/v1/media?folder=avatars
    alt 429 or 5xx (reads only)
        API-->>SDK: 503
        SDK->>SDK: wait (exponential backoff, honours Retry-After)
        SDK->>API: GET again (up to 2 retries)
    end
    API-->>SDK: 200 {"data": {...}}
    SDK-->>You: MediaPage (typed, sizes as int)
    Note over SDK,You: a 4xx raises a typed error instead,<br/>e.g. DreepNotFoundError
  • Arguments are checked before anything is sent. Passing both folder= and folder_id=, or misspelling a transform key such as "widht", raises immediately instead of quietly returning the original.
  • Responses come back as typed, frozen dataclasses with snake_case attributes. Numbers the API sends as strings (sizeBytes: "245000") are converted to int. Any field the SDK doesn't model yet is still available in .raw.
  • Only reads are retried. Retrying an upload could store it twice and bill it twice.

Large files go straight to storage

upload() sends the file through the API in one request, which suits files up to a few hundred MB. upload_presigned() sends the bytes directly to storage instead, streaming them from disk:

sequenceDiagram
    participant You as Your server
    participant API as Dreep API
    participant S as Storage
    You->>API: POST /upload/presign (size, type, folder)
    API-->>You: id + short-lived uploadUrl
    You->>S: PUT bytes, streamed in 1 MB chunks (no API key sent)
    You->>API: POST /upload/{id}/confirm
    API-->>You: 409 while the bytes settle, so the SDK retries
    API-->>You: Asset (status "ready")
# Everything on your server
asset = client.upload_presigned(Path("video.mp4"), folder="uploads/2026")

# Upload from a browser: presign on the server, the browser PUTs the bytes, confirm on the server
upload = client.presign_upload(content_type="video/mp4", size_bytes=84_000_000)
#   ... send upload.id and upload.upload_url to the browser ...
asset = client.confirm_upload(upload.id)

Signed URLs, done the way the API checks them

Files in a signed folder can only be fetched with a link that carries an expiry time (exp) and an HMAC signature (sig). When that link also includes a transform, the API rounds some values to fixed sizes before it checks the signature:

You ask for The API checks against
width=803 828 (rounded up to the next fixed width)
quality=82 85 (rounded to the nearest fixed quality)
dpr=1.5 2

If you sign 803 by hand, the API checks 828 and returns 401, with nothing in the URL to tell you why. client.signed_url() does the same rounding before signing, so the link works:

client = Dreep(signing_secret="...")  # or set DREEP_SIGNING_SECRET
link = client.signed_url(asset, expires_in=3600, transform={"width": 803})

Anyone holding a signed link can fetch the file until it expires, without an API key, so keep expiry times short.

Recipes

Folders and access control
# Uploads create folders as needed. Create one yourself only to set its access control.
client.create_folder(path="courses/premium", access_control_type="signed")

# Upload into a nested path, creating any missing folders
asset = client.upload(Path("lesson.mp4"), key="courses/premium/lesson-1.mp4")

# Every folder in the project, each with its full path
for folder in client.list_folders():
    print(folder.path, folder.access_control_type)
Listing and deleting
page = client.list_media(folder="courses", page=1, limit=50)
print(f"{len(page)} of {page.pagination.total}")

# Every asset across every page, one request per page, fetched lazily
for item in client.list_all_media(folder="courses", recursive=True):
    print(item.filename, item.size_bytes)

client.delete_media(asset.id)  # also removes its cached transforms
Presets: name a transform once
client.create_preset(
    key="thumb",
    name="Thumbnail",
    transform={"width": 200, "height": 200, "fit": "cover", "format": "webp"},
)
client.url(asset, {"preset": "thumb"})  # change the preset later and every URL using it follows
Background removal
cutout = client.remove_background(Path("product.jpg"), folder="products")

# One cutout can be served on any background colour, and only the removal is billed
client.url(cutout, {"bg": "ffffff"})
client.url(cutout, {"bg": "000000"})
Text extraction (OCR)
result = client.extract_text(Path("invoice.pdf"), include_blocks=True)
print(result.text)
for block in result.blocks:
    print(block)  # line-level bounding boxes

# For an asset already in Dreep: no re-upload, and cached after the first read
print(client.fetch_text(asset))
Document conversion
pdf = client.convert_document(Path("contract.docx"), format="pdf", folder="contracts")
slide = client.convert_document(Path("deck.pdf"), format="png", page=2)

if not pdf.is_ready:  # very long documents keep converting in the background
    print("Still converting; its URL answers 202 until it's ready")
Usage
usage = client.get_usage()
print(f"{usage.storage_bytes / 1024**3:.2f} GB across {usage.image_count + usage.file_count} files")

Runnable scripts are in examples/.

API reference

Method What it does Endpoint
upload(file, …) Upload in one multipart request, optionally transforming it first POST /upload
presign_upload(…) Start a direct-to-storage upload POST /upload/presign
confirm_upload(id, …) Finalise a presigned upload POST /upload/{id}/confirm
upload_presigned(file, …) Presign, stream the PUT, and confirm in one call all three
url(asset, transform) Delivery URL with a transform none
signed_url(asset, expires_in=…) Time-limited URL for a signed folder none
list_media(…) / list_all_media(…) One page / every page, fetched lazily GET /media
delete_media(id) Delete an asset and its cached transforms DELETE /media/{id}
create_folder(…) / list_folders(…) Folders and access control /folders
create_preset(…) / list_presets() / delete_preset(id) Named transforms /presets
remove_background(file, …) Cut out the subject, keeping the alpha channel POST /bg-remove
extract_text(file, …) OCR an image or PDF POST /ocr
fetch_text(asset) OCR text of an asset already in Dreep GET fetch/{id}?format=txt
convert_document(file, format=…) Word ↔ PDF, spreadsheets, PDF page to image POST /convert
get_usage() Storage and object counts GET /usage

file can be bytes, a pathlib.Path, or a binary file object. A plain str is rejected on purpose, because it could be either a path or the file's content.

Transform parameters

Key Applies to Values
format image, video, document webp, avif, jpeg, png, mp4, hls, pdf, docx, txt (OCR), …
width, height image, video pixels, max 4000
fit image, video cover, contain, fill, inside, outside
quality image, video 1–100
gravity image center, north, …, attention, entropy
crop image "left,top,width,height" or a 4-tuple
dpr image 1–3
rotate image 90, 180, 270
blur image 0.3–100
bg image hex without #, e.g. "ffffff"
radius image pixels, or "max" for a circle
trim_start, trim_end video seconds
video_codec, fps video h264, vp8, vp9, hevc; frames per second
page PDF → image 1-based page number
preset any a saved preset key

Errors

Every failure raises a subclass of DreepError. Catch DreepError for everything, or a specific subclass for the cases you want to handle differently:

from dreep import DreepError, DreepLimitError, DreepNotFoundError

try:
    client.upload(Path("hero.jpg"))
except DreepLimitError as error:          # 402: the plan ran out
    print(f"{error.feature_key}: {error.used} of {error.limit}")
except DreepNotFoundError:                # 404
    ...
except DreepError as error:               # everything else
    print(error.status, error.message, error.request_id)
Exception When
DreepValidationError 400 / 413: invalid parameters or file too large (validation_errors)
DreepAuthError 401 / 403: missing, malformed or revoked key
DreepLimitError 402: plan limit reached (feature_key, used, limit)
DreepNotFoundError 404: unknown asset, folder, preset or upload
DreepConflictError 409: confirming before the bytes landed
DreepRateLimitError 429 (retry_after)
DreepServerError 5xx
DreepProcessingError 202 where a finished result was needed
DreepConnectionError / DreepTimeoutError No response arrived

Retries and timeouts

  • Retries. GET requests are retried twice on 429, 5xx and connection failures, using exponential backoff with jitter and honouring Retry-After. Writes are never retried automatically. Set max_retries=0 to turn retries off.
  • Timeouts. JSON calls time out after 30 seconds. Uploads have no default limit, because a large file can take a long time; pass timeout= on the call to bound one: client.upload(path, timeout=120).

Configuration

Argument Environment variable Default
api_key DREEP_API_KEY required
signing_secret DREEP_SIGNING_SECRET none (needed only for signed_url)
timeout 30.0 seconds
max_retries 2
http_client an internal httpx client; pass your own for proxies or custom transports

The SDK logs retries to the dreep logger.

Contributing

Contributions are welcome. Every pull request is linked to an issue and reviewed by the maintainers. CONTRIBUTING.md walks through the whole process, from picking an issue to getting approval.

License

MIT © Dreep

Metadata

Release files for dreep 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dreep 0.1.0
File Size Uploaded Provenance Actions
dreep-0.1.0.tar.gz 56.9 kB With provenance Details for dreep-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for dreep 0.1.0
File Interpreter ABI Platform Actions
dreep-0.1.0-py3-none-any.whl Python 3 none any Details for dreep-0.1.0-py3-none-any.whl

Total release size: 115.6 kB

Release files / dreep-0.1.0.tar.gz

Download URL dreep-0.1.0.tar.gz
Size 56.9 kB
Tags Source
SHA-256 checksum
How to use checksums
21b134d3dbb4d4b5d7886f9d87137a62582d3d21632bf4cab1b8177eb86b1918
BLAKE2b-256 checksum
How to use checksums
1bc90288d4518dc64f599d5c69bb89129f14a9734e61f7a566a5e7893896cff6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Release files / dreep-0.1.0-py3-none-any.whl

Download URL dreep-0.1.0-py3-none-any.whl
Size 58.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7737a1a68f89539a445db2e3b4a6c843037fe3eeeef771de665fe0efd6b878a3
BLAKE2b-256 checksum
How to use checksums
5cb99f811219e1856ef2099d432bcc2f2980fe8264614d5be52141045509e9df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

Security for dreep 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for dreep-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page