# How to Integrate Replit with Vimeo

- Tool: Replit
- Difficulty: Intermediate
- Time required: 45 minutes
- Last updated: March 2026

## TL;DR

To integrate Replit with Vimeo, register an OAuth 2.0 app in the Vimeo developer portal, store your access token in Replit Secrets, and call the Vimeo API from a server-side Node.js or Python backend. You can upload videos, manage metadata, retrieve embed codes, and customize the Vimeo player. Deploy your Replit server on Autoscale for video management tools and media-rich applications.

## Why Integrate Vimeo with Replit?

Vimeo is the premier choice for developers and businesses that need professional video hosting with fine-grained privacy controls, customizable players, and a clean ad-free viewing experience. Unlike YouTube, Vimeo lets you restrict who can view or embed your videos, remove the Vimeo branding on paid plans, and control the player's colors and interface. Integrating Vimeo with Replit lets you automate video management workflows, build custom video galleries, and embed Vimeo content in applications you're building.

The Vimeo API gives you programmatic control over your entire video library: upload new videos, update titles and descriptions, set privacy settings, retrieve embed codes, and even generate video thumbnails. For content-heavy applications — online courses, portfolio sites, media archives, internal training platforms — having a Replit backend that communicates with Vimeo means you can automate what would otherwise be tedious manual work through the Vimeo dashboard.

Vimeo uses OAuth 2.0 for authentication. For personal or internal integrations, you can use a long-lived personal access token tied to your Vimeo account. For applications where multiple users connect their own Vimeo accounts, you implement the three-legged OAuth flow. Video uploads use the tus protocol (a resumable upload standard), which is important for handling large video files reliably — if an upload is interrupted, tus resumes from where it stopped rather than starting over.

## Before you start

- A Vimeo account (Basic, Plus, Pro, or Business — API upload access requires a paid plan for most use cases)
- A Vimeo developer app created at developer.vimeo.com with a personal access token generated
- A Replit account with a Node.js or Python Repl created
- Node.js with axios and express installed, or Python with requests and flask installed in your Repl
- Basic understanding of OAuth 2.0 and REST APIs

## Step-by-step guide

### 1. Create a Vimeo Developer App and Generate an Access Token

Go to developer.vimeo.com and sign in with your Vimeo account. Click 'Create App' in the top navigation. Fill in your app name (e.g., 'Replit Integration'), app description, and your app's URL (you can use your Replit URL or a placeholder like https://example.com for development). Under 'App Type', select 'Personal' if this is for your own account, or 'Web App' if you plan to support multiple users connecting their accounts via OAuth redirect. After creating the app, navigate to the Authentication tab. In the 'Personal Access Tokens' section, click 'Generate' to create a token. Select the scopes your integration needs: check 'Public' and 'Private' to access all your videos, 'Upload' if you plan to upload videos programmatically, 'Edit' to update video metadata, and 'Video Files' to access download links. Copy the generated access token immediately — it will only be shown once in full. Store it safely before closing the modal. Also note your App ID and Secret from the app page, which you'll need if you implement the full OAuth flow for multi-user support later.

**Expected result:** You have a Vimeo developer app with a personal access token that has the scopes needed for your integration.

### 2. Store Your Vimeo Access Token in Replit Secrets

Open your Repl and click the lock icon (🔒) in the left sidebar to open the Secrets panel. Add your Vimeo personal access token as a secret named VIMEO_ACCESS_TOKEN. If you're implementing multi-user OAuth later, also add VIMEO_CLIENT_ID and VIMEO_CLIENT_SECRET as separate secrets. Replit Secrets are encrypted at rest and injected as environment variables when your Repl starts — they're never visible in your source code, your Git history, or to other Replit users. In your Node.js code you access the token via process.env.VIMEO_ACCESS_TOKEN; in Python via os.environ['VIMEO_ACCESS_TOKEN']. After adding the secret, restart your Repl to ensure the environment variable loads. Replit's Secret Scanner continuously monitors your code files for accidentally hardcoded credentials — if you accidentally paste an API key into a code file, it will be flagged immediately.

**Expected result:** VIMEO_ACCESS_TOKEN is visible in the Replit Secrets panel and accessible as an environment variable in your running Repl.

### 3. Build the Node.js Vimeo API Server

