Video Planner

Video Planner API guide

How to connect a Discord bot or other tool to Video Planner: getting a key, permissions, every request with examples, webhooks, and keeping keys safe.

The Video Planner API lets a Discord bot, a script or another tool work with your workspace: add and change ideas, videos and streams, see what's coming up and how things are going, and get a message (a webhook) when something happens. This guide covers everything you need to build one.

Who can use it: the workspace's plan needs API access, and each person switches it on for themselves in My account → API access. It's off until they do.

1. Get a key

  1. Sign in to Video Planner and open My account → API access.
  2. Read what keys can do, tick the box to confirm, and switch API access on.
  3. Switch to the workspace the key should work in (the sidebar), then Make a key: give it a name (for example "Discord bot"), tick what it may do, and choose when it runs out.
  4. Copy the key straight away. It starts with vp_ and is only shown once. Lost it? Remove it and make another.

You can have up to 10 keys. Removing a key, or switching API access off (which removes them all), stops it working straight away.

2. What a key can do

Each key only has the permissions you tick when you make it. Tick only what the tool needs:

  • See ideas (ideas:read): idea titles, statuses and priorities in the Idea planner.
  • Add ideas (ideas:write): save ideas to the Idea planner.
  • Change ideas (ideas:edit): change an idea's title, status or priority, or turn it into a video.
  • See videos and what's coming up (videos:read): video titles, statuses and filming/publish dates, and upcoming streams.
  • Add videos (videos:write): add videos to the board.
  • Change videos (videos:edit): change a video's title, status, filming date, publish date or published link.
  • See streams (streams:read): stream titles, times, statuses and stats.
  • Add and change streams (streams:write): plan streams, and change their title, time, status or VOD link.
  • See suggestions (suggestions:read): community suggestions (including ones waiting for review and declined ones), their votes and comments.
  • Send suggestions (suggestions:write): send in suggestions, votes and comments for your community, e.g. from a Discord bot.
  • Look after suggestions (suggestions:manage): approve or decline suggestions, set their status, reply, and turn them into ideas.
  • See stats (stats:read): how many videos are at each status, published lately, coming up, and YouTube channel totals.
  • See YouTube videos (youtube:read): your channels' uploads with their views, likes and comments.

A key works as you, in one workspace, and never does more than your role there allows. Areas your role can't see stay hidden, "assigned videos only" still applies, and the workspace's plan limits still count.

A key can't read scripts, descriptions, notes, sponsor deals, messages, accounts or the team, and can't delete anything.

3. Sending requests

Every request goes to this address:

https://videoplanner.co.uk/api/v1

and sends the key in a header:

Authorization: Bearer vp_your_key

Opening that address in a browser (no key needed) lists every request. Send JSON (Content-Type: application/json) when adding or changing things. Answers are always JSON.

To change something, use PATCH. If your tool can't send PATCH, POST to the same address does the same.

Dates are 2026-10-24. Dates with a time are ISO 8601 with the time zone, like 2026-10-24T19:00:00+01:00; without a time zone they're taken as UK time. Send null to clear a date or link.

Limits: 60 requests a minute per key. After that you get 429 with a Retry-After header; wait and try again.

4. Answers and errors

A successful answer has "ok": true and the data:

{"ok": true, "id": 42, "title": "Reacting to my first video", "url": "https://videoplanner.co.uk/planner/42"}

When something's wrong you get "ok": false and a message you can show to people as it is:

{"ok": false, "error": "Give the idea a title."}
  • 401: no key, or the key isn't valid (removed or run out).
  • 403: not allowed (the key doesn't have that permission, your role can't, API access is off or restricted, or the plan doesn't include it).
  • 404: there's nothing with that id in the workspace (or it isn't assigned to you).
  • 409: already done (for example, the idea is already a video).
  • 422: something in what you sent needs fixing.
  • 429: too many requests; slow down.
  • 503: the API is switched off for maintenance; try again later.

Read-only: the Video Planner team can make the API read-only for everyone (or for one account). Reading still works, and adding or changing anything answers 403. GET /me tells you with "read_only": true.

5. Requests

GET /me

