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 Downloads CI Typed License: MIT

Documentation · PyPI · Releases · Changelog · 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 Time-limited links for private media, signed exactly as the API reference specifies and tested against the live API.
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

Or with uv or Poetry:

uv add dreep
poetry add dreep

Requires Python 3.10 or later. The latest release lists what changed. 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 for private media

Files in a signed folder can only be fetched with a link that carries an expiry time (exp) and an HMAC-SHA256 signature (sig) made with your project's URL Signing Secret. signed_url() builds that link locally, without a network call:

client = Dreep(signing_secret="...")  # or set DREEP_SIGNING_SECRET

link = client.signed_url(asset, expires_in=3600)
# https://cdn.dreep.cloud/…/<id>.png?exp=1774003600&sig=6b10e0…

thumb = client.signed_url(asset, expires_in=3600, transform={"width": 400, "format": "webp"})

The signature covers the asset and the expiry time, but not the transform. One signed link can therefore be resized by a frontend (&width=…) without being signed again. Once the link expires, or if anyone changes exp or sig, the API answers 401.

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",  # lowercase letters, numbers and underscores
    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.1

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.1
File Size Uploaded Provenance Actions
dreep-0.1.1.tar.gz 56.1 kB With provenance Details for dreep-0.1.1.tar.gz

Built distribution (wheel)

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

Total release size: 113.9 kB

Release files / dreep-0.1.1.tar.gz

Download URL dreep-0.1.1.tar.gz
Size 56.1 kB
Tags Source
SHA-256 checksum
How to use checksums
76fd8da5a98a5e132450230db8419c88248869a192fb0aac20adceea59746c0f
BLAKE2b-256 checksum
How to use checksums
8c64ada8139b401c24f6c06aa4e5ee0092959a2b1b283782aab4d181bacbcbfd
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.1-py3-none-any.whl

Download URL dreep-0.1.1-py3-none-any.whl
Size 57.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4f73197005d83c0ffdd863b5539685ac09f21c390f02889a807826459e2dc39e
BLAKE2b-256 checksum
How to use checksums
0ef6381211adaf1e2f3631dad935c1d417732e095dd88e576d0c0ff53c66d989
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.1

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.1.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.1 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