To customize and embed Vimeo videos from Python, call the Vimeo API v3.4 at https://api.vimeo.com with an OAuth 2.0 bearer token: use GET /me/videos and GET /videos/{id} to read data, PATCH /videos/{id} to change the name, description, and privacy, then render a responsive player.vimeo.com iframe. You can fire these calls with the requests library directly or with the official PyVimeo client.
Key takeaways
- Vimeo's old, unauthenticated "simple API" (the
vimeo.com/api/v2/...JSON/XML endpoints) is gone. Everything now runs through API v3.4 atapi.vimeo.comwith OAuth 2.0. - Authenticate every request with an
Authorization: bearer <token>header. Generate a personal access token in the Vimeo developer dashboard, or use the authorization-code flow to act on behalf of other users. - Read videos with
GET /me/videosandGET /videos/{id}; change metadata, privacy, and embed settings withPATCH /videos/{id}. - Use raw
requestsfor full control, or thePyVimeoclient for resumable (tus) uploads and a thinner wrapper. - Customize the player with embed settings (server-side) plus player URL parameters (
title,byline,portrait,color,dnt). - Wrap the iframe in a percentage-padding box for a responsive 16:9 embed.
- Domain-level privacy and hiding Vimeo branding require a paid Vimeo plan (Starter, Standard, Advanced, or Enterprise).
What changed since the old Vimeo API?
If you are porting a Python 2 script that hit vimeo.com/api/v2/... or relied on the old "simple API", it no longer works. The modern API:
- Lives at
https://api.vimeo.comand is versioned (pinversion=3.4). - Requires authentication on every call - there is no anonymous access to video data.
- Returns JSON only, with resources addressed by a
urisuch as/videos/123456789. - Uses standard HTTP verbs:
GETto read,POSTto create,PATCHto edit,PUTto add to a collection,DELETEto remove.
The patterns below are the same ones we use on Python integrations at MicroPyramid, and they mirror how you would talk to any modern REST API from Python - the same approach we take when sending SMS and MMS with Twilio.
How do I create a Vimeo app and access token?
- Sign in at the Vimeo developer site and create an app.
- Note the app's Client ID and Client Secret.
- For your own account, generate a personal access token and tick the scopes you need - common ones are
public,private,edit,upload, andvideo_files. - To act on behalf of other users, run the authorization-code (OAuth 2.0) flow instead of using a personal token.
Treat the token like a password: keep it in an environment variable, never in source control.
pip install requests PyVimeoHow do I authenticate requests with a bearer token?
Set up a requests.Session once, attach the bearer header, and pin the API version so a future Vimeo update cannot silently change your response shape.
import os
import requests
ACCESS_TOKEN = os.environ["VIMEO_ACCESS_TOKEN"]
API = "https://api.vimeo.com"
session = requests.Session()
session.headers.update({
"Authorization": f"bearer {ACCESS_TOKEN}",
"Content-Type": "application/json",
# Pin the API version you developed against.
"Accept": "application/vnd.vimeo.*+json;version=3.4",
})
resp = session.get(f"{API}/me", params={"fields": "name,uri"})
resp.raise_for_status()
print(resp.json()["name"])How do I list and fetch videos?
Use GET /me/videos for your own library (results are paginated) and GET /videos/{id} for a single video. Pass a fields parameter to keep responses small and fast.
# List your own videos (paginated).
resp = session.get(f"{API}/me/videos", params={"per_page": 25, "page": 1,
"fields": "uri,name,link,privacy"})
resp.raise_for_status()
for video in resp.json()["data"]:
print(video["uri"], video["name"])
# Fetch a single video by id.
video_id = 123456789
resp = session.get(f"{API}/videos/{video_id}",
params={"fields": "uri,name,privacy,embed,link"})
resp.raise_for_status()
print(resp.json())Should I use raw requests or the PyVimeo client?
The official PyVimeo client wraps the same endpoints, sets the auth header and base URL for you, and crucially handles resumable (tus) uploads - which you would otherwise have to implement by hand with requests. For read and edit calls, both are equally fine.
import vimeo
client = vimeo.VimeoClient(
token="your_personal_access_token",
key="your_client_id",
secret="your_client_secret",
)
# The client adds the Authorization header and the api.vimeo.com base URL.
me = client.get("/me", params={"fields": "name,uri"}).json()
print(me)
videos = client.get("/me/videos", params={"per_page": 25}).json()
for video in videos["data"]:
print(video["uri"], video["name"])requests vs PyVimeo at a glance
| Aspect | requests (raw) |
PyVimeo client |
|---|---|---|
| Install | pip install requests |
pip install PyVimeo |
| Auth header | You set Authorization: bearer ... |
Handled for you |
| Base URL | You prefix https://api.vimeo.com |
Handled for you |
| Read / edit (GET, PATCH) | Full control | Thin wrapper, same JSON |
| Resumable video upload | Manual tus implementation | client.upload(...) built in |
| Best for | Mixing Vimeo with other REST calls | Vimeo-only, especially uploads |
How do I customize a video's name, description, and privacy?
Send a single PATCH /videos/{id}. The privacy object controls who can view the video and who can embed it.
video_id = 123456789
payload = {
"name": "My customized video",
"description": "Updated from Python via the Vimeo API.",
"privacy": {
# view: anybody | nobody | contacts | password | unlisted | disable
"view": "anybody",
# embed: public | private | whitelist (domain-level privacy)
"embed": "whitelist",
},
}
resp = session.patch(f"{API}/videos/{video_id}", json=payload)
resp.raise_for_status()
print("Updated:", resp.json()["name"])Restricting embeds to specific domains
Setting privacy.embed to whitelist means the video only plays on domains you allow. Add each domain with a PUT (this is domain-level privacy, which needs a paid plan).
video_id = 123456789
domain = "example.com"
resp = session.put(f"{API}/videos/{video_id}/privacy/domains/{domain}")
resp.raise_for_status()
print("Whitelisted:", domain)How do I set a custom thumbnail?
Vimeo picks a frame by default, but you can upload your own image. It is a three-step dance: create a picture resource to get a one-time upload link, PUT the raw bytes to that link, then activate it.
video_id = 123456789
# 1. Create a picture resource; Vimeo returns a one-time upload link.
created = session.post(f"{API}/videos/{video_id}/pictures").json()
upload_link = created["link"]
picture_uri = created["uri"]
# 2. PUT the raw image bytes to that link (the upload URL needs no auth header).
with open("thumbnail.jpg", "rb") as fh:
requests.put(upload_link, data=fh.read()).raise_for_status()
# 3. Activate the uploaded thumbnail.
session.patch(f"{API}{picture_uri}", json={"active": True}).raise_for_status()
print("Thumbnail set")How do I customize the embedded player?
There are two layers:
- Embed settings stored on the video, changed with
PATCH /videos/{id}(or reused via a saved embed preset). - Player URL parameters appended to the
player.vimeo.comiframesrc.
Set server-side defaults like the accent color and whether the title, byline, and portrait show:
video_id = 123456789
payload = {
"embed": {
"color": "00adef",
"title": {"name": "hide", "owner": "hide", "portrait": "hide"},
"buttons": {"like": False, "watchlater": False, "share": True},
"logos": {"vimeo": False}, # hiding the Vimeo logo requires a paid plan
}
}
resp = session.patch(f"{API}/videos/{video_id}", json=payload)
resp.raise_for_status()Reusing embed presets
If you apply the same look to many videos, create an embed preset once in your account, list your presets, and attach one to a video with a PUT.
# List your saved embed presets.
presets = session.get(f"{API}/me/presets", params={"fields": "uri,name"}).json()
for preset in presets["data"]:
print(preset["uri"], preset["name"])
# Apply a preset to a video.
preset_id = 987654
session.put(f"{API}/videos/{video_id}/presets/{preset_id}").raise_for_status()How do I make a Vimeo embed responsive?
Wrap the iframe in a box whose top padding equals the aspect ratio (56.25% for 16:9) and absolutely position the iframe to fill it. Append player parameters to the src, and add the privacy hash (?h=...) for unlisted videos.
<!-- 16:9 responsive Vimeo embed. Append ?h=PRIVACY_HASH for unlisted videos. -->
<div style="padding:56.25% 0 0 0;position:relative;">
<iframe
src="https://player.vimeo.com/video/VIDEO_ID?h=PRIVACY_HASH&title=0&byline=0&portrait=0&color=00adef&dnt=1"
style="position:absolute;top:0;left:0;width:100%;height:100%;"
frameborder="0"
allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
allowfullscreen
title="My customized Vimeo video">
</iframe>
</div>Let oEmbed build the iframe for you
Do not want to assemble the markup yourself? Call the oEmbed endpoint with responsive=true and Vimeo returns ready-to-paste HTML.
# oEmbed needs no bearer token; it reads public/unlisted videos by URL.
params = {
"url": "https://vimeo.com/123456789",
"responsive": True,
"title": False,
"byline": False,
"color": "00adef",
}
oembed = requests.get("https://vimeo.com/api/oembed.json", params=params).json()
print(oembed["html"]) # ready-to-paste responsive <iframe>Player parameter reference
| Parameter | Effect | Notes |
|---|---|---|
color |
Accent color of the controls (hex) | e.g. 00adef |
title |
Show (1) or hide (0) the title |
Hiding needs a paid plan |
byline |
Show or hide the uploader name | Hiding needs a paid plan |
portrait |
Show or hide the owner avatar | Hiding needs a paid plan |
autoplay |
Start playback automatically | Pair with muted=1 for most browsers |
loop |
Restart when the video ends | |
muted |
Start muted | |
dnt |
Do Not Track: no cookies/analytics | Set dnt=1 for privacy |
h |
Privacy hash for unlisted videos | Required for unlisted embeds |
How do I upload or replace a video from Python?
For uploads, lean on PyVimeo - it implements the resumable tus protocol so large files survive flaky connections. replace swaps the source file while keeping the video's id, URL, and stats.
import vimeo
client = vimeo.VimeoClient(
token="your_personal_access_token",
key="your_client_id",
secret="your_client_secret",
)
# Resumable (tus) upload of a brand-new video.
video_uri = client.upload(
"intro.mp4",
data={"name": "Intro", "description": "Uploaded from Python"},
)
print("Uploaded:", video_uri)
# Replace the source file of an existing video (id and stats are kept).
client.replace(video_uri=video_uri, filename="intro-v2.mp4")Which features need a paid Vimeo plan?
You can upload and embed a public video on any plan, but the customization that makes embeds look "yours" sits behind paid tiers.
| Capability | Plan needed |
|---|---|
| Upload and public embed | Any plan, including Free |
| Hide title, byline, and portrait | Paid plan |
| Hide the Vimeo logo / unbranded player | Paid plan |
| Domain-level privacy (embed whitelist) | Paid plan |
| Advanced player customization | Higher paid plans |
Vimeo's paid tiers are currently named Starter, Standard, Advanced, and Enterprise. Exact feature gates shift over time, so confirm against your account before you build a flow around a paid-only setting.
Putting it together
A typical pipeline authenticates once, uploads with PyVimeo, patches metadata, privacy, and embed settings with requests, then stores the returned player.vimeo.com URL to drop into a responsive iframe. Add resp.raise_for_status() (or catch vimeo.exceptions.VimeoApiException) so API errors fail loudly, and respect rate limits by reading the X-RateLimit-Remaining response header.
If you are stitching Vimeo into a larger Python product - alongside scraping, messaging, or payments - the same disciplined approach applies. Our team has shipped integrations like this for 12+ years across 50+ projects; see how we apply it in web scraping with Beautiful Soup for another practical Python-plus-requests walkthrough.
Frequently Asked Questions
Is the old Vimeo simple API still available?
No. The unauthenticated "simple API" at vimeo.com/api/v2/... has been retired. All access now goes through API v3.4 at https://api.vimeo.com, and every request must carry an OAuth 2.0 bearer token. Any Python 2 script that relied on the old endpoints needs to be rewritten against the current API.
Do I need a paid Vimeo plan to use the API?
No, the API itself is available on every plan, including Free - you can authenticate, upload, read, and embed. A paid plan (Starter, Standard, Advanced, or Enterprise) is only required for specific customizations such as domain-level privacy, hiding the Vimeo logo, and removing the title, byline, and portrait from the player.
How do I make a Vimeo embed responsive?
Wrap the iframe in a container with top padding equal to the aspect ratio (56.25% for 16:9) and position the iframe absolutely to fill it. Alternatively, call the oEmbed endpoint with responsive=true and Vimeo returns responsive iframe HTML you can paste directly.
Should I use raw requests or the PyVimeo client?
Use requests when you want full control or are mixing Vimeo calls with other HTTP work. Use PyVimeo when you upload videos, because it implements the resumable tus upload protocol for you. For reads and edits the two are interchangeable - PyVimeo just sets the auth header and base URL automatically.
What scopes does my access token need?
Match scopes to the calls you make: public and private to read data, edit to change metadata and privacy, upload to add videos, and video_files to access source files. A read-only dashboard might need only public and private, while an upload pipeline needs upload and edit as well.
How do I restrict where my Vimeo video can be embedded?
Set the video's privacy.embed to whitelist with a PATCH /videos/{id}, then add each allowed domain with PUT /videos/{id}/privacy/domains/{domain}. The player will then refuse to load on any domain not on the list. Domain-level privacy requires a paid Vimeo plan.