Seedance 2.5 from ByteDance is officially available on Apiframe.

How to Use Apiframe in n8n (Step-by-Step Guide)

Learn how to use Apiframe on N8N and automate content creation. From Getting your Apiframe API key to creating workflows.

Renaud Last updated September 4, 2026 November 30, 2025 · 5 min read Beginner
How to Use Apiframe in n8n (Step-by-Step Guide)

In this guide, you will learn how to connect Apiframe to n8n so you can generate AI images (or videos, music, etc.) from any workflow. If you are still comparing image models and providers, the AI Image API guide covers the wider landscape.

💡
Heads up on the screenshots below: they were captured against Apiframe's earlier API, so a few field names in the images differ from the text. The n8n steps themselves are unchanged. Follow the text, which reflects the current v2 API: X-API-Key authentication, POST /v2/images/generate, jobId, and webhookUrl / webhookEvents.

We will cover:

  1. What we're building
  2. Prerequisites
  3. Creating Apiframe credentials in n8n
  4. Workflow 1 - Simple image generation using /v2/images/generate
  5. Workflow 2 - Polling results with /v2/jobs
  6. Workflow 3 - Recommended: Using webhooks for real-time results
  7. Extending the pattern to other Apiframe endpoints

All examples will use Midjourney through the /v2/images/generate endpoint, but the same pattern works for most of the other endpoints and models.

I. What we're going to build

We’ll build two small n8n integrations:

  1. Generate an image on demand
    • Trigger: Manual, Webhook, Google Sheet, anything
    • HTTP Request: POST https://api.apiframe.ai/v2/images/generate
    • Get back a jobId that you can store or log
  2. Get the final images automatically
    • By polling Apiframe with GET /v2/jobs/{jobId} until the job is complete
    • Or (recommended) let Apiframe call n8n Webhook when the job is complete

Once the image URLs land in n8n, you can do anything: send them to Slack, store them in Airtable, Google Drive, etc.

II. Prerequisites

You will need:

  • An Apiframe account and an API key. You can grab this from your API keys page in the dashboard. Your key starts with afk_ and it authenticates every request through the X-API-Key header. If this is your first time calling the API, run through Getting Started with Apiframe first.
Apiframe API key
Apiframe API key
  • An n8n instance (self-hosted or cloud)
  • Basic familiarity with n8n nodes (HTTP Request, Webhook, Set, IF, etc.). The HTTP Request node is how n8n calls any REST API.

III. Create Apiframe credentials in n8n

We’ll configure credentials once, then reuse them in all HTTP Request nodes.

  • Step 1: In n8n, go to Credentials → Create credential.
Create credential
Create credential
  • Step 2: Choose Header Auth (or "HTTP Header Auth", "API Key in Header" depending on your version)
Header Auth
Header Auth
  • Step 3: Configure:
    • Header name: X-API-Key
    • Value: your Apiframe API key, exactly as shown in your dashboard (it begins with afk_)
Apiframe header auth
Apiframe header auth
  • Step 4: Give it a name like Apiframe Auth and save

Apiframe expects:

text
X-API-Key: afk_your_api_key_here
Content-Type: application/json

IV. Workflow 1 - Basic image generation with /v2/images/generate

We’ll build a simple workflow:

💡
Manual Trigger → Set prompt → HTTP Request → Log jobId

1. Create the workflow

    1. In n8n, create a New workflow.
    2. Add a Manual Trigger node.
manual trigger
manual trigger

2. Add a "Set" node for the prompt

    1. Add a Set node after the Manual Trigger.
    2. In Values → Add Field → String:
      • Name: prompt
      • Value: something like a cinematic photo of a cyberpunk city at night, ultra detailed, 4k. For stronger results, borrow the structure from The Complete Midjourney Prompt Guide.
    3. (Optional) Add another string field:
      • Name: aspect_ratio
      • Value: 3:2
Set Node
Set Node

Now the Set node’s output JSON looks roughly like:

json
{
  "prompt": "a cinematic photo of a cyberpunk city at night, ultra detailed, 4k",
  "aspect_ratio": "3:2"
}

3. Add the HTTP Request node for /v2/images/generate

    1. Add an HTTP Request node after the Set node.
    2. Configure:
      • Method: POST
      • URL: https://api.apiframe.ai/v2/images/generate
      • For authentication, choose the "Generic Credential type", then "Header Auth", then the “Apiframe Auth” credentials you created earlier.
      • For the body, turn on "Send body", and let's add our fields: prompt, model (set it to midjourney), a midjourneyParams object holding aspect_ratio, and optionally webhookUrl and webhookEvents for later.
