{
  "name": "Schedule and publish Instagram posts at the best time with Rubinyun",
  "nodes": [
    {
      "parameters": {
        "content": "## Schedule and publish Instagram posts at the best time\n\n**Who's it for**\n\nAnyone automating Instagram from n8n who needs a post to go out at a chosen time, including when their own machine is off. Images, carousels, reels and stories, on Instagram Business or Creator accounts.\n\n**How it works**\n\nThe Instagram API publishes immediately: there is no \"publish on Thursday at 6pm\", so something has to be awake at that minute to fire the call. This workflow hands that job to Rubinyun, which holds the queue and publishes for you.\n\n1. **Get the best time to post** returns the hour that performed best on your own account, worked out from your published posts. It also steps around the times you already have queued, so running this several times does not stack posts on the same minute.\n2. **Schedule the Instagram post** puts the post in the queue at that time.\n\n**How to set up**\n\n1. Create an account on chrononyte.com/rubinyun and connect Instagram once, there. Your Meta token never touches n8n.\n2. Open **API Keys** and copy your key and secret.\n3. In n8n add a **Header Auth** credential: name `Authorization`, value `Key YOUR_KEY:YOUR_SECRET`. Select it in both HTTP nodes.\n4. Put your own image URL and caption in *Schedule the Instagram post*.\n\n**Requirements**\n\nAn Instagram Business or Creator account linked to a Facebook Page, and a Rubinyun account. Free posts are included every month.\n\n**How to customize**\n\nChange `type` to `carousel`, `reel` or `story`. Add `channel` to publish on a second profile (`action=channels` lists them). No public URL for your media? Enable *Optional: upload a media file*: it takes binary data and answers with a public URL to use as the image URL.\n",
        "height": 1080,
        "width": 500
      },
      "id": "sticky-1",
      "name": "Read me first",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        40
      ]
    },
    {
      "parameters": {},
      "id": "trigger-1",
      "name": "Run manually to test",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "url": "https://www.chrononyte.com/rubinyun/api.php",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "action",
              "value": "besttime"
            },
            {
              "name": "when",
              "value": "tomorrow"
            }
          ]
        },
        "options": {}
      },
      "id": "http-besttime",
      "name": "Get the best time to post",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        680,
        300
      ],
      "notes": "Asks Rubinyun when to publish, from the engagement history of your own account.\n\nRun this flow several times in a row and you get different times: the answer steps around the posts already queued on that channel, so a loop does not pile everything onto the same minute. 'busy_skipped' in the response counts the slots it passed over.\n\nYou keep the last word: node 2 accepts any 'scheduled_at' you put there.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "method": "POST",
        "url": "https://www.chrononyte.com/rubinyun/api.php",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "action",
              "value": "add"
            }
          ]
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "type",
              "value": "image"
            },
            {
              "name": "image_url",
              "value": "https://www.chrononyte.com/assets/demo/rubinyun-demo.jpg"
            },
            {
              "name": "caption",
              "value": "Posted from n8n with Rubinyun"
            },
            {
              "name": "scheduled_at",
              "value": "={{ $json.slot.at }}"
            },
            {
              "name": "channel",
              "value": ""
            }
          ]
        },
        "options": {}
      },
      "id": "http-add",
      "name": "Schedule the Instagram post",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        920,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "method": "POST",
        "url": "https://www.chrononyte.com/rubinyun/upload.php",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "image",
              "inputDataFieldName": "data"
            }
          ]
        },
        "options": {}
      },
      "id": "http-upload",
      "name": "Optional: upload a media file",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        680,
        520
      ],
      "disabled": true,
      "notes": "Spare part, off by default and wired to nothing: enable it only if you have a FILE and no public URL for it.\n\nTo use it: connect a node that outputs the file as binary data (HTTP Request downloading it, Google Drive, your AI generator...) into this node, select the same Header Auth credential, and enable it. It answers {\"ok\":true,\"url\":\"...\"}, so in 'Schedule the Instagram post' set image_url to the expression {{ $json.url }}.\n\nField 'image' takes jpg, png, mp4 and mov (yes, 'image' for videos too). 'inputDataFieldName' is the name of the binary property coming in: n8n usually calls it 'data'.\n\nRubinyun keeps the file only as long as it takes to publish, then deletes it; files never attached to a post expire after 14 days. Parking is a pool per account, the TOTAL you can have parked at once: 200 MB on Free and packs, 2 GB with the €9 subscription, 5 GB with Unlimited. The reply includes parking.used_bytes and parking.limit_bytes so the workflow can check before hitting parking_full. Per file: images up to 8 MB, videos up to 100 MB. Channel keys work here too, and the file lands in that channel's own folder.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "content": "### Optional: you have a FILE, not a URL\n\nInstagram downloads the media from a public address, so a file on your disk cannot\nbe published as it is. Feed this node your file as binary data and enable it: it\nanswers with a public URL to put in the node on the right.\n\nThe file is deleted the moment the post is published, and expires on its own if it\nnever is: it is parking, not hosting.",
        "height": 260,
        "width": 420,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        620,
        640
      ],
      "id": "sticky-upload-note",
      "name": "About the optional upload"
    }
  ],
  "connections": {
    "Run manually to test": {
      "main": [
        [
          {
            "node": "Get the best time to post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get the best time to post": {
      "main": [
        [
          {
            "node": "Schedule the Instagram post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "instanceId": "rubinyun-template"
  }
}