Customize and Embed Vimeo Videos Using Python Requests

Blog / Python Β· December 17, 2015 Β· Updated June 10, 2026 Β· 9 min read
Customize and Embed Vimeo Videos Using Python Requests

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 at api.vimeo.com with 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/videos and GET /videos/{id}; change metadata, privacy, and embed settings with PATCH /videos/{id}.
  • Use raw requests for full control, or the PyVimeo client 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.com and is versioned (pin version=3.4).
  • Requires authentication on every call - there is no anonymous access to video data.
  • Returns JSON only, with resources addressed by a uri such as /videos/123456789.
  • Uses standard HTTP verbs: GET to read, POST to create, PATCH to edit, PUT to add to a collection, DELETE to 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?

  1. Sign in at the Vimeo developer site and create an app.
  2. Note the app's Client ID and Client Secret.
  3. For your own account, generate a personal access token and tick the scopes you need - common ones are public, private, edit, upload, and video_files.
  4. 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 PyVimeo

How 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:

  1. Embed settings stored on the video, changed with PATCH /videos/{id} (or reused via a saved embed preset).
  2. Player URL parameters appended to the player.vimeo.com iframe src.

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.

Share this article