{
  "nodes": [
    {
      "id": "6153cff6-ceeb-4c2d-b4d9-1e643448efd8",
      "name": "Sticky - Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1056,
        64
      ],
      "parameters": {
        "color": 5,
        "width": 1824,
        "height": 108,
        "content": "## \ud83d\ude80 Swonkie \u2013 Publish a Social Media Post\nThis workflow publishes a post to a Swonkie-connected social profile via the **Swonkie Public API**. It covers the full flow: upload media \u2192 create post \u2192 validate \u2192 schedule or publish immediately.\n**Before running:** open the **Configure** node and fill in your credentials and post settings. Get your credentials at [app.swonkie.com/settings/workspace/public-api](https://app.swonkie.com/settings/workspace/public-api) and your Profile ID from the GET /profiles endpoint."
      },
      "typeVersion": 1
    },
    {
      "id": "bc3ef4fe-205c-4c33-932c-e38b36e421e2",
      "name": "Sticky - Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1056,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 450,
        "content": "## \u2699\ufe0f Setup - Edit This Node First\n\nOpen the **Configure** node below and fill in:\n\n- **apiId**: Your App ID\n- **apiKey**: Your API Key\n- **profileId**: Target social profile ID\n- **caption**: Post text\n- **stage**: `publishNow` (publish immediately, default) or `schedule`\n- **mediaUrl**: Public URL of the image/video to upload\n- **mediaName**: Filename, e.g. `photo.jpg`\n\n---\n\u26a0\ufe0f **Security note:** Credentials are stored as plain text in the workflow JSON and visible in execution logs. For production use, create a **Generic Credential \u2192 Header Auth** in n8n's Credentials panel with `X-API-ID` and `X-API-KEY` as headers, then reference it in each HTTP Request node instead."
      },
      "typeVersion": 1
    },
    {
      "id": "60b5ff29-bfde-40e1-b349-5998431c8d8d",
      "name": "Sticky - Media Upload",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -640,
        192
      ],
      "parameters": {
        "color": 2,
        "width": 416,
        "height": 452,
        "content": "## \ud83d\udce4 Media Upload\nSwonkie requires a 3-step upload before a file can be attached to a post:\n1. **Create Media Entry**: registers the file with Swonkie, returns an `uploadUrl` and a `mediaId`\n2. **Download & Attach Binary**: fetches the file from `mediaUrl` as binary data in n8n\n3. **Upload File to Blob**: PUTs the binary to Azure Blob Storage via the `uploadUrl`\n4. **Confirm Upload**: notifies Swonkie the upload is complete and starts async processing\n5. **Poll loop**: waits 3 s, polls `status` \u2192 `SUCCESS` continues, `ERROR` stops, anything else loops back"
      },
      "typeVersion": 1
    },
    {
      "id": "b6d67b37-3818-4d1d-9c89-b238deb1aa0a",
      "name": "Sticky - Post Publishing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        192
      ],
      "parameters": {
        "color": 5,
        "width": 452,
        "height": 452,
        "content": "## \ud83d\udcdd Post Publishing\n1. **Create Post**: creates an IDEA post with the uploaded media, caption, and profile\n2. **Validate Post**: checks the post meets all network rules: caption length, media format, profile connection status\n3. **Change Stage**: moves the post to `publishNow` (publishes immediately, default) or `schedule` (only use if you first set a `publishAt` date on the post via the API)\n\nThe final **Post Published** node outputs `postId`, `stage`, and a `success: true` flag for use in downstream nodes."
      },
      "typeVersion": 1
    },
    {
      "id": "02a8db34-c1ae-4ed5-9a8e-02dcde07aa72",
      "name": "Sticky - Error Handling",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        192
      ],
      "parameters": {
        "color": 3,
        "width": 484,
        "height": 444,
        "content": "## \u274c Error Handling\nBoth failure paths use the **Stop and Error** node, which marks the execution as **failed** (shown in red in n8n's execution history) and triggers any error workflow configured under *Workflow Settings \u2192 Error Workflow*.\n\n- **Media Processing Failed**: Swonkie returned `status: ERROR` during async processing. The file was uploaded successfully but could not be processed, it may be corrupt, too large, in an unsupported format, or have incompatible specs (codec, resolution, duration). Try re-uploading a different file or check the media in your Swonkie media library for details.\n- **Validation Failed**: the post did not pass Swonkie's validation rules before publishing. The error message contains the full API response, check caption length limits, media requirements, and whether the profile is properly connected."
      },
      "typeVersion": 1
    },
    {
      "id": "e018d93b-1594-467f-a14c-6c0c7163a82b",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -992,
        864
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1662902f-26d7-47c4-9054-2c6296e61147",
      "name": "Configure",
      "type": "n8n-nodes-base.set",
      "notes": "Fill in your credentials and post settings here.",
      "position": [
        -768,
        864
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-001",
              "name": "apiBase",
              "type": "string",
              "value": "https://api.swonkie.dev/v2"
            },
            {
              "id": "cfg-002",
              "name": "apiId",
              "type": "string",
              "value": "YOUR_APP_ID_HERE"
            },
            {
              "id": "cfg-003",
              "name": "apiKey",
              "type": "string",
              "value": "YOUR_API_KEY_HERE"
            },
            {
              "id": "cfg-004",
              "name": "profileId",
              "type": "string",
              "value": "YOUR_PROFILE_ID_HERE"
            },
            {
              "id": "cfg-005",
              "name": "caption",
              "type": "string",
              "value": "Your post caption here \ud83d\ude80 #Swonkie"
            },
            {
              "id": "cfg-007",
              "name": "stage",
              "type": "string",
              "value": "publishNow"
            },
            {
              "id": "cfg-008",
              "name": "mediaUrl",
              "type": "string",
              "value": "https://picsum.photos/800/600"
            },
            {
              "id": "cfg-009",
              "name": "mediaName",
              "type": "string",
              "value": "post-image.png"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "515d8a5c-7a91-4236-a874-6916c559e3d9",
      "name": "Create Media Entry",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Registers the media file in Swonkie and returns an upload URL.",
      "position": [
        -480,
        816
      ],
      "parameters": {
        "url": "={{ $('Configure').item.json.apiBase }}/media",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ destinationType: 'POST_MEDIA', name: $('Configure').item.json.mediaName }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-ID",
              "value": "={{ $('Configure').item.json.apiId }}"
            },
            {
              "name": "X-API-KEY",
              "value": "={{ $('Configure').item.json.apiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "c66da8de-a070-4dfb-8e03-0103165801cb",
      "name": "Download & Attach Binary",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Downloads the media file from the configured URL and attaches it as binary data (field name: data).",
      "position": [
        -272,
        816
      ],
      "parameters": {
        "url": "={{ $('Configure').item.json.mediaUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "53325d9b-5022-4ebc-9280-5b2c8f13ac22",
      "name": "Upload File to Blob",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Uploads the binary file to the Azure Blob Storage URL returned by Create Media Entry.",
      "position": [
        -48,
        816
      ],
      "parameters": {
        "url": "={{ $('Create Media Entry').item.json.uploadUrl }}",
        "method": "PUT",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendBody": true,
        "contentType": "binaryData",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-ms-blob-type",
              "value": "BlockBlob"
            }
          ]
        },
        "inputDataFieldName": "data"
      },
      "typeVersion": 4.2
    },
    {
      "id": "95f716d2-eccc-436b-961f-70e63edd7b7c",
      "name": "Confirm Upload",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Notifies Swonkie that the file upload is complete and processing should begin.",
      "position": [
        176,
        816
      ],
      "parameters": {
        "url": "={{ $('Configure').item.json.apiBase + '/media/' + $('Create Media Entry').item.json.id }}",
        "method": "PATCH",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-ID",
              "value": "={{ $('Configure').item.json.apiId }}"
            },
            {
              "name": "X-API-KEY",
              "value": "={{ $('Configure').item.json.apiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0347a11f-5710-48fb-b375-208107c133a6",
      "name": "Wait for Processing",
      "type": "n8n-nodes-base.wait",
      "notes": "Waits 3 seconds before polling media status. Loops until status is SUCCESS.",
      "position": [
        400,
        816
      ],
      "parameters": {
        "amount": 3
      },
      "typeVersion": 1.1
    },
    {
      "id": "f0ee4557-fe53-4774-878c-cc988b2d3eed",
      "name": "Check Media Status",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Polls the media status. Expected values: PROCESSING, SUCCESS, ERROR.",
      "position": [
        608,
        816
      ],
      "parameters": {
        "url": "={{ $('Configure').item.json.apiBase + '/media/' + $('Create Media Entry').item.json.id }}",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-ID",
              "value": "={{ $('Configure').item.json.apiId }}"
            },
            {
              "name": "X-API-KEY",
              "value": "={{ $('Configure').item.json.apiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "4fa24821-084e-45dd-bdd4-77d845a5eb5e",
      "name": "Media Ready?",
      "type": "n8n-nodes-base.switch",
      "notes": "Output 0 (SUCCESS) \u2192 Create Post. Output 1 (ERROR) \u2192 Media Processing Failed. Fallback (PROCESSING) \u2192 loop back to Wait.",
      "position": [
        832,
        816
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "SUCCESS",
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cond-success",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "SUCCESS"
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "ERROR",
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "cond-error",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.status }}",
                    "rightValue": "ERROR"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "65a37632-92bb-4b16-96ef-9fc08f0dd96b",
      "name": "Create Post",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Creates the post with the uploaded media. Stage will be IDEA (no publishAt date set).",
      "position": [
        1312,
        816
      ],
      "parameters": {
        "url": "={{ $('Configure').item.json.apiBase }}/posts",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ type: 'MEDIA', profileIds: [$('Configure').item.json.profileId], captions: [{ plainText: $('Configure').item.json.caption, net: null }], medias: [{ mediaFiles: [{ mediaLibId: $('Create Media Entry').item.json.id }], net: null }], links: [], labelIds: [] }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-ID",
              "value": "={{ $('Configure').item.json.apiId }}"
            },
            {
              "name": "X-API-KEY",
              "value": "={{ $('Configure').item.json.apiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "12e28f73-403b-4459-98f6-df6cf66ce169",
      "name": "Validate Post",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Validates the post before publishing. Returns { valid: true } or a 400 with errors.",
      "position": [
        1536,
        816
      ],
      "parameters": {
        "url": "={{ $('Configure').item.json.apiBase + '/posts/' + $json.id + '/validate' }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-ID",
              "value": "={{ $('Configure').item.json.apiId }}"
            },
            {
              "name": "X-API-KEY",
              "value": "={{ $('Configure').item.json.apiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "618aaa2a-b0be-441b-9799-3ce858703b64",
      "name": "Post Valid?",
      "type": "n8n-nodes-base.if",
      "notes": "TRUE \u2192 change stage to schedule/publishNow. FALSE \u2192 stop with validation error details.",
      "position": [
        1744,
        816
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-post-valid",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.valid }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "41a6c82b-ae0d-4dd2-98c0-785f79fc4b19",
      "name": "Change Stage",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Moves the post to the configured stage (schedule or publishNow). Set in Configure node.",
      "position": [
        2288,
        864
      ],
      "parameters": {
        "url": "={{ $('Configure').item.json.apiBase + '/posts/' + $('Create Post').item.json.id + '/stage/' + $('Configure').item.json.stage }}",
        "method": "PATCH",
        "options": {},
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-API-ID",
              "value": "={{ $('Configure').item.json.apiId }}"
            },
            {
              "name": "X-API-KEY",
              "value": "={{ $('Configure').item.json.apiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "50d3d293-2f4b-4b9b-8c8d-84776ab3b07f",
      "name": "Post Published",
      "type": "n8n-nodes-base.set",
      "notes": "Workflow completed successfully.",
      "position": [
        2512,
        864
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "out-001",
              "name": "success",
              "type": "boolean",
              "value": true
            },
            {
              "id": "out-002",
              "name": "message",
              "type": "string",
              "value": "Post successfully handed off to Swonkie."
            },
            {
              "id": "out-003",
              "name": "postId",
              "type": "string",
              "value": "={{ $('Create Post').item.json.id }}"
            },
            {
              "id": "out-004",
              "name": "stage",
              "type": "string",
              "value": "={{ $('Configure').item.json.stage }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7df1da80-b229-4647-b488-f8f323ae6752",
      "name": "Media Processing Failed",
      "type": "n8n-nodes-base.stopAndError",
      "notes": "Media processing returned ERROR status. The file was uploaded but Swonkie's async processor could not handle it. It may be corrupt, too large, in an unsupported format, or have incompatible specs. Try re-uploading a different file.",
      "position": [
        1088,
        944
      ],
      "parameters": {
        "errorMessage": "=Media processing failed for mediaId {{ $('Create Media Entry').item.json.id }}. The file may be corrupt or in an unsupported format."
      },
      "typeVersion": 1
    },
    {
      "id": "9676ef6b-deb7-4c11-a7a6-e917e7a81257",
      "name": "Validation Failed",
      "type": "n8n-nodes-base.stopAndError",
      "notes": "Post failed validation. The error message contains details from the Swonkie API.",
      "position": [
        1984,
        928
      ],
      "parameters": {
        "errorMessage": "=Post validation failed for postId {{ $('Create Post').item.json.id }}. Details: {{ JSON.stringify($json) }}"
      },
      "typeVersion": 1
    },
    {
      "id": "f7481383-ca5f-4f5d-904b-97730e25806b",
      "name": "Sticky - Media Upload1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        672
      ],
      "parameters": {
        "color": 2,
        "width": 1776,
        "height": 484,
        "content": "## Media Upload\n"
      },
      "typeVersion": 1
    },
    {
      "id": "80ea18ff-59e5-465e-8d6a-55ddfa7b81e2",
      "name": "Sticky - Post Publishing1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        672
      ],
      "parameters": {
        "color": 5,
        "width": 884,
        "height": 484,
        "content": "## Create Post"
      },
      "typeVersion": 1
    },
    {
      "id": "5da6d3d3-0e05-41e4-a205-c7373d4ad796",
      "name": "Sticky - Post Publishing2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        672
      ],
      "parameters": {
        "color": 4,
        "width": 564,
        "height": 484,
        "content": "## Publish Post"
      },
      "typeVersion": 1
    },
    {
      "id": "e0ab6c97-bb2f-421a-b6db-994a41b4ef24",
      "name": "Sticky - Post Publishing3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1056,
        672
      ],
      "parameters": {
        "color": 7,
        "width": 484,
        "height": 484,
        "content": "## Trigger & Configure"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Configure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure": {
      "main": [
        [
          {
            "node": "Create Media Entry",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Post": {
      "main": [
        [
          {
            "node": "Validate Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Valid?": {
      "main": [
        [
          {
            "node": "Change Stage",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Validation Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Change Stage": {
      "main": [
        [
          {
            "node": "Post Published",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Media Ready?": {
      "main": [
        [
          {
            "node": "Create Post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Media Processing Failed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait for Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Post": {
      "main": [
        [
          {
            "node": "Post Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Confirm Upload": {
      "main": [
        [
          {
            "node": "Wait for Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Media Status": {
      "main": [
        [
          {
            "node": "Media Ready?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Media Entry": {
      "main": [
        [
          {
            "node": "Download & Attach Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload File to Blob": {
      "main": [
        [
          {
            "node": "Confirm Upload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Processing": {
      "main": [
        [
          {
            "node": "Check Media Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download & Attach Binary": {
      "main": [
        [
          {
            "node": "Upload File to Blob",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}