Skip to content
reelspanDocs

Integrations

Airtable

Publish a video from an Airtable record to 22 networks with a scripting extension script, and write the request ID and each network's result back to the record.

Keep your content plan in Airtable and post from it: a script in the Scripting extension sends a record's video and caption to Reelspan, then writes the request_id and the results back to the record. There is no Reelspan app for Airtable; the script calls the REST API.

Set up the table

Create a table called Posts with these fields:

Field Type Holds
Caption Long text The caption (YouTube uses it as the title)
Video Attachment The video file
Networks Multiple select Network ids: tiktok, youtube, instagram, x, sandbox…
Profile Single line text The Reelspan profile, for example my-brand
Request ID Single line text Filled in by the script
Status Single line text Filled in by the script

The multiple-select options must be Reelspan's network ids exactly; see Networks. Add sandbox to try the flow without a connected account.

Add the script

  1. Create an API key under Developers → API keys. Use a test key (rs_test_…) while you set this up: posts go through every check but nothing reaches the networks.
  2. In the base, open Extensions, add Scripting, and paste the script below.
  3. Open the script's settings and paste the key into Reelspan API key.
const settings = input.config({
  title: 'Post to Reelspan',
  description: 'Publishes a record’s video to the networks in its Networks field.',
  items: [input.config.text('apiKey', { label: 'Reelspan API key' })],
});

const table = base.getTable('Posts');
const record = await input.recordAsync('Pick the post to publish', table);
if (!record) throw new Error('No record picked');

const video = record.getCellValue('Video')?.[0];
const networks = (record.getCellValue('Networks') ?? []).map((n) => n.name);
if (!video || networks.length === 0) throw new Error('The record needs a video and at least one network');

// the video endpoint takes multipart/form-data; Airtable's remote fetch can't send FormData, so build the body by hand
const fields = [
  ['user', record.getCellValueAsString('Profile')],
  ...networks.map((n) => ['platform[]', n]),
  ['title', record.getCellValueAsString('Caption')],
  ['video', video.url],
  ['async_upload', 'true'],
];
const boundary = `reelspan${Date.now()}`;
const body =
  fields.map(([name, value]) => `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`).join('') +
  `--${boundary}--\r\n`;

const res = await remoteFetchAsync('https://api.reelspan.com/v1/posts/video', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${settings.apiKey}`,
    'Content-Type': `multipart/form-data; boundary=${boundary}`,
    'Idempotency-Key': `airtable-${record.id}`,
  },
  body,
});
const json = await res.json();

if (!res.ok) {
  await table.updateRecordAsync(record, { Status: `Error: ${json.message}` });
  throw new Error(json.message);
}
await table.updateRecordAsync(record, { 'Request ID': json.request_id, Status: 'Posting' });
output.markdown(`Sent to ${networks.join(', ')}. Request ID: \`${json.request_id}\``);

Select Run, pick a record, and the script fills in Request ID and Status.

A few things the script relies on:

  • remoteFetchAsync sends the request from Airtable's servers. A plain fetch from the extension runs in your browser, and the Reelspan API doesn't accept browser requests from other sites.
  • The video goes as a URL. The script sends the attachment's URL in the video field and Reelspan downloads the file from there, so nothing is uploaded from your computer.
  • Idempotency-Key is the record's ID, so running the script twice on the same record never posts twice. See Retrying safely.

Other fields from Post a video, such as youtube_title or scheduled_date, go in as more [name, value] pairs. For text-only posts, send JSON to /v1/posts/text instead; see Post text.

Check the results

Add a second script that reads the status of the picked record:

const settings = input.config({
  title: 'Check Reelspan status',
  items: [input.config.text('apiKey', { label: 'Reelspan API key' })],
});

const table = base.getTable('Posts');
const record = await input.recordAsync('Pick the post to check', table);
const requestId = record?.getCellValueAsString('Request ID');
if (!requestId) throw new Error('This record has no Request ID yet');

const res = await remoteFetchAsync(`https://api.reelspan.com/v1/posts/status?request_id=${encodeURIComponent(requestId)}`, {
  headers: { Authorization: `Bearer ${settings.apiKey}` },
});
const json = await res.json();
if (!res.ok) throw new Error(json.message);

const lines = json.results.map((r) => `${r.platform}: ${r.success ? r.post_url ?? 'done' : `failed (${r.error_message})`}`);
await table.updateRecordAsync(record, { Status: `${json.status} (${json.completed}/${json.total})` });
output.markdown(lines.join('\n\n') || 'No network has finished yet.');

status is pending or in_progress while networks are still working, then completed (at least one network published) or failed. Only networks that have finished appear in results. With a test key, finished posts have no post_url. See Post status.

To have results arrive on their own, set a webhook that points at an Airtable automation's When webhook received trigger, or at a tool such as n8n that updates the record.

Keep the key safe

Script settings are visible to everyone who can edit the base. Use a key with only the Publish and Read scopes, and one key per base, so you can revoke it without touching anything else. See Authentication.

Something unclear or missing? Write to [email protected] and we’ll fix the page.