Why do Instagram image URLs expire with a 403?
Instagram signs every media URL with an expiry. A few days after you fetched it, the signature stops matching and the CDN answers 403. The image still exists. Your link to it died. Anything that stored raw Instagram URLs, a website feed, a report, a database, breaks on a schedule.
Openhandle serves media through media.openhandle.dev. The URL in the response is ours and does not expire. Store it once and it keeps working.
Checked September 2026 · request and response run against the API · no affiliate links
curl "https://api.openhandle.dev/v1/instagram/posts/910100000001" \
-H "Authorization: Bearer $OPENHANDLE_API_KEY"import { OpenHandle } from '@openhandle/sdk';
const openhandle = new OpenHandle({ apiKey: process.env.OPENHANDLE_API_KEY! });
const { data } = await openhandle.instagram.post('910100000001').get();
console.log(data.media[0]?.url); // https://media.openhandle.dev/…{
"platform": "instagram",
"resource": "post",
"capturedAt": "2026-09-18T11:26:40Z",
"source": "live",
"data": {
"id": "910100000001",
"media": [
{
"type": "image",
"url": "https://media.openhandle.dev/instagram/910100000001-media",
"width": 1080,
"height": 1350
}
]
}
}Trimmed to the media array. Video posts carry a video asset plus a cover image. Where Instagram publishes several sizes, they appear in `variants`. The example is a Test post.
What people try, and what happens
- —Re-fetch the post when the image breaks. Works, but you find out from a user with a broken page, and every stored link needs the same repair.
- —Download every file yourself. Works. Now you run storage, a CDN, and a copy job for every post you touch.
- —Strip the signature from the URL. Answers 403 immediately. The signature is the permission.
- —Hotlink the raw Instagram URL in an image tag. Shows for a few days, then 403. This is where most broken feed widgets come from.
How Openhandle media works
- —One stable URL per asset. The response carries a
media.openhandle.devlink. It serves the same file every time. - —Served from our cache and CDN. We fetch the original when you ask, keep it, and serve it from edge locations. Your page never talks to Instagram’s CDN.
- —Renditions when they exist. Where Instagram publishes multiple sizes,
variantslists them with width and height. Pick the one your layout needs. - —Public posts only. A post from a private account is never fetched, so its media is never served.
When this does not work
Media follows the post. If the post is gone, so is the media.
| Situation | Status | Code |
|---|---|---|
| The post is deleted or never existed | 404 | POST_NOT_FOUND |
| The account is private | 403 | PROFILE_PRIVATE |
| The identifier is malformed | 400 | INVALID_IDENTIFIER |
{
"error": {
"code": "POST_NOT_FOUND",
"message": "This post was not found.",
"requestId": "req_01j8…",
"retryable": false
}
}A media link for a post that later got deleted keeps serving from our cache until the cache expires. Account owners can ask us to block their profile, and blocked media stops serving.
Read next
More Instagram questions
Send your agent ahead.
Paste the prompt into your agent and it sets everything up. Or come yourself, it takes five minutes.