Create an Express server that acts as a secure proxy for Vimeo API calls. All requests to the Vimeo API are made server-side — your frontend never directly calls Vimeo or sees the access token. The Vimeo API base URL is https://api.vimeo.com and all requests need an Authorization header with 'bearer YOUR_TOKEN'. Install required packages by running npm install express axios in the Replit Shell. Key Vimeo API endpoints include /me/videos (list your videos), /videos/{video_id} (get video details), /me/videos with POST (initiate upload), and PATCH /videos/{video_id} (update metadata). The GET /videos/{video_id} response includes an embed object with HTML iframe code and customizable query parameters. You can customize the embedded player by appending parameters to the Vimeo video URL: autoplay=1, background=1 (for background video loops), loop=1, muted=1, color=hexcode (player color), title=0 (hide title), byline=0 (hide author), portrait=0 (hide avatar).

```
const express = require('express');
const axios = require('axios');

const app = express();
app.use(express.json());

const VIMEO_TOKEN = process.env.VIMEO_ACCESS_TOKEN;
const VIMEO_BASE_URL = 'https://api.vimeo.com';

const vimeoClient = axios.create({
  baseURL: VIMEO_BASE_URL,
  headers: {
    'Authorization': `bearer ${VIMEO_TOKEN}`,
    'Content-Type': 'application/json',
    'Accept': 'application/vnd.vimeo.*+json;version=3.4'
  },
  timeout: 15000
});

// List videos in account
app.get('/api/videos', async (req, res) => {
  const { per_page = 10, page = 1, sort = 'date' } = req.query;
  try {
    const response = await vimeoClient.get('/me/videos', {
      params: { per_page, page, sort, fields: 'uri,name,description,created_time,duration,privacy,embed,pictures' }
    });
    const videos = response.data.data.map(v => ({
      id: v.uri.replace('/videos/', ''),
      name: v.name,
      description: v.description,
      duration: v.duration,
      created_time: v.created_time,
      privacy: v.privacy.view,
      thumbnail: v.pictures?.sizes?.[3]?.link || null,
      embed_html: v.embed?.html || null
    }));
    res.json({ videos, total: response.data.total, page: response.data.page });
  } catch (error) {
    console.error('Vimeo API error:', error.response?.data || error.message);
    res.status(error.response?.status || 500).json({ error: 'Failed to fetch videos' });
  }
});

// Get a single video with customized embed code
app.get('/api/videos/:id', async (req, res) => {
  const { color = '00adef', autoplay = 0, loop = 0, title = 1 } = req.query;
  try {
    const response = await vimeoClient.get(`/videos/${req.params.id}`);
    const video = response.data;
    const embedUrl = `https://player.vimeo.com/video/${req.params.id}?color=${color}&autoplay=${autoplay}&loop=${loop}&title=${title}&byline=0&portrait=0`;
    const embedHtml = `<iframe src="${embedUrl}" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>`;
    res.json({
      id: req.params.id,
      name: video.name,
      description: video.description,
      duration: video.duration,
      privacy: video.privacy.view,
      embed_html: embedHtml,
      thumbnail: video.pictures?.sizes?.[3]?.link || null
    });
  } catch (error) {
    console.error('Vimeo API error:', error.response?.data || error.message);
    res.status(error.response?.status || 500).json({ error: 'Failed to fetch video' });
  }
});

// Update video privacy settings
app.patch('/api/videos/:id/privacy', async (req, res) => {
  const { view, password, embed } = req.body;
  const privacySettings = { view };
  if (view === 'password' && password) privacySettings.password = password;
  if (embed) privacySettings.embed = embed;
  try {
    const response = await vimeoClient.patch(`/videos/${req.params.id}`, {
      privacy: privacySettings
    });
    res.json({ id: req.params.id, privacy: response.data.privacy });
  } catch (error) {
    console.error('Vimeo API error:', error.response?.data || error.message);
    res.status(error.response?.status || 500).json({ error: 'Failed to update privacy' });
  }
});

