An approval link puts a client in the loop before anything goes out. Schedule the posts as usual, put them in a link, and send the link to the client. They see each post as it will appear and approve it or ask for changes. A post in a link doesn't publish until it is approved.
Create a link
Name the profile and the scheduled posts, by job_ids or request_ids:
curl -X POST https://api.reelspan.com/v1/approval-links \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile_username": "my-brand",
"job_ids": ["sched_5d0e8c1a7b3f49e2a6c4d8b0", "sched_7a2c9e4b1d6f40a8c3e5b9d2"],
"title": "December posts",
"note": "Two posts for the launch week. Approve each, or tell us what to change.",
"expires_in_days": 7
}'import { Reelspan } from 'reelspan';
const reelspan = new Reelspan();
const { approval_link } = await reelspan.approvals.create({
user: 'my-brand',
jobIds: ['sched_5d0e8c1a7b3f49e2a6c4d8b0', 'sched_7a2c9e4b1d6f40a8c3e5b9d2'],
title: 'December posts',
note: 'Two posts for the launch week. Approve each, or tell us what to change.',
expiresInDays: 7,
});from reelspan import Reelspan
reelspan = Reelspan()
link = reelspan.approvals.create(
"my-brand",
["sched_5d0e8c1a7b3f49e2a6c4d8b0", "sched_7a2c9e4b1d6f40a8c3e5b9d2"],
title="December posts",
note="Two posts for the launch week. Approve each, or tell us what to change.",
expires_in_days=7,
)["approval_link"]{
"success": true,
"approval_link": {
"id": "apl_Hk3mP9qT2vX7wZ1bN5cR",
"title": "December posts",
"note": "Two posts for the launch week. Approve each, or tell us what to change.",
"url": "https://app.reelspan.com/approve/…",
"expires_at": "2026-12-08T10:15:00.000Z",
"created_at": "2026-12-01T10:15:00.000Z",
"posts": { "pending": 2, "approved": 0, "changes": 0 }
}
}Send url to the client. It works without a Reelspan account until expires_at, so treat it like a password: anyone with the link can answer for the client.
- Up to 100 posts per link, all scheduled and all of the named profile. Any other post gets 400
NOT_SCHEDULED; an id that doesn't exist gets 404. titleis up to 120 characters (default "Posts to approve");note, shown above the posts, up to 1,000.expires_in_daysis 1 to 60, 14 by default.- A post can be in one link at a time. Putting it in a new link takes it out of the old one.
The SDKs take job_ids; to pass request_ids, use request('POST', '/v1/approval-links', …).
What the client sees
The link opens a page with the link's title, your note, and each post in time order:
- the networks it goes to, and when, in the time zone the posts were scheduled in;
- the video, or up to four of the photos;
- the caption;
- Approve and Request changes. Asking for changes needs a comment saying what should change.
The client can change an answer until the post goes out. On a plan with white-label, the page shows the profile's logo and says it was sent by the brand; otherwise it says it was sent with Reelspan. It is shown in the profile's white-label language if one is set, else in the browser's. An expired or deleted link shows a short message instead.
How a post waits
- Pending or changes asked: the post doesn't publish. When its time comes it keeps waiting, still listed in Scheduled posts.
- Approved before its time: it publishes at its scheduled time as usual.
- Approved after its time: it publishes at once.
- Edited: changing a post with
PATCH /v1/scheduled/{job_id}sends it back to the client as pending. A waiting post can be edited, and given a new time, even after its original time has passed. - Cancelled: cancel it as any scheduled post; the page then shows it as cancelled, with no buttons.
Follow the answers
GET /v1/approval-links lists links newest first (up to 100), each with its counts. Filter by profile_username:
const { approval_links } = await reelspan.approvals.list('my-brand');
for (const l of approval_links) console.log(l.title, l.posts);for l in reelspan.approvals.list("my-brand")["approval_links"]:
print(l["title"], l["posts"])Each post's own answer is on the post: GET /v1/scheduled and GET /v1/scheduled/{job_id} include approval with the link's id, the status (pending, approved or changes), the client's comment and when they decided. Every answer also shows as a notification in the dashboard.
Delete a link
DELETE /v1/approval-links/{id} removes the link. Its posts stop waiting: they publish at their scheduled times, and any whose time has already passed publish at once.
curl -X DELETE https://api.reelspan.com/v1/approval-links/apl_Hk3mP9qT2vX7wZ1bN5cR \
-H "Authorization: Bearer $REELSPAN_API_KEY"await reelspan.approvals.delete('apl_Hk3mP9qT2vX7wZ1bN5cR');reelspan.approvals.delete("apl_Hk3mP9qT2vX7wZ1bN5cR")When a link expires, the client can no longer open it, but its unanswered posts keep waiting. Put them in a new link, or delete the old one to let them publish.
Creating and deleting links needs the publish scope; listing them needs read.