Who the key belongs to, its workspace, and what it may do. Works with any key; handy to check a key.

curl -H "Authorization: Bearer vp_your_key" https://videoplanner.co.uk/api/v1/me
{"ok": true, "user": {"name": "Eddie"}, "workspace": {"id": 1, "name": "My workspace", "role": "Owner"},
 "key": {"name": "Discord bot", "scopes": ["ideas:write", "videos:read"], "expires_at": "2027-01-17T10:00:00+00:00"}, "read_only": false}

Ideas

GET /ideas (needs See ideas): ideas in the Idea planner, most important first.

  • view (optional): open (not turned into videos yet; the default), promoted or all
  • status (optional): new, developing or parked
  • priority (optional): high, medium or low
  • limit (optional): 1 to 100 (default 25)
{"ok": true, "ideas": [{"id": 42, "title": "Reacting to my first video", "status": "new", "status_label": "New",
  "priority": "medium", "priority_label": "Medium", "url": "https://videoplanner.co.uk/planner/42", "type": null, "video_id": null, "created_at": "2026-10-19T10:00:00+01:00"}]}

GET /ideas/{id} (needs See ideas): one idea, as "idea": {…}.

POST /ideas (needs Add ideas): save an idea.

  • title (required, up to 200 characters)
  • notes (optional, up to 2,000)
  • hook (optional, up to 500)
  • priority (optional): high, medium (the default) or low
curl -H "Authorization: Bearer vp_your_key" -H "Content-Type: application/json" \
     -d '{"title": "Reacting to my first video", "notes": "From the Discord"}' \
     https://videoplanner.co.uk/api/v1/ideas

Answers 201 with the new idea's id and a link (url) to it.

PATCH /ideas/{id} (needs Change ideas): send any of title, status, priority. Answers with the idea.

POST /ideas/{id}/promote (needs Change ideas, and your role must be able to add videos): turns the idea into a video on the board. Send status to choose where it starts (default idea). Answers 201 with the new video.

Videos

GET /videos (needs See videos): videos on the board.

  • status (optional): idea, scripting, filming, editing, scheduled or published
  • limit (optional): 1 to 100 (default 25)
{"ok": true, "videos": [{"id": 7, "title": "My studio tour", "status": "filming", "status_label": "Filming",
  "film_date": "2026-10-21", "publish_at": null, "url": "https://videoplanner.co.uk/videos/7"}]}

GET /videos/{id} (needs See videos): one video, with its type, channel, published_url, assigned_to (names), idea_id (the idea it came from), created_at and updated_at.

POST /videos (needs Add videos): add a video.

  • title (required, up to 200 characters)
  • notes (optional, up to 5,000)
  • status (optional): where it starts on the board (default idea)

Answers 201 with the new video's id, status and url.

PATCH /videos/{id} (needs Change videos): send any of these. Answers with the video.

  • title
  • status: moving a video to published sends the published webhook
  • film_date: a date, or null
  • publish_at: a date and time, or null
  • published_url: the link to the video once it's out (https://…), or null
curl -X PATCH -H "Authorization: Bearer vp_your_key" -H "Content-Type: application/json" \
     -d '{"status": "scheduled", "publish_at": "2026-10-30T18:00:00+00:00"}' \
     https://videoplanner.co.uk/api/v1/videos/7

GET /upcoming (needs See videos): filming days, publish dates and streams coming up, soonest first.

  • days (optional): how far ahead, 1 to 90 (default 14)

Each item has a type (filming, publish or stream), a date, a time when there is one, and the video or stream it's about. Streams are only included if your role can see Streams.

Streams

These need your role to be able to see Streams, and the plan to include them.

GET /streams (needs See streams):

  • when (optional): upcoming (the default) or past
  • limit (optional): 1 to 50 (default 20)
{"ok": true, "when": "upcoming", "streams": [{"id": 3, "title": "Friday night racing", "category": "Forza Horizon 5",
  "platform": "twitch", "platform_label": "Twitch", "status": "planned", "status_label": "Planned",
  "starts_at": "2026-10-23T19:00:00+01:00", "duration_min": 180, "checklist": {"done": 2, "total": 6}, "vod_url": null, "url": "https://videoplanner.co.uk/streams/3"}]}

