Content table queue
A table is the natural place to plan content with other people: one row per post, columns for the file, the text, where and when. This recipe reads such a table and turns each row into a queued post. It is written for n8n because that is where most people keep such flows, with the same calls as a script at the end. The file column holds a public https URL (a shared drive link, an S3 object), so the upload step is POST /media/from-url and no bytes pass through the workflow.
You will need
Section titled “You will need”- A paid plan and an API key with
write, stored as an n8n Header Auth credential - nameAuthorization, valueBearer ds_live_xxxxxxxxxxxxxxxxxxxx- never in a node’s body. - A sheet or CSV with the columns
file_url,text,title,accounts(account ids separated by|, fromGET /accounts),when(a local time,2026-09-29T18:00, read in the workspace’s time zone),status(empty until done). - n8n with the Google Sheets node, or a CSV file.
Google Sheets: Read rows (status is empty) → HTTP Request: POST /media/from-url → mediaId → Wait 20 s → HTTP Request: GET /media/{{mediaId}} → loop until status = ready → HTTP Request: POST /posts/plan → stop the row if ok = false → HTTP Request: POST /posts (confirm: true) → post ids → Google Sheets: Update row (status = queued, post_ids)-
Read rows. Google Sheets node, operation Get many rows, filter
statusempty. -
Import the file. HTTP Request node,
POST https://api.dropslate.top/v1/media/from-url, credential Header Auth, body JSON:{ "url": "{{ $json.file_url }}", "filename": "{{ $json.file_url.split('/').pop() }}" }The answer is
{ "mediaId": "med_…", "status": "processing" }. -
Wait until ready. A Wait node of 20 seconds, then HTTP Request
GET https://api.dropslate.top/v1/media/{{ $('Import').item.json.mediaId }}, then an IF node on{{ $json.status === 'ready' }}that loops back to the Wait node when false. Afailedstatus ends the row withfailReasonwritten to the sheet. -
Plan. HTTP Request
POST https://api.dropslate.top/v1/posts/plan, body:{"targets": { "accountIds": {{ JSON.stringify($('Read rows').item.json.accounts.split('|')) }} },"mediaIds": ["{{ $('Import').item.json.mediaId }}"],"title": "{{ $('Read rows').item.json.title }}","text": "{{ $('Read rows').item.json.text }}","when": { "publishAt": "{{ $('Read rows').item.json.when }}", "timezone": "Europe/Kiev" }}An IF node on
{{ $json.ok }}: false routes the row to a sheet update withstatus = checkand{{ $json.table }}in a notes column - a person reads which account and why. -
Create. The same body plus
"confirm": truetoPOST https://api.dropslate.top/v1/posts. The answer hasposts[]; write{{ $json.posts.map(p => p.id).join('|') }}andstatus = queuedback to the row.
Set Batching on the plan and create nodes to one item per second: the workspace allows 60 POST /posts an hour, and a sheet of 100 rows would otherwise hit rate_limited at row 61 - the node’s retry-on-fail with the Retry-After header handles the rest.
The same, as a script
Section titled “The same, as a script”export KEY="ds_live_xxxxxxxxxxxxxxxxxxxx"; export API="https://api.dropslate.top/v1"; AUTH="Authorization: Bearer $KEY"tail -n +2 posts.csv | while IFS=, read -r file_url text title accounts when; do MEDIA=$(curl -s -X POST "$API/media/from-url" -H "$AUTH" -H "Content-Type: application/json" -d "{\"url\":\"$file_url\"}" | jq -r .mediaId) until [ "$(curl -s "$API/media/$MEDIA" -H "$AUTH" | jq -r .status)" = "ready" ]; do sleep 10; done BODY=$(jq -n --arg a "$accounts" --arg m "$MEDIA" --arg t "$text" --arg ti "$title" --arg w "$when" \ '{targets:{accountIds:($a|split("|"))}, mediaIds:[$m], text:$t, title:$ti, when:{publishAt:$w, timezone:"Europe/Kiev"}}') PLAN=$(curl -s -X POST "$API/posts/plan" -H "$AUTH" -H "Content-Type: application/json" -d "$BODY") [ "$(echo "$PLAN" | jq -r .ok)" = "true" ] || { echo "$PLAN" | jq -r .table; continue; } curl -s -X POST "$API/posts" -H "$AUTH" -H "Content-Type: application/json" -d "$(echo "$BODY" | jq '.+{confirm:true}')" | jq -r '.posts[].statusLine'doneVerify
Section titled “Verify”Every row with status = queued has a post on the Calendar at its when; GET /posts?status=queued lists them with the same statusLine. Rows marked check are the ones a person has to fix - usually text over a network’s limit or a file the content type does not take.
Variations
Section titled “Variations”- Per-network text columns. Add
text_threads,text_facebookand put them inoverrideswhen not empty. - A group column instead of account ids:
"targets": {"groupId": "{{ $json.group }}"}. - Make or Zapier. The same five HTTP calls; Make and Zapier show the module and action names.
Related
Section titled “Related”- n8n - the credential and the HTTP Request node in detail.
- Uploading - what
from-urlaccepts (publichttps, size and format checks). - Automation overview