HTTP Request Node
HTTP Request Node
Request body
Request body

When you execute this node, Apiframe returns something like:

json
{
  "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "QUEUED"
}

This means the task is queued/processing. Images are generated asynchronously; you don’t get the final URLs from /v2/images/generate itself.

You can now:

  • Log the jobId
  • Store it in a DB / Google Sheet
  • Pass it forward to a job-polling workflow

V. Workflow 2 - Polling Apiframe with /v2/jobs

Now let’s get the actual image URLs using the /v2/jobs endpoint.

Apiframe exposes a GET https://api.apiframe.ai/v2/jobs/{jobId} endpoint. Pass the jobId in the URL path and it returns the current state of the job. A job moves through four statuses: QUEUED, PROCESSING, COMPLETED, and FAILED. The image URLs only appear once the status is COMPLETED.

We’ll do a minimal “Wait then Poll” flow.

1. Add a Wait node

After the /v2/images/generate HTTP Request node:

    1. Add a Wait node.
    2. Set it to wait, for example, 30 seconds.

A Midjourney generation usually takes 30 to 60 seconds, and busy periods can take longer. A short wait will almost always come back as QUEUED or PROCESSING, so plan on polling more than once.

Wait node
Wait node

2. Add the job-polling HTTP Request node

Add another HTTP Request node after the Wait node:

    • Method: GET
    • URL: https://api.apiframe.ai/v2/jobs/{{ $json.jobId }}
    • For authentication, choose the "Generic Credential type", then "Header Auth", then the “Apiframe Auth”, like before.
    • No body is needed here. The job ID travels in the URL path, so leave "Send body" off and just reference the jobId returned by the previous node
Fetch Request Node
Fetch Request Node
Fetch request body
Fetch request body

Processing (job still running):

json
{
  "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "PROCESSING",
  "model": "midjourney",
  "progress": 40
}

Completed (job done, image URLs ready):

json
{
  "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "COMPLETED",
  "model": "midjourney",
  "creditCost": 10,
  "result": {
    "images": [
      "https://cdn2.apiframe.ai/images/a1b2c3d4-1.png",
      "https://cdn2.apiframe.ai/images/a1b2c3d4-2.png",
      "https://cdn2.apiframe.ai/images/a1b2c3d4-3.png",
      "https://cdn2.apiframe.ai/images/a1b2c3d4-4.png"
    ],
    "gridUrl": "https://cdn2.apiframe.ai/images/a1b2c3d4-grid.png"
  }
}

3. Handling “still processing”

For a quick dev setup, you can:

  • Just wait longer and poll once.
  • Or add a simple IF node after the poll:
    • Condition: status is still "QUEUED" or "PROCESSING"
    • If “true”: branch to another Wait + Poll
    • If “false”: continue with your final logic (Slack, Airtable, etc.)

In production, Apiframe recommends using webhooks instead of polling to avoid unnecessary requests and get instant updates.

Let’s do that next.

This is the cleaner setup, and results arrive the moment they are ready:

  • Workflow A: Send generation request (with webhookUrl and webhookEvents)
  • Workflow B: Receive webhook from Apiframe when generation is done

1. Create Workflow B - The webhook receiver

    1. Create a New workflow in n8n and name it Apiframe Image Completed.
    2. Add a Webhook node. Configure the Webhook node:
      • HTTP Method: POST
      • Path: something like apiframe/midjourney-completed
      • Response mode:
        • For example, When Last Node Finishes (so you can return data back if you want).

Copy the Production URL. This is what you will set as webhookUrl in Apiframe.

Webhook node
Webhook node

2. Verify the webhook signature

Apiframe signs every webhook call. Each request arrives with an X-Webhook-Signature header, which is a fingerprint of the exact request body, and an X-Webhook-Event header naming the event (completed, failed, or progress). The signing secret is not your API key. It is the SHA-256 hash of your API key, written as a hex string, and the signature itself is prefixed with sha256=. Signature checks are one of several habits worth adopting, see AI Media API Security Best Practices.

    1. Add a Code node after the Webhook node (an IF node can't compute an HMAC).
    2. In the Code node, recompute the signature over the raw body and compare it against the header:
javascriptn8n Code node
const crypto = require('crypto');

// The signing secret is the SHA-256 hash of your API key, as a hex string.
const signingSecret = crypto
  .createHash('sha256')
  .update('afk_your_api_key_here')
  .digest('hex');

// Requires "Raw Body" enabled on the Webhook node.
const raw = $json.rawBody ?? JSON.stringify($json.body);

const expected =
  'sha256=' +
  crypto.createHmac('sha256', signingSecret).update(raw).digest('hex');

const received = $json.headers['x-webhook-signature'] ?? '';

const a = Buffer.from(expected);
const b = Buffer.from(received);

if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
  throw new Error('Invalid webhook signature');
}