Streams that are done also have stats: peak_viewers, avg_viewers, new_followers and new_subs.

GET /streams/{id} (needs See streams): one stream, as "stream": {…}.

POST /streams (needs Add and change streams): plan a stream. It starts as planned, with the usual go-live checklist.

  • title (required, up to 200 characters)
  • starts_at (required): a date and time
  • category (optional): e.g. the game
  • platform (optional): twitch (the default), youtube, kick, tiktok or other
  • duration_min (optional): 1 to 1440

PATCH /streams/{id} (needs Add and change streams): send any of title, starts_at, category, platform, duration_min, vod_url, and status (planned, ready, live, done or cancelled). Marking it live sends the live webhook.

Community suggestions

Each workspace can have a public suggestion page (switch it on in Suggestions, in the sidebar) where viewers suggest videos, vote and comment. These requests let a bot do the same, for example a /suggest command in your Discord. They need your role to be able to see Ideas, and the plan to include community suggestions.

If Review new suggestions is on, new suggestions start as pending (waiting for review), just like ones from the page. With Show ones waiting for review on (the default) they're listed publicly straight away and people can vote for them while you decide; with it off, nobody sees them until you approve them. New comments wait for approval. Declined suggestions are never shown publicly.

Statuses: pending (waiting for review), open, planned, in_progress, done and declined. People can vote for pending (when shown), open, planned and in_progress ones.

GET /suggestions (needs See suggestions):

  • status (optional): one status; leave it out for all of them
  • sort (optional): votes (the default) or new
  • limit (optional): 1 to 100 (default 25)
{"ok": true, "page": {"enabled": true, "url": "https://videoplanner.co.uk/suggest/a1b2c3d4e5f6", "moderation": true, "shows_waiting": true},
 "suggestions": [{"id": 12, "title": "Studio tour", "details": "Show the new desk!", "name": "Sam", "status": "open", "status_label": "Suggested",
   "votes": 31, "comments": 4, "response": null, "idea_id": null, "source": "web", "created_at": "2026-10-18T20:15:00+01:00", "url": "https://videoplanner.co.uk/suggest/a1b2c3d4e5f6/12"}]}

GET /suggestions/{id} (needs See suggestions): one suggestion, with comment_list (each comment's name, body, status and whether it's from_team).

POST /suggestions (needs Send suggestions): title (required, 3 to 150 characters), details (optional, up to 2,000), name (optional: who suggested it, e.g. their Discord name), private_note (optional, up to 1,000: something only you should see, e.g. their Discord tag or how to contact them). Answers 201; its status is pending when it's waiting for review.

POST /suggestions/{id}/vote (needs Send suggestions): voter (required): who's voting, e.g. "discord:123456789". Each voter counts once; send "remove": true to take a vote back. Answers with the new votes.

POST /suggestions/{id}/comments (needs Send suggestions): body (required, up to 1,000 characters) and name (optional).

PATCH /suggestions/{id} (needs Look after suggestions): any of status (e.g. open to approve, declined, planned, done), response (a public reply, or null), title, and team_note (your own private notes, or null).

Private notes: private_note (from the person who suggested it) and team_note (yours) are only in answers for a key with Look after suggestions made by someone with creator access (the workspace owner or a channel manager). They're never on the public page, in webhooks or copied into the Idea planner.

POST /suggestions/{id}/idea (needs Look after suggestions, and your role must be able to change ideas): adds it to the Idea planner (who suggested it and the votes go in the notes) and marks it planned. Answers 201 with the idea.

Stats and YouTube

GET /stats (needs See stats):

{"ok": true, "videos_by_status": {"idea": 12, "scripting": 3, "filming": 1, "editing": 2, "scheduled": 1, "published": 40},
 "videos_total": 59, "in_progress": 7, "published_last_30_days": 4, "coming_up": 3,
 "channels": [{"id": 1, "title": "My Channel", "handle": "@mychannel", "subscribers": 12300, "views": 987654, "videos": 60, "synced_at": "2026-10-19T09:00:00+01:00"}]}

