data/skills/n8n-workflow-patterns/http_api_integration.md
Use Case: Fetch data from REST APIs, transform it, and use it in workflows.
Trigger → HTTP Request → [Transform] → [Action] → [Error Handler]
Key Characteristic: External data fetching with error handling
Options:
Purpose: Call external REST APIs
Configuration:
{
method: "GET", // GET, POST, PUT, DELETE, PATCH
url: "https://api.example.com/users",
authentication: "predefinedCredentialType",
sendQuery: true,
queryParameters: {
"page": "={{$json.page}}",
"limit": "100"
},
sendHeaders: true,
headerParameters: {
"Accept": "application/json",
"X-API-Version": "v1"
}
}
Purpose: Extract and transform API response data
Typical flow:
HTTP Request → Code (parse) → Set (map fields) → Action
Common actions:
Purpose: Handle API failures gracefully
Error Trigger Workflow:
Error Trigger → Log Error → Notify Admin → Retry Logic (optional)
Flow: Schedule → HTTP Request → Transform → Database
Example (Fetch GitHub issues):
1. Schedule (every hour)
2. HTTP Request
- Method: GET
- URL: https://api.github.com/repos/owner/repo/issues
- Auth: Bearer Token
- Query: state=open
3. Code (filter by labels)
4. Set (map to database schema)
5. Postgres (upsert issues)
Response Handling:
// Code node - filter issues
const issues = $input.all();
return issues
.filter(item => item.json.labels.some(l => l.name === 'bug'))
.map(item => ({
json: {
id: item.json.id,
title: item.json.title,
created_at: item.json.created_at
}
}));
Flow: Trigger → Fetch from API A → Transform → Send to API B
Example (Jira to Slack):
1. Schedule (every 15 minutes)
2. HTTP Request (GET Jira tickets updated today)
3. IF (check if tickets exist)
4. Set (format for Slack)
5. HTTP Request (POST to Slack webhook)
Flow: Trigger → Fetch base data → Call enrichment API → Combine → Store
Example (Enrich contacts with company data):
1. Postgres (SELECT new contacts)
2. Code (extract company domains)
3. HTTP Request (call Clearbit API for each domain)
4. Set (combine contact + company data)
5. Postgres (UPDATE contacts with enrichment)
Flow: Schedule → Check API health → IF unhealthy → Alert
Example (API health check):
1. Schedule (every 5 minutes)
2. HTTP Request (GET /health endpoint)
3. IF (status !== 200 OR response time > 2000ms)
4. Slack (alert #ops-team)
5. PagerDuty (create incident)
Flow: Trigger → Fetch large dataset → Split in Batches → Process → Loop
Example (Process all users):
1. Manual Trigger
2. HTTP Request (GET /api/users?limit=1000)
3. Split In Batches (100 items per batch)
4. HTTP Request (POST /api/process for each batch)
5. Wait (2 seconds between batches - rate limiting)
6. Loop (back to step 4 until all processed)
{
authentication: "none"
}
Setup: Create credential
{
authentication: "predefinedCredentialType",
nodeCredentialType: "httpHeaderAuth",
headerAuth: {
name: "Authorization",
value: "Bearer YOUR_TOKEN"
}
}
Access in workflow:
{
authentication: "predefinedCredentialType",
nodeCredentialType: "httpHeaderAuth"
}
Header auth:
{
sendHeaders: true,
headerParameters: {
"X-API-Key": "={{$credentials.apiKey}}"
}
}
Query auth:
{
sendQuery: true,
queryParameters: {
"api_key": "={{$credentials.apiKey}}"
}
}
Setup: Create "Basic Auth" credential
{
authentication: "predefinedCredentialType",
nodeCredentialType: "httpBasicAuth"
}
Setup: Create OAuth2 credential with:
{
authentication: "predefinedCredentialType",
nodeCredentialType: "oAuth2Api"
}
Default: Data flows to next node
Access response:
// Entire response
{{$json}}
// Specific fields
{{$json.data.id}}
{{$json.results[0].name}}
1. Set (initialize: page=1, has_more=true)
2. HTTP Request (GET /api/items?page={{$json.page}})
3. Code (check if more pages)
4. IF (has_more === true)
└→ Set (increment page) → Loop to step 2
Code node (check pagination):
const items = $input.first().json;
const currentPage = $json.page || 1;
return [{
json: {
items: items.results,
page: currentPage + 1,
has_more: items.next !== null
}
}];
1. HTTP Request (GET /api/items)
2. Code (extract next_cursor)
3. IF (next_cursor exists)
└→ Set (cursor={{$json.next_cursor}}) → Loop to step 1
// Code node - parse Link header
const linkHeader = $input.first().json.headers['link'];
const hasNext = linkHeader && linkHeader.includes('rel="next"');
return [{
json: {
items: $input.first().json.body,
has_next: hasNext,
next_url: hasNext ? parseNextUrl(linkHeader) : null
}
}];
Configure HTTP Request:
{
continueOnFail: true, // Don't stop workflow on error
ignoreResponseCode: true // Get response even on error
}
Handle errors:
HTTP Request (continueOnFail: true)
→ IF (check error)
├─ [Success Path]
└─ [Error Path] → Log → Retry or Alert
IF condition:
{{$json.error}} is empty
// OR
{{$json.statusCode}} < 400
Split In Batches (1 item per batch)
→ HTTP Request
→ Wait (1 second)
→ Loop
// Code node
const maxRetries = 3;
let retryCount = $json.retryCount || 0;
if ($json.error && retryCount < maxRetries) {
const delay = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s
return [{
json: {
...$json,
retryCount: retryCount + 1,
waitTime: delay
}
}];
}
// Code node - check rate limit
const headers = $input.first().json.headers;
const remaining = parseInt(headers['x-ratelimit-remaining'] || '999');
const resetTime = parseInt(headers['x-ratelimit-reset'] || '0');
if (remaining < 10) {
const now = Math.floor(Date.now() / 1000);
const waitSeconds = resetTime - now;
return [{
json: {
shouldWait: true,
waitSeconds: Math.max(waitSeconds, 0)
}
}];
}
return [{ json: { shouldWait: false } }];
{
method: "GET",
url: "https://api.example.com/users",
sendQuery: true,
queryParameters: {
"page": "1",
"limit": "100",
"filter": "active"
}
}
{
method: "POST",
url: "https://api.example.com/users",
sendBody: true,
bodyParametersJson: JSON.stringify({
name: "={{$json.name}}",
email: "={{$json.email}}",
role: "user"
})
}
{
method: "POST",
url: "https://api.example.com/upload",
sendBody: true,
bodyParametersUi: {
parameter: [
{ name: "file", value: "={{$json.fileData}}" },
{ name: "filename", value: "={{$json.filename}}" }
]
},
sendHeaders: true,
headerParameters: {
"Content-Type": "multipart/form-data"
}
}
{
method: "PATCH",
url: "https://api.example.com/users/={{$json.userId}}",
sendBody: true,
bodyParametersJson: JSON.stringify({
status: "active",
last_updated: "={{$now}}"
})
}
{
method: "DELETE",
url: "https://api.example.com/users/={{$json.userId}}"
}
HTTP Request (continueOnFail: true)
→ IF (error occurred)
└→ Wait (5 seconds)
└→ HTTP Request (retry)
HTTP Request (Primary API, continueOnFail: true)
→ IF (failed)
└→ HTTP Request (Fallback API)
Main Workflow:
HTTP Request → Process Data
Error Workflow:
Error Trigger
→ Set (extract error details)
→ Slack (alert team)
→ Database (log error for analysis)
// Code node - circuit breaker logic
const failures = $json.recentFailures || 0;
const threshold = 5;
if (failures >= threshold) {
throw new Error('Circuit breaker open - too many failures');
}
return [{ json: { canProceed: true } }];
// Code node
const response = $input.first().json;
return response.data.items.map(item => ({
json: {
id: item.id,
name: item.attributes.name,
email: item.attributes.contact.email
}
}));
// Code node - flatten nested array
const items = $input.all();
const flattened = items.flatMap(item =>
item.json.results.map(result => ({
json: {
parent_id: item.json.id,
...result
}
}))
);
return flattened;
HTTP Request 1 (users)
→ Set (store users)
→ HTTP Request 2 (orders for each user)
→ Merge (combine users + orders)
Replace Schedule with Manual Trigger for testing
// Code node - log for debugging
console.log('API Response:', JSON.stringify($input.first().json, null, 2));
return $input.all();
For file downloads:
{
method: "GET",
url: "https://api.example.com/download/file.pdf",
responseFormat: "file", // Important for binary data
outputPropertyName: "data"
}
Use Split In Batches with multiple items:
Set (create array of IDs)
→ Split In Batches (10 items per batch)
→ HTTP Request (processes all 10 in parallel)
→ Loop
IF (check cache exists)
├─ [Cache Hit] → Use cached data
└─ [Cache Miss] → HTTP Request → Store in cache
Only fetch if data changed:
HTTP Request (GET with If-Modified-Since header)
→ IF (status === 304)
└─ Use existing data
→ IF (status === 200)
└─ Process new data
If API supports batch operations:
{
method: "POST",
url: "https://api.example.com/batch",
bodyParametersJson: JSON.stringify({
requests: $json.items.map(item => ({
method: "GET",
url: `/users/${item.id}`
}))
})
}
url: "https://api.example.com/prod/users"
url: "={{$env.API_BASE_URL}}/users"
headerParameters: {
"Authorization": "Bearer sk-abc123xyz" // ❌ Exposed!
}
authentication: "predefinedCredentialType",
nodeCredentialType: "httpHeaderAuth"
HTTP Request → Process (fails if API down)
HTTP Request (continueOnFail: true) → IF (error) → Handle
Processing 10,000 items synchronously
Split In Batches (100 items) → Process → Loop
From n8n template library (892 API integration templates):
GitHub to Notion:
Schedule → HTTP Request (GitHub API) → Transform → HTTP Request (Notion API)
Weather to Slack:
Schedule → HTTP Request (Weather API) → Set (format) → Slack
CRM Sync:
Schedule → HTTP Request (CRM A) → Transform → HTTP Request (CRM B)
Use search_templates({query: "http api"}) to find more!
Key Points:
Pattern: Trigger → HTTP Request → Transform → Action → Error Handler
Related: