n8n Integration
Automate content workflows with n8n's HTTP Request node. Exact field-by-field configuration for self-hosted and cloud instances.
Overview
n8n is a self-hosted (and cloud-hosted) workflow automation tool. Use the HTTP Request node to connect to Brainpercent's API. This guide covers every field in n8n's node configuration, credential setup, and workflow patterns.
Prerequisites
- An n8n instance, self-hosted (
npx n8nor Docker) or n8n.cloud - A Brainpercent API key (
bp_prefix) with read + write permissions, created at brainpercent.app/chat/developers. Keys cannot be created over the API. - A project id. Every generation call needs one. Fetch it with
GET https://www.brainpercent.app/api/v1/projectsor add that call as the first HTTP Request node. Projects are created in the app, not over the API. - n8n version 0.220+ (for latest HTTP Request node features)
Use the www host in every URL. The apex https://brainpercent.app answers with a 307 redirect and the POST body is dropped when it is followed, so the request arrives empty.
Step 1: Create Header Auth Credential
Before configuring the HTTP Request node, create a reusable credential. Go to Settings → Credentials → Add Credential and search for "Header Auth".
| Credential Field | Value |
|---|---|
| Credential Type | Header Auth |
| Name (Label) | Brainpercent API |
| Header Auth: Name | Authorization |
| Header Auth: Value | Bearer bp_your_api_key_here |
Self-hosted alternative: You can use environment variables instead. Set BRAINPERCENT_API_KEY=bp_your_key in your n8n environment and reference it as {{ $env.BRAINPERCENT_API_KEY }} in the credential value.
Step 2: HTTP Request Node Configuration
Add an HTTP Request node to your workflow. Here is every field in the node settings panel:
| n8n Field | Value |
|---|---|
| Method | POST |
| URL | https://www.brainpercent.app/api/v1/articles/generate |
| Authentication | Generic Credential Type |
| Generic Auth Type | Header Auth |
| Credential for Header Auth | Select your "Brainpercent API" credential |
| Send Headers | ON |
| Header Parameters | Add one header (see below) |
| Send Body | ON |
| Body Content Type | JSON |
| Specify Body | Using JSON |
| JSON | Your request body (see below) |
| Options → Response Format | JSON (auto-detected, default) |
| Options → Always Output Data | ON (for error handling) |
| Options → Timeout | 30000 ms (30 seconds) |
Header Parameters
Under "Header Parameters", add one header (the Authorization is handled by the credential):
| Name | Value |
|---|---|
Content-Type | application/json |
JSON Body
In the "JSON" field, paste your request body. Use n8n expressions with {{ }} to reference data from previous nodes:
{
"topic": "Your Article Topic",
"project_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"keywords": ["keyword1", "keyword2"],
"tone": "professional",
"word_count": 1200
}topic (3 to 500 characters) and project_id are required. Optional: keywords (up to 20), tone (professional, casual, academic, conversational), word_count (300 to 5000, default 800). There is no length and no language field: the language comes from the project. Costs 1.5 credits and returns 202 with a poll_url.
Workflow: Content Automation Pipeline
Cron → Generate Article → Wait → Check Status → IF Ready → Generate Social
Node 1: Schedule Trigger
| Field | Value |
|---|---|
| Node | Schedule Trigger (previously "Cron") |
| Rule: Trigger Interval | Days |
| Rule: Hour | 9 |
| Rule: Minute | 0 |
Node 2: HTTP Request (Generate Article)
Configured as shown in the HTTP Request Node Configuration above. POST to /api/v1/articles/generate.
{
"topic": "Daily SEO insights for {{ $now.format('MMMM D, YYYY') }}",
"project_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"keywords": ["SEO", "daily insights"],
"tone": "professional",
"word_count": 900
}Node 3: Wait
| Field | Value |
|---|---|
| Node | Wait |
| Resume | After Time Interval |
| Wait Amount | 2 |
| Wait Unit | Minutes |
Article generation takes roughly 10 to 15 minutes, so this Wait is one turn of a loop, not a single sleep. Node 5 sends the workflow back here until the article reports complete. The Wait node pauses the entire workflow execution, and on self-hosted n8n the workflow stays in memory during the wait, so for production use n8n's queue mode (EXECUTIONS_MODE=queue) so waits do not block other workflows.
Node 4: HTTP Request (Check Status)
| Field | Value |
|---|---|
| Method | GET |
| URL | https://www.brainpercent.app/api/v1/articles/{{ $json.data.article_id }}/status |
| Authentication | Same "Brainpercent API" Header Auth credential |
| Send Body | OFF (GET request) |
Node 5: IF (Check Generation Status)
| IF Field | Value |
|---|---|
| Condition | Boolean |
| Value 1 | {{ $json.data.is_complete }} |
| Operation | is true |
Test is_complete, not a status string. Article statuses are draft, queued, cms, needs_editing, failed, and archived: a finished article is cms, and neither published nor generating exists, so a comparison against either will loop forever. Add a second IF on {{ $json.data.status }} equals failed so a failed job breaks out of the loop.
| Output | Connect To |
|---|---|
| True (article finished) | → Node 6: Generate Social Content |
| False (still working) | → Loop back to Node 3: Wait (creates a polling loop) |
Node 6: HTTP Request (Generate Social)
| Field | Value |
|---|---|
| Method | POST |
| URL | https://www.brainpercent.app/api/v1/social/generate |
{
"source_url": "https://www.brainpercent.app/articles/{{ $json.data.slug }}",
"platforms": ["twitter", "linkedin"],
"project_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"angle": "Practical takeaways for busy marketers"
}source_url and platforms (1 to 10) are required. Optional: source_type (website, blog, youtube), angle, title, project_id. There is no tone or language field: use angle to steer it. Valid platforms: instagram, facebook, linkedin, twitter, threads, tiktok, bluesky, pinterest, google_business, telegram. Costs 0.6 credits per platform and returns a single data.content_id.
Node 7: Publish or Schedule
Loop a Wait plus an HTTP Request on https://www.brainpercent.app/api/v1/social/content/{{ $json.data.content_id }} until data.status is ready or completed, then POST to /api/v1/social/publish. Publishing while the content is still generating returns 412, and so does publishing with no connected social accounts.
{
"content_id": "{{ $json.data.content_id }}"
}Only content_id is required. Omit platforms to publish every variant. Without scheduled_at you get a 202 and it publishes immediately; with it, a 200 and it is scheduled.
Workflow: Conditional Generation
Check credits before generating to avoid failures and wasted workflow runs.
Schedule Trigger or Webhook
HTTP Request: GET https://www.brainpercent.app/api/v1/user/credits
IF Node: Check credits
| IF Field | Value |
|---|---|
| Condition | Number |
| Value 1 | {{ $json.data.available_credits }} |
| Operation | Larger or Equal |
| Value 2 | 1 (cost of one article) |
For a social job the threshold is the number of platforms times 0.6, since that is what one platform costs. 1 credit equals $1.00 of a single balance that covers every content type.
True: HTTP Request → POST /articles/generate
False: Send Notification (Slack/Email node) saying "Low credits!"
Workflow: Webhook Listener
Expose an n8n webhook endpoint that triggers article generation when called externally. This webhook is hosted by your n8n instance and is called by your own systems. Brainpercent does not send webhooks: there is no callback registration and no signed payload, so completion still has to be polled.
Webhook Node Configuration
| Field | Value |
|---|---|
| Node | Webhook |
| HTTP Method | POST |
| Path | generate-article |
| Respond | Using 'Respond to Webhook' Node |
The full webhook URL will be: https://your-n8n-instance.com/webhook/generate-article
n8n Expressions Reference
n8n uses JavaScript expressions inside {{ }} to reference data from previous nodes:
| Data | n8n Expression | Example |
|---|---|---|
| Article ID | {{ $json.data.article_id }} | f8c9d0e1-2345-... |
| Article status | {{ $json.data.status }} | cms |
| Article finished? | {{ $json.data.is_complete }} | true |
| Article slug | {{ $json.data.slug }} | content-marketing-trends-2026 |
| Credit balance | {{ $json.data.available_credits }} | 42 |
| Social content id (one per job) | {{ $json.data.content_id }} | b1c2d3e4-f5a6-... |
| LinkedIn caption | {{ $json.data.platforms.linkedin }} | Caption text... |
| LinkedIn image (permanent) | {{ $json.data.image_urls.linkedin }} | https://...supabase.co/... |
| Error code (sits beside data, not inside it) | {{ $json.error.code }} | INSUFFICIENT_CREDITS |
| Current date | {{ $now.format('YYYY-MM-DD') }} | 2026-02-02 |
| Webhook body field | {{ $json.body.topic }} | AI Marketing |
| Env variable | {{ $env.BRAINPERCENT_API_KEY }} | bp_abc123... |
Error Handling & Workflow Settings
Error Trigger Workflow
Create a separate "Error Workflow" in n8n that catches failures from all workflows:
| Setting | Value |
|---|---|
| Error Workflow trigger node | Error Trigger |
| Connected to | Slack/Email node for alerts |
Then in your main workflow's settings (gear icon), set Error Workflow to point to this error handler.
Retry on Failure
| Workflow Setting | Value |
|---|---|
| Retry On Fail | ON |
| Max Tries | 3 |
| Wait Between Tries | 60000 ms (1 minute) |
Tips & Best Practices
- Use Header Auth credentials. Never hardcode API keys in HTTP Request nodes.
- Enable "Always Output Data" on HTTP Request nodes so subsequent nodes receive data even on error
- Use the Wait node (not Code node with
sleep()) for workflow pauses - Set up an Error Trigger workflow to catch failures across all workflows
- Use "Split In Batches" node when processing large arrays of articles
- Social generation returns one row for every platform, so there is no items array. Read captions from
data.platforms.<platform>and images fromdata.image_urls. - Store the
project_idalongside the API key. It is required on every generate call. - For self-hosted: use environment variables for secrets, e.g.
{{ $env.BRAINPERCENT_API_KEY }} - For self-hosted: enable queue mode in production for concurrent workflow execution
- Enable "Retry On Fail" on HTTP Request nodes with a 60-second interval for rate limit resilience