channels is only filled in if your role can see YouTube channels. subscribers is null when the channel hides its count.

GET /youtube/videos (needs See YouTube videos): uploads from the workspace's YouTube channels, as of their last sync.

  • channel (optional): a channel id from /stats
  • order (optional): recent (the default) or views
  • limit (optional): 1 to 50 (default 10)
{"ok": true, "videos": [{"channel_id": 1, "channel": "My Channel", "youtube_id": "dQw4w9WgXcQ", "title": "My studio tour",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "published_at": "2026-10-12T17:00:00+01:00", "duration_sec": 754,
  "views": 10432, "likes": 812, "comments": 96, "thumbnail": "https://i.ytimg.com/…", "stats_at": "2026-10-19T09:00:00+01:00"}]}

6. Webhooks

A webhook sends a message to an address when something happens, so your bot doesn't have to keep asking. Add one in My account → API access → Webhooks: give it a name, paste the address, and tick what it should tell you about:

  • A video is added to the board (video.created)
  • A video moves to another status (video.status)
  • A video is published (video.published)
  • An idea is added to the Idea planner (idea.created)
  • A stream is planned (stream.planned)
  • A stream is marked live (stream.live)
  • A stream is cancelled (stream.cancelled)
  • A community suggestion is posted (or approved) (suggestion.created)
  • A community suggestion is waiting for review (suggestion.pending): handy for a private moderators' channel
  • A community suggestion's status changes (suggestion.status)

Suggestion messages include its number (id), a link to its public page (url, when it's public) and a link for your team (manage_url). Discord posts show them as View it and Manage it.

Like a key, a webhook works as you in one workspace and only tells you about things your role there can see. It covers changes made on the site, by your team and through the API.

Discord: in your server, open the channel's settings → Integrations → Webhooks → New Webhook → Copy Webhook URL, and paste it. Messages arrive as posts in that channel (they can't ping @everyone or anyone else).

Anything else: any public https:// address gets a POST with JSON:

{"event": "video.published", "at": "2026-10-19T18:00:04+01:00", "by": "Eddie",
 "data": {"id": 7, "title": "My studio tour", "status": "published", "status_label": "Published", "previous_status": "scheduled",
          "film_date": "2026-10-12", "publish_at": "2026-10-19T18:00:00+01:00", "published_url": "https://youtu.be/…", "url": "https://videoplanner.co.uk/videos/7"},
 "workspace": {"id": 1}, "webhook": {"id": 2, "name": "My bot"}}

When you add one you get a signing secret (shown once). Each message has these headers:

  • X-VideoPlanner-Event: the event, e.g. video.published
  • X-VideoPlanner-Timestamp: when it was sent (Unix seconds)
  • X-VideoPlanner-Signature: sha256= and an HMAC-SHA256 of timestamp.body made with your secret

Check the signature (and that the timestamp is recent) before trusting a message. In Node.js:

const crypto = require('crypto');
function isGenuine(req, rawBody) {
  const ts = req.headers['x-videoplanner-timestamp'];
  const expected = 'sha256=' + crypto.createHmac('sha256', process.env.VP_WEBHOOK_SECRET).update(ts + '.' + rawBody).digest('hex');
  const given = req.headers['x-videoplanner-signature'] || '';
  return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))
    && Math.abs(Date.now() / 1000 - Number(ts)) < 300;
}

Answer with any 2xx to say you got it. If the address doesn't answer, the message is tried again after 1, 5, 30 and 120 minutes. After 15 failures in a row the webhook is paused (and straight away if the address says it's gone, e.g. a deleted Discord webhook); turn it back on in My account. Use Send a test to check one works.

7. Example: a Discord bot

A /idea command for a bot built with discord.js. Put the key in the bot's settings as VP_KEY (an environment variable), never in the code.

client.on('interactionCreate', async (interaction) => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== 'idea') return;
  const res = await fetch('https://videoplanner.co.uk/api/v1/ideas', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.VP_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ title: interaction.options.getString('title'), notes: 'Added by ' + interaction.user.username }),
  });
  const j = await res.json();
  await interaction.reply({ content: j.ok ? 'Saved to the Idea planner: ' + j.url : 'Not saved: ' + j.error, ephemeral: true });
});

