Unofficial Python client for the GoPro cloud / Quik HTTP API at api.gopro.com: search your library and fetch download metadata (CDN URLs, filenames, variants). Built with Pydantic models, plus sync (requests) and async (aiohttp) clients and a small gopro-api CLI.
This project is not affiliated with or endorsed by GoPro.
GoProAPI— synchronous client (requests),withcontext managerAsyncGoProAPI— async client (aiohttp),async withcontext manager- Pydantic request/response types in
gopro_api.api.models - CLI —
gopro-api search,gopro-api info,gopro-api pull GP_ACCESS_TOKENfrom environment /.env(browser cookie value)
- Python 3.10+
GP_ACCESS_TOKEN— see Configuration
From the repository root:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
pip install -e .From a local wheel (name matches your build):
pip install ./dist/gopro_api-*-py3-none-any.whlPublished package on PyPI (distribution name gopro-api, import gopro_api):
pip install gopro-apiAfter install, gopro-api is on your PATH:
gopro-api --help
gopro-api --version
gopro-api search --start 2026-03-01 --end 2026-03-03 --per-page 30
gopro-api search --start 2026-03-01 --end 2026-03-03 --all-pages
gopro-api search --start 2026-03-01 --end 2026-03-03 --json
gopro-api info MEDIA_ID
gopro-api info MEDIA_ID --json
gopro-api pull MEDIA_ID ./downloads
gopro-api pull MEDIA_ID ./downloads --height 1080
gopro-api pull MEDIA_ID ./downloads --width 1920 --height 1080| Command | Purpose |
|---|---|
search |
List media in a capture range. Default: a # _pages summary line, a tab-separated header (id, type, captured_at, filename, …; not gopro_user_id / source_gumi / source_mgumi), then one row per item (other API fields in an extra JSON column). --json: full API-shaped response; with --all-pages, a JSON array of every page. |
info |
Show download metadata for one media id (filename + file lines with size and URL), or --json for the full payload. |
pull |
Download asset(s) for a media id into destination (directory; created if missing). Videos (.mp4 extension, case-insensitive): one variations entry — tallest by default, or closest to --height / --width (sum of squared pixel deltas; ties broken by larger resolution). Photos: uses files (one request per file). |
Global --timeout (seconds, default 60) applies to API calls and to pull CDN downloads (requests.get).
Run without an installed script:
python -m gopro_api.cli search --start 2026-03-01 --end 2026-03-02
python -m gopro_api.cli info MEDIA_ID
python -m gopro_api.cli pull MEDIA_ID ./out
python -m gopro_api.cli pull MEDIA_ID ./out --height 720gopro_api.config reads settings from the environment and from a .env file in the current working directory via pydantic-settings. The only required setting is GP_ACCESS_TOKEN.
Example .env:
GP_ACCESS_TOKEN=your_token_hereThe clients send it as a cookie: gp_access_token=<value>. Put only the token string in GP_ACCESS_TOKEN (not the gp_access_token= prefix).
You can override the token in code: GoProAPI(access_token="...") or AsyncGoProAPI(access_token="...").
Sign in to the GoPro web app (e.g. gopro.com media / Quik). The site sets a cookie gp_access_token.
Chrome / Edge / Brave
- Open the site while logged in.
- F12 → Application → Cookies → choose the origin (often
https://quik.gopro.comor another*.gopro.comhost). - Copy the Value of
gp_access_token.
Firefox
F12 → Storage → Cookies → same idea.
Network panel (Chromium)
- Network → trigger requests to
api.gopro.com. - Pick a request → Headers → Cookie.
- Copy the value after
gp_access_token=up to the next;(or end of string).
Notes
- If the cookie is HttpOnly, use the Network method.
- Tokens expire; refresh from the browser if you get 401.
- Treat the token like a password.
Security: Do not commit .env or tokens. Keep .env in .gitignore.
import asyncio
from datetime import datetime
from gopro_api.api import AsyncGoProAPI
from gopro_api.api.models import CapturedRange, GoProMediaSearchParams
async def main() -> None:
params = GoProMediaSearchParams(
captured_range=CapturedRange(
start=datetime.fromisoformat("2026-03-01"),
end=datetime.fromisoformat("2026-03-02"),
),
per_page=50,
page=1,
)
async with AsyncGoProAPI() as api:
search = await api.search(params)
for item in search.embedded.media:
meta = await api.download(item.id)
print(meta.filename, len(meta.embedded.files), "files")
if __name__ == "__main__":
asyncio.run(main())from datetime import datetime
from gopro_api.api import GoProAPI
from gopro_api.api.models import CapturedRange, GoProMediaSearchParams
def main() -> None:
params = GoProMediaSearchParams(
captured_range=CapturedRange(
start=datetime.fromisoformat("2026-03-01"),
end=datetime.fromisoformat("2026-03-02"),
),
per_page=50,
page=1,
)
with GoProAPI() as api:
search = api.search(params)
for item in search.embedded.media:
meta = api.download(item.id)
print(meta.filename, len(meta.embedded.files), "files")
if __name__ == "__main__":
main()- Requests:
GoProMediaSearchParams,CapturedRange, etc. ingopro_api.api.models. - Responses: search and download JSON shapes (including
_embedded/_pagesaliases).
List fields in search params are serialized to comma-separated strings when you call model_dump() (used by the HTTP clients).
| Path | Role |
|---|---|
gopro_api/api/gopro.py |
GoProAPI — sync search, download |
gopro_api/api/async_gopro.py |
AsyncGoProAPI — async search, download |
gopro_api/api/models.py |
Pydantic request/response models |
gopro_api/api/__init__.py |
Re-exports GoProAPI, AsyncGoProAPI |
gopro_api/config.py |
pydantic-settings Settings, GP_ACCESS_TOKEN |
gopro_api/cli/ |
gopro-api CLI |
setup.py |
Package metadata, dependencies, console entry point |
.github/workflows/release.yml:
- Push to
main— builds wheel + source.zip, uploads workflow artifacts. - Push tag
v*(e.g.v0.0.5) — attaches the same files to a GitHub Release.
GoPro, Quik, and related marks are trademarks of their respective owners.