Documentation

Everything you need to resolve Reel URLs and download the media programmatically. The API is public and requires no authentication.

Endpoints

  • POST/api/reels/resolve

    Resolve a Reel URL and return its metadata and download options.

  • GET/api/reels/download/:resolutionId/:quality

    Stream a resolved media variant. Supports Range requests.

  • GET/api/health

    Liveness and version information.

  • GET/api/health/ready

    Reports whether the optional database and Redis dependencies are in use.

Resolve a Reel

Send the URL as JSON. The resolver accepts the formats people actually paste: a trailing slash, a missing scheme, shared ?igsh= query strings and #media fragments are all normalised.

curl -X POST /api/reels/resolve \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.instagram.com/reel/ABC123/"}'

A successful response returns the reel metadata. The direct media URL located on the public Instagram page stays server-side, so downloadUrl always points back at this API.

{
  "success": true,
  "data": {
    "id": "ig_ABC123",
    "title": "Amazing Reel Title",
    "thumbnail": "https://...fbcdn.net/...",
    "duration": 30,
    "shortCode": "ABC123",
    "media": [
      {
        "quality": "hd",
        "format": "mp4",
        "downloadUrl": "/api/reels/download/ig_ABC123/hd",
        "width": 1080,
        "height": 1920
      }
    ]
  }
}

Download

Request the downloadUrl from the resolve response. Range headers are forwarded upstream, so partial requests return 206 with a Content-Range. Instagram signs the underlying media URLs, so a link that has expired returns 410 and the Reel should be resolved again.

curl -L -o reel.mp4 "/api/reels/download/ig_ABC123/hd"

Media variants

QualityFormatTypical size
originalmp4Source quality
hdmp4720p-1080p
sdmp4Lower bandwidth

media[] only lists the renditions that were actually located on the public page, so the set can vary per Reel.

Rate limits

Resolutions are limited to 30 requests per minute per IP and downloads to 10 per hour. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a limited response adds Retry-After.

Errors

Failures return a machine-readable code alongside a human-readable message.

{
  "success": false,
  "error": {
    "code": "PRIVATE_CONTENT",
    "message": "This content is from a private account"
  }
}
StatusCodeMeaning
400INVALID_URLThe URL format is invalid.
400UNSUPPORTED_URLNot a supported Instagram Reel or Post URL.
403PRIVATE_CONTENTThe content is from a private account.
404NOT_FOUNDThe Reel was not found or has been deleted.
401AUTH_REQUIREDThe Reel exists, but Instagram only serves its video file to a signed-in session.
404MEDIA_UNAVAILABLEThe media could not be located or accessed.
429RATE_LIMITEDToo many requests.
451NOT_PERMITTEDThe content cannot be legally or technically accessed.
500INTERNAL_ERRORAn unexpected server error occurred.

Using it from JavaScript

const response = await fetch('/api/reels/resolve', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ url }),
});

const result = await response.json();
if (!result.success) throw new Error(result.error.message);

const downloadUrl = result.data.media.find((m) => m.quality === 'hd')?.downloadUrl;
if (downloadUrl) {
  const video = await fetch(downloadUrl);
  // Handle the streamed response.
}