An /upcoming command works the same way with GET https://videoplanner.co.uk/api/v1/upcoming?days=7, and a /live command could PATCH https://videoplanner.co.uk/api/v1/streams/{id} with {"status": "live"}.

Suggestions from Discord: /suggest and /ideavote

A key with Send suggestions lets your community suggest and vote from Discord. Every suggestion has a number (its id): the bot replies with it, the public page shows it (#12), and webhook posts include it, so people know what to vote for. Put your command in Suggestions → Your bot's vote command (e.g. /ideavote) and it's shown with each suggestion (/ideavote 12), and sent as vote_command in the API and webhooks.

client.on('interactionCreate', async (interaction) => {
  if (!interaction.isChatInputCommand()) return;
  const headers = { Authorization: 'Bearer ' + process.env.VP_KEY, 'Content-Type': 'application/json' };
  if (interaction.commandName === 'suggest') {           // /suggest title:<text>
    const res = await fetch('https://videoplanner.co.uk/api/v1/suggestions', { method: 'POST', headers,
      body: JSON.stringify({ title: interaction.options.getString('title'), name: interaction.user.username }) });
    const j = await res.json();
    await interaction.reply(j.ok ? 'Thanks! Your suggestion is #' + j.suggestion.id + '. Vote for it with /ideavote ' + j.suggestion.id : 'Not sent: ' + j.error);
  }
  if (interaction.commandName === 'ideavote') {          // /ideavote id:<number>
    const id = interaction.options.getInteger('id');
    const res = await fetch('https://videoplanner.co.uk/api/v1/suggestions/' + id + '/vote', { method: 'POST', headers,
      body: JSON.stringify({ voter: 'discord:' + interaction.user.id }) });
    const j = await res.json();
    await interaction.reply({ content: j.ok ? 'Voted for #' + id + ': it has ' + j.votes + ' votes now.' : 'Couldn\'t vote: ' + j.error, ephemeral: true });
  }
});

Using the Discord user id as voter means each person counts once, however many times they use the command.

8. Example: Python

import os, requests

r = requests.get('https://videoplanner.co.uk/api/v1/stats', headers={'Authorization': 'Bearer ' + os.environ['VP_KEY']})
data = r.json()
if data['ok']:
    print(data['in_progress'], 'videos in progress,', data['coming_up'], 'coming up')
else:
    print('Error:', data['error'])

9. Keeping keys safe

  • Treat a key like a password: anyone who has it can do what it allows, as you.
  • Keep it in your bot's or server's settings, never in a public code repository, a screenshot or a Discord message.
  • Give each tool its own key with only the permissions it needs, and an expiry date. A bot that only posts what's coming up doesn't need any Add or Change permissions.
  • A Discord webhook address is a secret too: anyone with it can post in that channel.
  • If a key might have leaked, remove it in My account → API access and make a new one. It stops working straight away.
  • Your activity in My account shows everything added or changed through the API.

Frequently asked questions

Can a key see my scripts or sponsor deals?

No. Keys can only see titles, dates, statuses and stats, and add or change the things you allow. They can't read scripts, descriptions, notes or sponsor deals.

Can a key delete anything?

No. Nothing can be deleted through the API.

Can I use a key in someone else's workspace?

Yes, if you're a member there. Make the key while you're working in that workspace. It can only do what your role there allows, and stops working if you leave.

Can my viewers suggest videos?

Yes. Switch on your suggestion page in Suggestions and share the link: viewers suggest, vote and comment there, and a bot with a Send suggestions key can do the same from Discord. New ones show as "Waiting for review" so people can vote while you decide; you approve or decline them, reply, and add the best to your Idea planner.

How do I post in Discord when a video goes live?

Add a webhook with your Discord channel's webhook address and tick A video is published. When anyone moves a video to Published (on the board, or through the API), a post appears in that channel.

What happens if API access is switched off?

Every key and webhook you made is removed and stops working straight away. Switch it on again and make new ones.