Schedule Instagram Stories with an API
This guide shows how to schedule an Instagram Story from a script, a cron job or your own app. You upload the image or video, schedule the story for a time, and check that it published. Storylink publishes the story at that time.
What you need
- A Storylink account on the Business plan with a connected Instagram account. Free and Pro keys can read data, but only Business keys can create content.
- An API key that can write. On the Developers page in the app, create a key and select Allow creating and scheduling content. You cannot add this permission to a key later. The key starts with
sl_live_. - The connection id of the Instagram account. Get it with the call below.
The base URL is https://storylink.pro/api/v1. Send the key in the Authorization: Bearer header. Keep the key on your server. Do not put it in a browser, a mobile app or a file that you commit.
curl https://storylink.pro/api/v1/connections \
-H "Authorization: Bearer $STORYLINK_API_KEY"Each item in data has an id that starts with cnx_. Use the id of the account that must publish the story.
Step 1: upload the image or video
Storylink publishes media from its own storage, so you upload the file first. Ask for an upload URL. Tell the API the file type, the size in bytes, and that the file is a story slide:
curl -X POST https://storylink.pro/api/v1/media/uploads \
-H "Authorization: Bearer $STORYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content_type":"image/jpeg","size_bytes":786432,"kind":"story_slide"}'The answer gives you an upload URL, a token for the upload, and the final URL of the file:
{
"id": "mda_…",
"upload_url": "https://vercel.com/api/blob/…",
"upload_token": "…",
"blob_url": "https://….public.blob.vercel-storage.com/….jpg",
"expires_at": "2026-11-07T10:15:00.000Z"
}Send the bytes of the file to upload_url with a PUT request. Use the upload_token, not your API key:
curl -X PUT "$UPLOAD_URL" \
-H "Authorization: Bearer $UPLOAD_TOKEN" \
-H "Content-Type: image/jpeg" \
--data-binary @story.jpg- The upload URL and token expire after 15 minutes. The
blob_urlstays valid. - A story slide can be JPEG, PNG or WebP up to 30 MB, or MP4, MOV or WebM up to 500 MB.
- Send the real
size_bytesandcontent_typeof the file. The upload rejects a file that is larger, or of a different type. - Uploaded media counts toward the storage of your plan.
Step 2: schedule the story
Create the story with the connection id, the publish time and the slides. Give the time in UTC, with a Z at the end. The time must be in the future.
curl -X POST https://storylink.pro/api/v1/stories \
-H "Authorization: Bearer $STORYLINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"connection_id": "cnx_…",
"scheduled_at": "2026-11-08T09:00:00Z",
"slides": [
{ "media": { "blob_url": "https://….jpg", "type": "image", "width": 1080, "height": 1920 } }
]
}'- A story has 1 to 20 slides. Each slide has one image or one video, and
typeisimageorvideo. widthandheightare optional. For a video, you can also send athumbnail_blob_url.- The answer is
201with the new story. Itsstatusisscheduled. Keep itsid.
The full flow in Node.js
This script does both steps. It needs Node 20 or later, and no packages:
import { readFile } from "node:fs/promises";
const API = "https://storylink.pro/api/v1";
const KEY = process.env.STORYLINK_API_KEY;
async function call(method, path, body) {
const res = await fetch(API + path, {
method,
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${json.error.code}: ${json.error.message}`);
return json;
}
// The Instagram account to publish to.
const { data: [account] } = await call("GET", "/connections");
// Step 1: upload the image.
const file = await readFile("story.jpg");
const upload = await call("POST", "/media/uploads", {
content_type: "image/jpeg",
size_bytes: file.length,
kind: "story_slide",
});
const put = await fetch(upload.upload_url, {
method: "PUT",
headers: { Authorization: `Bearer ${upload.upload_token}`, "Content-Type": "image/jpeg" },
body: file,
});
if (!put.ok) throw new Error(`Upload failed: ${put.status}`);
// Step 2: schedule the story for tomorrow at 09:00 UTC.
const at = new Date();
at.setUTCDate(at.getUTCDate() + 1);
at.setUTCHours(9, 0, 0, 0);
const story = await call("POST", "/stories", {
connection_id: account.id,
scheduled_at: at.toISOString(),
slides: [{ media: { blob_url: upload.blob_url, type: "image" } }],
});
console.log(story.id, story.status); // "scheduled"Check, move or cancel the story
Use the story id for these calls:
GET /v1/stories/{id}gives the status:scheduled,publishing,published,failedorcancelled.PATCH /v1/stories/{id}with a newscheduled_atmoves a scheduled story.DELETE /v1/stories/{id}cancels a scheduled story.POST /v1/stories/{id}/retry-publishtries a failed story again.GET /v1/stories/{id}/insightsgives the views, reach and replies after the story publishes.
You can move or cancel a story only while its status is scheduled. You can retry only a failed story. Other cases return 409 story_not_in_scheduled_state.
Errors and limits
A failed request returns an error object with a code, a message and a docs_url. These are the errors that you are most likely to see first:
402 plan_not_entitled: the key cannot write. Use a Business key with write permission.400 validation_failed: a field is missing or not valid. Theparamfield names it.409 workspace_read_only: the Instagram account is disconnected. Reconnect it in the app.429 rate_limited: you sent more than 60 requests in a minute with this key. Wait for the number of seconds in theRetry-Afterheader.
The Developers page lists every error code, and the OpenAPI spec has every request and response schema.
How Storylink does it
You can also schedule stories in the Storylink calendar, with no code. For the steps, read How to schedule Instagram Stories. To ask an AI assistant to do it, connect Claude to the Storylink MCP server.
To see the app with sample data and no sign-up, open the live demo.
Put this into practice with Storylink
Free forever to start. No card required.