app.listen(3000, '0.0.0.0', () => console.log('Vimeo API server running on port 3000'));
```

**Expected result:** Your Express server starts and GET /api/videos returns a JSON array of your Vimeo videos with metadata and embed codes.

### 4. Build the Python Flask Alternative

For Python-based Replit projects, the same Vimeo integration can be implemented with Flask and the requests library. Python is well-suited for video metadata processing and batch operations on large video libraries. Install the dependencies by running pip install flask requests in the Replit Shell. The Python implementation follows the same pattern: all Vimeo API calls go through the server-side code, the access token is read from environment variables, and the response data is cleaned up and returned as JSON. A useful Python-specific addition is a helper function for building customized embed URLs — this makes it easy to generate player iframes with different configuration options for different contexts (public gallery versus private team page).

```
import os
import requests
from flask import Flask, jsonify, request

app = Flask(__name__)

VIMEO_TOKEN = os.environ['VIMEO_ACCESS_TOKEN']
VIMEO_BASE_URL = 'https://api.vimeo.com'

def vimeo_headers():
    return {
        'Authorization': f'bearer {VIMEO_TOKEN}',
        'Content-Type': 'application/json',
        'Accept': 'application/vnd.vimeo.*+json;version=3.4'
    }

def build_embed_url(video_id, color='00adef', autoplay=0, loop=0, title=1):
    params = f'color={color}&autoplay={autoplay}&loop={loop}&title={title}&byline=0&portrait=0'
    return f'https://player.vimeo.com/video/{video_id}?{params}'

def build_embed_html(video_id, **kwargs):
    url = build_embed_url(video_id, **kwargs)
    return f'<iframe src="{url}" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>'

@app.route('/api/videos')
def list_videos():
    per_page = request.args.get('per_page', 10)
    page = request.args.get('page', 1)
    try:
        response = requests.get(
            f'{VIMEO_BASE_URL}/me/videos',
            headers=vimeo_headers(),
            params={'per_page': per_page, 'page': page, 'sort': 'date',
                    'fields': 'uri,name,description,created_time,duration,privacy,embed,pictures'},
            timeout=15
        )
        response.raise_for_status()
        data = response.json()
        videos = [{
            'id': v['uri'].replace('/videos/', ''),
            'name': v['name'],
            'description': v.get('description', ''),
            'duration': v['duration'],
            'privacy': v['privacy']['view'],
            'thumbnail': v.get('pictures', {}).get('sizes', [{}] * 4)[3].get('link'),
            'embed_html': v.get('embed', {}).get('html')
        } for v in data['data']]
        return jsonify({'videos': videos, 'total': data['total']})
    except requests.RequestException as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/videos/<video_id>')
def get_video(video_id):
    color = request.args.get('color', '00adef')
    try:
        response = requests.get(
            f'{VIMEO_BASE_URL}/videos/{video_id}',
            headers=vimeo_headers(),
            timeout=15
        )
        response.raise_for_status()
        video = response.json()
        return jsonify({
            'id': video_id,
            'name': video['name'],
            'description': video.get('description', ''),
            'duration': video['duration'],
            'privacy': video['privacy']['view'],
            'embed_html': build_embed_html(video_id, color=color)
        })
    except requests.RequestException as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)
```

**Expected result:** Your Flask server starts and /api/videos returns your Vimeo video list with metadata and embed codes as JSON.

### 5. Implement Video Upload Using the Tus Protocol

Uploading videos to Vimeo from a Replit backend requires using the tus resumable upload protocol rather than a simple multipart form POST. Vimeo requires this approach to reliably handle large video files. The process has three stages: first, create an upload slot by POSTing to /me/videos with video metadata and the file size; this returns an upload link and a Vimeo video URI. Second, use the tus protocol to stream the actual video bytes to the upload link. Third, verify the upload completed by checking the video's upload status. Install the tus-py-client (for Python) or tus-js-client (for Node.js) to handle the tus protocol. For simpler cases where you want to avoid the tus dependency, Vimeo also supports a streaming approach using the upload_approach: 'pull' parameter where you provide a URL and Vimeo fetches the video itself — useful when the video file is already hosted somewhere accessible.

```
// Node.js: Simple video upload using Vimeo's 'pull' approach
// (Vimeo fetches the video from a public URL — simpler than tus)
async function uploadVideoFromUrl(videoUrl, title, description = '') {
  // Step 1: Create upload ticket with 'pull' approach
  const createResponse = await vimeoClient.post('/me/videos', {
    upload: {
      approach: 'pull',
      link: videoUrl
    },
    name: title,
    description: description,
    privacy: {
      view: 'nobody' // Start as private
    }
  });

  const videoUri = createResponse.data.uri;
  const videoId = videoUri.replace('/videos/', '');
  console.log(`Upload initiated. Vimeo video ID: ${videoId}`);

  // Step 2: Poll until upload is complete
  let status = 'in_progress';
  while (status === 'in_progress') {
    await new Promise(resolve => setTimeout(resolve, 5000)); // Wait 5 seconds
    const statusResponse = await vimeoClient.get(videoUri, {
      params: { fields: 'upload,transcode' }
    });
    status = statusResponse.data.upload?.status || 'error';
    console.log(`Upload status: ${status}`);
  }

  if (status === 'complete') {
    console.log(`Video uploaded successfully: https://vimeo.com/${videoId}`);
    return videoId;
  } else {
    throw new Error(`Upload failed with status: ${status}`);
  }
}

