{
  "name": "Pushyou · Content approval (social post)",
  "nodes": [
    {
      "parameters": {},
      "id": "manual",
      "name": "Manual trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "id": "schedule",
      "name": "Daily schedule (off)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        200
      ],
      "disabled": true
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "api_url",
              "name": "api_url",
              "value": "https://asia-northeast3-pushyou-prod.cloudfunctions.net/pushyouApi",
              "type": "string"
            },
            {
              "id": "conversation_id",
              "name": "conversation_id",
              "value": "YOUR_CONVERSATION_ID",
              "type": "string"
            },
            {
              "id": "post_id",
              "name": "post_id",
              "value": "={{ 'post-' + $now.toFormat('yyyy-LL-dd') }}",
              "type": "string"
            },
            {
              "id": "platform",
              "name": "platform",
              "value": "instagram",
              "type": "string"
            },
            {
              "id": "attachment_id",
              "name": "attachment_id",
              "value": "",
              "type": "string"
            },
            {
              "id": "link",
              "name": "link",
              "value": "",
              "type": "string"
            },
            {
              "id": "expires_at",
              "name": "expires_at",
              "value": "={{ Date.now() + 24 * 60 * 60 * 1000 }}",
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "settings",
      "name": "Post settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "caption",
              "name": "caption",
              "value": "Replace this node with your AI or template step. Its caption output is sent for approval.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "draft",
      "name": "Draft the post (replace)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Optional image upload, step 1 of 3. Enable these three nodes and provide the image as binary 'data'.\n// Requires NODE_FUNCTION_ALLOW_BUILTIN=crypto in n8n.\nconst crypto = require('crypto');\nconst item = $input.first();\nconst bytes = await this.helpers.getBinaryDataBuffer(0, 'data');\nreturn [{ json: { ...item.json, media_id: $('Post settings').item.json.post_id + '-image', filename: item.binary.data.fileName || 'image.jpg', content_type: item.binary.data.mimeType, size: bytes.length, sha256: crypto.createHash('sha256').update(bytes).digest('hex') }, binary: item.binary }];"
      },
      "id": "describe",
      "name": "Describe image (optional)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ],
      "disabled": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Post settings').item.json.api_url + '/v1/conversations/' + encodeURIComponent($('Post settings').item.json.conversation_id) + '/media/uploads' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 15000
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ media_id: $json.media_id, filename: $json.filename, content_type: $json.content_type, size: $json.size, sha256: $json.sha256 }) }}"
      },
      "id": "prepare",
      "name": "Prepare image upload (optional)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        0
      ],
      "disabled": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.upload.url }}",
        "sendHeaders": true,
        "specifyHeaders": "json",
        "jsonHeaders": "={{ JSON.stringify($json.upload.headers) }}",
        "sendBody": true,
        "contentType": "binaryData",
        "inputDataFieldName": "data",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 60000
        }
      },
      "id": "upload",
      "name": "Upload image bytes (optional)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        0
      ],
      "disabled": true
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Post settings').item.json.api_url + '/v1/conversations/' + encodeURIComponent($('Post settings').item.json.conversation_id) + '/tasks' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 15000
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ id: $('Post settings').item.json.post_id, title: 'Social post · ' + $('Post settings').item.json.platform, body: 'Drafted by n8n. Approve to publish, or edit the caption first.', kind: 'social-post', category_id: 'work', expires_at: $('Post settings').item.json.expires_at, preview: { platform: $('Post settings').item.json.platform, caption: $('Draft the post (replace)').item.json.caption, attachment_ids: $('Post settings').item.json.attachment_id ? [$('Post settings').item.json.attachment_id] : [], ...($('Post settings').item.json.link ? { link: $('Post settings').item.json.link } : {}) } }) }}"
      },
      "id": "create",
      "name": "Pushyou content approval",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        0
      ]
    },
    {
      "parameters": {
        "amount": 1,
        "unit": "minutes"
      },
      "id": "wait",
      "name": "Wait 1 minute",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1540,
        0
      ],
      "webhookId": "00000000-0000-4000-8000-000000000001"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $('Post settings').item.json.api_url + '/v1/conversations/' + encodeURIComponent($('Post settings').item.json.conversation_id) + '/tasks' + '/' + encodeURIComponent($('Post settings').item.json.post_id) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 15000
        }
      },
      "id": "status",
      "name": "Get decision",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1760,
        0
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.task.status }}",
                    "rightValue": "queued",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "approved"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.task.status }}",
                    "rightValue": "changes_requested",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "changes requested"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "leftValue": "={{ $json.task.status }}",
                    "rightValue": "pending",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "waiting"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "closed"
        }
      },
      "id": "switch",
      "name": "Route by decision",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1980,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Persist this token before claiming: n8n keeps it in the execution data.\nconst crypto = require('crypto');\nconst task = $input.first().json.task;\nreturn [{ json: { task, attempt: task.attempt, request_id: 'claim-' + task.attempt + '-' + crypto.randomUUID(), execution_token: crypto.randomBytes(32).toString('base64url') } }];"
      },
      "id": "token",
      "name": "Create execution token",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2200,
        -200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Post settings').item.json.api_url + '/v1/conversations/' + encodeURIComponent($('Post settings').item.json.conversation_id) + '/tasks' + '/' + encodeURIComponent($('Post settings').item.json.post_id) + '/claim' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 15000
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ attempt: $json.attempt, request_id: $json.request_id, execution_token: $json.execution_token }) }}"
      },
      "id": "claim",
      "name": "Claim approved post",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2420,
        -200
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "published_caption",
              "name": "published_caption",
              "value": "={{ $json.task.preview.caption }}",
              "type": "string"
            },
            {
              "id": "published_url",
              "name": "published_url",
              "value": "",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "publish",
      "name": "Publish (replace)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2640,
        -200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Post settings').item.json.api_url + '/v1/conversations/' + encodeURIComponent($('Post settings').item.json.conversation_id) + '/tasks' + '/' + encodeURIComponent($('Post settings').item.json.post_id) + '/complete' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 15000
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ attempt: $('Create execution token').item.json.attempt, request_id: 'complete-' + $('Create execution token').item.json.request_id, execution_token: $('Create execution token').item.json.execution_token, status: 'succeeded', result: 'Published the approved caption (replace with your publish step).', ...($json.published_url ? { result_links: [{ label: 'Post', url: $json.published_url }] } : {}) }) }}"
      },
      "id": "complete",
      "name": "Report result",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2860,
        -200
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "caption",
              "name": "caption",
              "value": "={{ $('Draft the post (replace)').item.json.caption + '\\n\\n(Revised: ' + $json.task.change_request + ')' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "revise_draft",
      "name": "Revise draft (replace)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2200,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Post settings').item.json.api_url + '/v1/conversations/' + encodeURIComponent($('Post settings').item.json.conversation_id) + '/tasks' + '/' + encodeURIComponent($('Post settings').item.json.post_id) + '/revise' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 15000
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ request_id: 'revise-' + $('Get decision').item.json.task.revision, expected_revision: $('Get decision').item.json.task.revision, change_request_id: $('Get decision').item.json.task.change_request_id, body: $('Get decision').item.json.task.body, preview: { ...$('Get decision').item.json.task.preview, attachment_ids: ($('Get decision').item.json.task.preview.attachments || []).map((file) => file.id), attachments: undefined, caption: $json.caption } }) }}"
      },
      "id": "revise",
      "name": "Send revision",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2420,
        0
      ]
    },
    {
      "parameters": {},
      "id": "closed",
      "name": "Closed without publishing",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        2200,
        200
      ]
    },
    {
      "parameters": {
        "content": "## Pushyou content approval\nImport inactive. Set **Post settings** (API URL, conversation ID) and one **HTTP Header Auth** credential on every Pushyou HTTP node: Name `Authorization`, Value `Bearer <program key>`. Never paste the key into this workflow.\n\nThe owner reviews the platform-styled draft in Pushyou, can edit the caption and approves that exact version. **Publish (replace)** receives the approved caption from the claim response. A new run needs a new post_id.\n\nInstead of polling, you can configure a Pushyou response webhook for `task.updated` and resume from a Webhook trigger.",
        "height": 300,
        "width": 420
      },
      "id": "note",
      "name": "Setup",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -400
      ]
    }
  ],
  "connections": {
    "Manual trigger": {
      "main": [
        [
          {
            "node": "Post settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily schedule (off)": {
      "main": [
        [
          {
            "node": "Post settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post settings": {
      "main": [
        [
          {
            "node": "Draft the post (replace)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft the post (replace)": {
      "main": [
        [
          {
            "node": "Describe image (optional)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Describe image (optional)": {
      "main": [
        [
          {
            "node": "Prepare image upload (optional)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare image upload (optional)": {
      "main": [
        [
          {
            "node": "Upload image bytes (optional)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload image bytes (optional)": {
      "main": [
        [
          {
            "node": "Pushyou content approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pushyou content approval": {
      "main": [
        [
          {
            "node": "Wait 1 minute",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 1 minute": {
      "main": [
        [
          {
            "node": "Get decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get decision": {
      "main": [
        [
          {
            "node": "Route by decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by decision": {
      "main": [
        [
          {
            "node": "Create execution token",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Revise draft (replace)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait 1 minute",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Closed without publishing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create execution token": {
      "main": [
        [
          {
            "node": "Claim approved post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claim approved post": {
      "main": [
        [
          {
            "node": "Publish (replace)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Publish (replace)": {
      "main": [
        [
          {
            "node": "Report result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Revise draft (replace)": {
      "main": [
        [
          {
            "node": "Send revision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send revision": {
      "main": [
        [
          {
            "node": "Wait 1 minute",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "tags": []
}