return $input.all();
      • Turn on "Raw Body" in the Webhook node, so you hash the exact bytes Apiframe signed rather than a re-serialized copy.
      • Store the API key in an n8n credential or environment variable rather than pasting it into the Code node.
      • Compare the two values with crypto.timingSafeEqual instead of ===. A plain comparison finishes faster on an early mismatch, which can leak clues about the real signature.
    • If the signature doesn’t match, throw (as above) or route to a branch that just ends and logs the attempt.
Secure webhooks
Secure webhooks

This ensures only Apiframe’s webhooks are processed.

3. Accessing the image URLs in the webhook payload

The webhook body wraps the same result object shown earlier, alongside the event name and job metadata:

json
{
  "event": "completed",
  "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "COMPLETED",
  "progress": 100,
  "model": "midjourney",
  "creditCost": 10,
  "completedAt": "2026-08-17T09:14:22.000Z",
  "result": {
    "images": [
      "https://cdn2.apiframe.ai/images/a1b2c3d4-1.png",
      "https://cdn2.apiframe.ai/images/a1b2c3d4-2.png",
      "https://cdn2.apiframe.ai/images/a1b2c3d4-3.png",
      "https://cdn2.apiframe.ai/images/a1b2c3d4-4.png"
    ],
    "gridUrl": "https://cdn2.apiframe.ai/images/a1b2c3d4-grid.png"
  }
}

You can access the image URLs, then pass them into:

  • Slack node (send a message with the URL)
  • Airtable / Notion (store the URL)
  • HTTP Request (push to your app’s backend)

4. Update Workflow A to use the webhook

Return to Workflow A (the one calling /v2/images/generate) and edit the HTTP Request node body to include:

  • webhookUrl
  • webhookEvents

The available events are progress, completed, and failed. If you set webhookUrl and leave webhookEvents out, Apiframe defaults to completed and failed.

Added webhook fields
Added webhook fields

Example JSON body in the HTTP Request node:

json
{
  "prompt": "a cinematic photo of a cyberpunk city at night, ultra detailed, 4k",
  "model": "midjourney",
  "midjourneyParams": {
    "aspect_ratio": "3:2"
  },
  "webhookUrl": "https://your-n8n-domain.com/webhook/apiframe/midjourney-completed",
  "webhookEvents": ["completed", "failed"]
}

Now the flow is:

  1. Workflow A → /v2/images/generate with webhookUrl + webhookEvents
  2. Apiframe generates the image in the background
  3. When done, Apiframe calls your Webhook (Workflow B) with the final URLs
  4. Workflow B processes them and pushes them wherever you want

No polling and no wasted requests. Apiframe tells n8n the moment the job is done.

VII. Extending this pattern to other Apiframe endpoints

Once n8n and Apiframe are connected, the same three steps work for every other endpoint: send a request, get a jobId, then poll or wait for a webhook.

Some ideas:

  • Follow-up actions: POST https://api.apiframe.ai/v2/images/midjourney/action, sending the completed job's parentJobId plus an action (upsample, variation, inpaint, outpaint, or pan). These act on a finished generation instead of a new prompt.
  • Upscaling: POST https://api.apiframe.ai/v2/images/upscale with the image you want enlarged.
  • Background removal: POST https://api.apiframe.ai/v2/images/background-remove.
  • Image editing: POST https://api.apiframe.ai/v2/images/edit for fill and inpaint style edits.
  • Other media: Flux, Ideogram, Luma, and Suno all follow the same REST pattern. POST to /v2/images/generate, /v2/videos/generate, or /v2/music/generate with JSON, including webhookUrl and webhookEvents if you want webhooks.
  • Video: the same three steps work for video. Send a POST to /v2/videos/generate, then poll or wait for the webhook. The AI Video Generation API guide walks through the model choices.

Each of these becomes just another HTTP Request node (or two, if you also poll /v2/jobs) using the same “Apiframe Auth” credentials.

VIII. Wrap-up

You now have:

  • A basic /v2/images/generate workflow to trigger Midjourney via Apiframe in n8n
  • A polling setup using /v2/jobs for quick experiments
  • A webhook setup for live production workflows

Prefer a different automation tool? The same setup works in Make.com.

Power your next AI product with Apiframe.

Instant access to 70+ media models through a single API. Start free and scale when you're ready.