// Add upload endpoint to Express server
app.post('/api/videos/upload-from-url', async (req, res) => {
  const { url, title, description } = req.body;
  if (!url || !title) {
    return res.status(400).json({ error: 'url and title are required' });
  }
  try {
    const videoId = await uploadVideoFromUrl(url, title, description);
    res.json({ success: true, video_id: videoId, vimeo_url: `https://vimeo.com/${videoId}` });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
```

**Expected result:** POST /api/videos/upload-from-url with a publicly accessible video URL initiates a Vimeo upload and returns the new video ID once complete.

## Best practices

- Always store your Vimeo access token in Replit Secrets (lock icon 🔒) — never include it in client-side code or commit it to your repository
- Use the fields parameter when listing videos to request only the data you need — this reduces response size and improves API performance for large video libraries
- Proxy all Vimeo API calls through your Replit server — never call the Vimeo API directly from browser JavaScript, which would expose your access token
- Cache video metadata and embed codes (which rarely change) for at least 1 hour to reduce API call frequency and stay within Vimeo's rate limits
- Start newly uploaded videos as private (privacy.view: 'nobody') and only make them public after you've verified the title, description, and thumbnail are correct
- Use Replit Autoscale deployment for video management backends and player proxy APIs — the on-demand scaling handles variable traffic without maintaining an always-on server
- Implement proper error handling for Vimeo's 429 rate limit responses with exponential backoff, especially in batch video update operations
- For multi-user applications where users connect their own Vimeo accounts, implement the full OAuth 2.0 authorization code flow and never share a single access token across multiple users

## Use cases

### Video Gallery with Custom Player

Build a backend that fetches a user's Vimeo video library and returns metadata plus embed codes for display in a custom gallery page. Your Replit server queries the Vimeo API for videos in a specific folder, retrieves the embed HTML for each, and passes customized player parameters to control autoplay, color, and privacy settings.

Prompt example:

```
Build a video gallery endpoint that fetches all videos from a specific Vimeo folder, retrieves the embed code for each with custom player settings (no title, no byline, custom color), and returns a JSON array with the video title, description, thumbnail URL, and iframe embed code.
```

### Automated Video Upload Pipeline

Create a Replit backend that accepts video file uploads from your own users, uploads them to Vimeo using the tus protocol, sets the title and description, configures privacy settings, and returns the Vimeo video URL. This removes the need for users to manually log in to Vimeo and upload files themselves.

Prompt example:

```
Build an upload endpoint that accepts a video file via multipart form data, initiates a tus upload to the Vimeo API, tracks the upload progress, and returns the final Vimeo video URL along with the embed code once the upload is complete and ready for playback.
```

### Video Privacy Management Dashboard

Build an internal tool that lists all videos in a Vimeo account, shows their current privacy settings, and lets you update them in bulk. For example, switch a batch of training videos from private to password-protected, or restrict embedding to specific domains. Your Replit backend handles all the API calls while a simple frontend provides the management UI.

Prompt example:

```
Build an admin endpoint that lists all videos in a Vimeo account with their current privacy settings, and provides a PATCH endpoint to update a video's privacy setting to 'password', 'nobody', 'anybody', or 'disable', returning the updated video object after the change.
```

## Troubleshooting

### API returns 401 Unauthorized with 'Your access token does not have the correct scope'

Cause: The personal access token you generated doesn't have the required scope for the endpoint you're calling. For example, trying to upload without the 'upload' scope, or accessing private videos without the 'private' scope.

Solution: Go to developer.vimeo.com, open your app's Authentication tab, and generate a new access token with all the scopes your integration needs (public, private, edit, upload, video_files). Update the VIMEO_ACCESS_TOKEN secret in Replit's Secrets panel (lock icon 🔒) with the new token and restart your Repl.

### Player embed shows an error 'This video does not exist' or shows a black screen

Cause: The video privacy setting is set to 'nobody' or 'password', or the embed privacy is set to restrict embedding to specific domains. The Vimeo video ID in the embed URL may also be incorrect.

Solution: Verify the video's privacy and embed settings in your Vimeo account, or use the PATCH /videos/{id} endpoint to update privacy settings. For domain-restricted embedding, add your Replit deployment domain to the allowed embed domains list in the Vimeo video settings.

```
// Update video to allow embedding everywhere
const response = await vimeoClient.patch(`/videos/${videoId}`, {
  privacy: {
    view: 'anybody',
    embed: 'public'
  }
});
```

### API returns 403 Forbidden when trying to upload videos

Cause: Vimeo's API upload functionality requires a Vimeo Plus, Pro, or Business plan. Basic (free) accounts cannot upload via API. Additionally, upload scope must be included in your access token.

Solution: Upgrade your Vimeo account to at least a Plus plan to enable API uploads. Then generate a new access token that includes the 'upload' scope and update your VIMEO_ACCESS_TOKEN secret in Replit.

### Rate limit errors (429 Too Many Requests) when making multiple API calls

Cause: Vimeo's API enforces rate limits: up to 100 requests per minute for most endpoints on standard plans. Batch operations that loop through many videos can exhaust this limit quickly.

Solution: Implement request throttling in your application. Add delays between API calls in batch operations and cache responses that don't change frequently (such as video metadata and embed codes) with a TTL of at least 1 hour.

```
// Throttled batch requests
const sleep = (ms) => new Promise(r => setTimeout(r, ms));

async function batchUpdateVideos(videoIds, settings) {
  for (const id of videoIds) {
    await vimeoClient.patch(`/videos/${id}`, settings);
    await sleep(700); // Stay well under 100 req/min limit
  }
}
```

## Frequently asked questions

### How do I connect Replit to Vimeo?

Create a developer app at developer.vimeo.com, generate a personal access token with the required scopes, and store it in Replit Secrets as VIMEO_ACCESS_TOKEN. In your server code, include 'Authorization: bearer YOUR_TOKEN' (lowercase 'bearer') in all API request headers when calling https://api.vimeo.com/.

### Does Replit work with Vimeo for free?

Replit is free for development. Vimeo's API access is available on Basic (free) accounts for reading public video data, but uploading via API and accessing private videos requires a paid Vimeo plan (Plus or higher). Check vimeo.com/upgrade for current plan features.

### How do I store my Vimeo API key in Replit?

Click the lock icon (🔒) in the Replit left sidebar to open the Secrets panel. Add a new secret named VIMEO_ACCESS_TOKEN and paste your Vimeo personal access token as the value. Access it in Node.js with process.env.VIMEO_ACCESS_TOKEN or in Python with os.environ['VIMEO_ACCESS_TOKEN']. Restart your Repl after adding the secret.

### How do I customize the Vimeo player embed in Replit?

Build the embed URL yourself by appending query parameters to the player URL: https://player.vimeo.com/video/VIDEO_ID?color=hexcode&autoplay=1&loop=1&title=0&byline=0&portrait=0. You can control the player color, autoplay behavior, loop mode, and whether the title, author name, and avatar are displayed. Wrap this in an iframe for embedding.

### Can I upload videos to Vimeo from Replit?

Yes — Vimeo supports video uploads via API using either the tus resumable upload protocol (for direct file uploads) or the 'pull' approach where you provide a public URL and Vimeo fetches the file. The pull approach is simpler to implement and works well when your video is already stored in S3 or another cloud service. API uploads require a paid Vimeo account and the 'upload' scope in your access token.

### Why is the Vimeo API returning 'bearer' authorization errors?

Vimeo requires 'bearer' in lowercase in the Authorization header: 'Authorization: bearer YOUR_TOKEN'. Using capital 'Bearer' causes authentication failures. This is different from many other APIs that accept either case. Double-check your header capitalization in the code examples above.

---

Source: https://www.rapidevelopers.com/replit-integration/vimeo
© RapidDev — https://www.rapidevelopers.com/replit-integration/vimeo
