• 7 min read
Pagination and Authentication
In this video, I take you through the journey of building an API for UsWork.ai using N8N. I share the real-world ups and downs, from finding jobs to posting proposals, including the tough deployment s
The workflow JSON is below:
{
"nodes": [
{
"parameters": {
"content": "## Index API",
"height": 140
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-940,
-440
],
"id": "116fcd89-df5d-49e8-a5dc-ee0d7af90e84",
"name": "Sticky Note"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "5364e106-07bb-4158-bca1-9c2a93dc287f",
"leftValue": "={{ $json }}",
"rightValue": "",
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-480,
-240
],
"id": "6ea97347-cbfd-4630-a0f6-8a9a00b79fa3",
"name": "IfAuthenticated"
},
{
"parameters": {
"respondWith": "allIncomingItems",
"options": {
"responseCode": 200
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1240,
-380
],
"id": "1c6b336b-2484-43bd-9ce2-278a0fb2f377",
"name": "Respond to Webhook1"
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT id, title, description, status, created_at\nFROM job_postings\nWHERE\n (created_at::date, id) > (\n COALESCE($1::timestamp, '9999-12-31'),\n COALESCE($2::uuid, 'ffffffff-ffff-ffff-ffff-ffffffffffff')\n )\n AND status = 'available'\nORDER BY created_at::date DESC, id DESC\nLIMIT $3+1;",
"options": {
"queryReplacement": "={{ $json.cursor_created_at }},{{ $json.cursor_id || $json.cursor_default}},{{ $json.limit }}"
}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
380,
-420
],
"id": "0a26c8c5-9027-4acd-8c33-d84842f34bd9",
"name": "Postgres",
"alwaysOutputData": true,
"credentials": {
"postgres": {
"id": "xkBc3U4KN6H3qi0M",
"name": "Supabase Postgres"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "18275e2c-f606-4d3a-9597-c9809fde5a90",
"name": "cursor_id",
"value": "={{ $('Index').item.json.query.cursor_id || null }}",
"type": "string"
},
{
"id": "899fc5c5-6970-48ff-91d9-ef6e67582844",
"name": "limit",
"value": "={{ Math.min(Number($('Index').item.json.query?.limit) || 10, 100) }}",
"type": "number"
},
{
"id": "a755be91-10bd-414d-a2a9-287b21f5f885",
"name": "cursor_created_at",
"value": "={{ $('Index').item.json.query.cursor_created_at || $now.minus(1, 'days').format('yyyy-MM-dd') }}",
"type": "string"
},
{
"id": "b129f79e-7f5a-4b09-9cf9-5ed48e17d545",
"name": "cursor_default",
"value": "={{ $json.id }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
100,
-420
],
"id": "d5a017de-2149-4610-8fe1-0887da861524",
"name": "Args"
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"options": {}
},
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
640,
-420
],
"id": "f072ea9a-be04-4dac-990b-99f2c4f2330d",
"name": "Aggregate1"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "0c05f1b6-59b4-4847-80a8-9e7fbbfe6279",
"name": "data",
"value": "={{ $json.data }}",
"type": "array"
},
{
"id": "7a7b9c11-b464-4685-a718-64b9f66e9cef",
"name": "pagination.count",
"value": "={{ $json.data.length }}",
"type": "number"
},
{
"id": "4af17b4c-e006-48d6-a57c-29682f845415",
"name": "pagination.per_page",
"value": "={{ $('Args').item.json.limit }}",
"type": "number"
},
{
"id": "3b7d37fa-5d61-4765-bd3b-d8c3d560b42b",
"name": "pagination.next_cursor",
"value": "={{ $json.data.length > 0 ? $json.data[$json.data.length - 1].id : null }}",
"type": "string"
},
{
"id": "930cb30e-ff4e-438d-a4b8-cad7fe3234ee",
"name": "pagination.next_created_at",
"value": "={{ $json.data.length > 0 ? $json.data[$json.data.length - 1].created_at : null }}",
"type": "string"
},
{
"id": "43096653-144c-4e97-9e9f-5f334321aa16",
"name": "has_next_page",
"value": false,
"type": "boolean"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
1000,
-380
],
"id": "5287d557-d002-40e0-b0da-7dff93c9d84a",
"name": "Response"
},
{
"parameters": {
"content": "## API Post a Job Proposal",
"width": 400
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-960,
200
],
"id": "b7b2c324-8f41-44b4-9303-25e3fca4c615",
"name": "Sticky Note1"
},
{
"parameters": {
"path": "/api/job_postings",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-920,
-240
],
"id": "6c01b70a-31c1-4a32-954c-3aa306c2aaa0",
"name": "Index",
"webhookId": "193061f4-83c0-4f7d-8d6b-0d260838c0cf"
},
{
"parameters": {
"httpMethod": "POST",
"path": "job_proposals/create",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-920,
440
],
"id": "12223ebb-8e2f-4802-9307-3207d9168baf",
"name": "CreateProposal",
"webhookId": "cc54c2c1-a220-4dad-be32-76d28d1b9000"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "f7a23904-ec94-4ee9-bc5c-d79a52e124fa",
"leftValue": "={{ $('CreateProposal').item.json.body.content }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
},
{
"id": "c7c74677-bd63-42f3-970f-0f6c2b9a7580",
"leftValue": "={{ $('CreateProposal').item.json.body.job_id }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-80,
420
],
"id": "44dbc7f6-7cda-42f2-939d-9fafeaecc4ba",
"name": "IfValid"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "5364e106-07bb-4158-bca1-9c2a93dc287f",
"leftValue": "={{ $json }}",
"rightValue": "",
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-440,
440
],
"id": "f98c1423-766f-4242-851a-2b37bc0b1494",
"name": "IfAuthenticated1"
},
{
"parameters": {
"respondWith": "noData",
"options": {
"responseCode": 404
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
340,
0
],
"id": "26052ef0-6daa-42ac-b285-a18ce7b44c88",
"name": "NotAuthenticated"
},
{
"parameters": {
"respondWith": "allIncomingItems",
"options": {
"responseCode": 200
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1180,
180
],
"id": "a0e8a60c-be25-4488-86bb-ec11077f0d42",
"name": "Respond to Webhook"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "213e53f4-3cfc-40c0-a3d0-1bbb005e42c5",
"leftValue": "={{$json}}",
"rightValue": "",
"operator": {
"type": "object",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
520,
280
],
"id": "8dfd3866-e194-41c1-b448-35cbe1b53eb0",
"name": "IfNotDuplicate"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"errors\": [\n \"Duplicate\"\n ]\n}",
"options": {
"responseCode": 422
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
900,
580
],
"id": "885c7fd2-d674-4b74-b5fb-21df1a9f5ac2",
"name": "Respond to Webhook2"
},
{
"parameters": {
"tableId": "job_proposals",
"fieldsUi": {
"fieldValues": [
{
"fieldId": "job_id",
"fieldValue": "={{ $('CreateProposal').item.json.body.job_id }}"
},
{
"fieldId": "user_id",
"fieldValue": "={{ $('IfValid').item.json.user_id }}"
},
{
"fieldId": "content",
"fieldValue": "={{ $('CreateProposal').item.json.body.content }}"
}
]
}
},
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
900,
180
],
"id": "2970f2fa-53e7-4ba1-b5f0-2cab1f893cf4",
"name": "Supabase1",
"credentials": {
"supabaseApi": {
"id": "17CivS2ZFGNKSLb5",
"name": "Supabase account"
}
}
},
{
"parameters": {
"operation": "get",
"tableId": "job_proposals",
"filters": {
"conditions": [
{
"keyName": "job_id",
"keyValue": "={{ $('CreateProposal').item.json.body.job_id }}"
},
{
"keyName": "user_id",
"keyValue": "={{ $json.user_id }}"
}
]
}
},
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
260,
280
],
"id": "9ea65049-be3a-4d53-a3df-f092a8fa57f2",
"name": "Exists",
"alwaysOutputData": true,
"credentials": {
"supabaseApi": {
"id": "17CivS2ZFGNKSLb5",
"name": "Supabase account"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT at.*, s.*\nFROM api_tokens at\nJOIN subscriptions s ON s.user_id = at.user_id\nWHERE\n at.token_hash = $1\n AND s.status LIKE 'active'",
"options": {
"queryReplacement": "={{ $json.body.api_key }}"
}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
-700,
440
],
"id": "eedc1a24-ac34-497a-8566-219459415995",
"name": "ActiveAndValidToken",
"alwaysOutputData": true,
"credentials": {
"postgres": {
"id": "xkBc3U4KN6H3qi0M",
"name": "Supabase Postgres"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT at.*, s.*\nFROM api_tokens at\nJOIN subscriptions s ON s.user_id = at.user_id\nWHERE\n at.token_hash = $1\n AND s.status = 'active'",
"options": {
"queryReplacement": "={{ $json.query.api_key }}"
}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
-720,
-240
],
"id": "5cb89af3-042f-4630-9c2c-de7b263aa7bc",
"name": "ActiveAndValidToken1",
"alwaysOutputData": true,
"credentials": {
"postgres": {
"id": "xkBc3U4KN6H3qi0M",
"name": "Supabase Postgres"
}
}
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"errors\": [\n \"Missing job_id or content\"\n ]\n}",
"options": {
"responseCode": 403
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
240,
560
],
"id": "00e51525-e2cd-48a0-8df4-ce7bc21c1fed",
"name": "Respond to Webhook3"
},
{
"parameters": {
"operation": "select",
"schema": {
"__rl": true,
"mode": "list",
"value": "public"
},
"table": {
"__rl": true,
"value": "job_postings",
"mode": "list",
"cachedResultName": "job_postings"
},
"limit": 1,
"sort": {
"values": [
{
"column": "created_at"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
-160,
-400
],
"id": "193c7e1f-b605-46b4-a490-747130cec6fd",
"name": "Default",
"credentials": {
"postgres": {
"id": "xkBc3U4KN6H3qi0M",
"name": "Supabase Postgres"
}
}
}
],
"connections": {
"IfAuthenticated": {
"main": [
[
{
"node": "Default",
"type": "main",
"index": 0
}
],
[
{
"node": "NotAuthenticated",
"type": "main",
"index": 0
}
]
]
},
"Postgres": {
"main": [
[
{
"node": "Aggregate1",
"type": "main",
"index": 0
}
]
]
},
"Args": {
"main": [
[
{
"node": "Postgres",
"type": "main",
"index": 0
}
]
]
},
"Aggregate1": {
"main": [
[
{
"node": "Response",
"type": "main",
"index": 0
}
]
]
},
"Response": {
"main": [
[
{
"node": "Respond to Webhook1",
"type": "main",
"index": 0
}
]
]
},
"Index": {
"main": [
[
{
"node": "ActiveAndValidToken1",
"type": "main",
"index": 0
}
]
]
},
"CreateProposal": {
"main": [
[
{
"node": "ActiveAndValidToken",
"type": "main",
"index": 0
}
]
]
},
"IfValid": {
"main": [
[
{
"node": "Exists",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond to Webhook3",
"type": "main",
"index": 0
}
]
]
},
"IfAuthenticated1": {
"main": [
[
{
"node": "IfValid",
"type": "main",
"index": 0
}
],
[
{
"node": "NotAuthenticated",
"type": "main",
"index": 0
}
]
]
},
"IfNotDuplicate": {
"main": [
[
{
"node": "Supabase1",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond to Webhook2",
"type": "main",
"index": 0
}
]
]
},
"Supabase1": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Exists": {
"main": [
[
{
"node": "IfNotDuplicate",
"type": "main",
"index": 0
}
]
]
},
"ActiveAndValidToken": {
"main": [
[
{
"node": "IfAuthenticated1",
"type": "main",
"index": 0
}
]
]
},
"ActiveAndValidToken1": {
"main": [
[
{
"node": "IfAuthenticated",
"type": "main",
"index": 0
}
]
]
},
"Default": {
"main": [
[
{
"node": "Args",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {
"Index": [
{
"headers": {
"host": "n8n-uswork.apps.thedailyaistudio.com",
"user-agent": "PostmanRuntime/7.43.3",
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"accept-language": "application/json",
"authorization": "Bearer gvgw3lhbxJWi4vVimwVqOUk9QcJiqK0cMlCK8lbRIwtHC6Pm",
"content-type": "application/json",
"postman-token": "da908679-fe31-44f7-bea2-beb95e25940e",
"x-forwarded-for": "24.60.183.124",
"x-forwarded-host": "n8n-uswork.apps.thedailyaistudio.com",
"x-forwarded-port": "443",
"x-forwarded-proto": "https",
"x-forwarded-server": "536677c81f48",
"x-real-ip": "24.60.183.124"
},
"params": {},
"query": {
"limit": "1",
"api_key": "gvgw3lhbxJWi4vVimwVqOUk9QcJiqK0cMlCK8lbRIwtHC6Pm"
},
"body": {},
"webhookUrl": "https://n8n-uswork.apps.thedailyaistudio.com/webhook/api/job_postings",
"executionMode": "production"
}
],
"CreateProposal": [
{
"headers": {
"host": "n8n-uswork.apps.thedailyaistudio.com",
"user-agent": "PostmanRuntime/7.43.3",
"content-length": "194",
"accept": "application/json",
"accept-encoding": "gzip, deflate, br",
"accept-language": "application/json",
"authorization": "Bearer 12345",
"content-type": "application/json",
"postman-token": "f350bbc5-a82e-4fb6-9776-b4ba69c35dff",
"x-forwarded-for": "24.60.183.124",
"x-forwarded-host": "n8n-uswork.apps.thedailyaistudio.com",
"x-forwarded-port": "443",
"x-forwarded-proto": "https",
"x-forwarded-server": "536677c81f48",
"x-real-ip": "24.60.183.124"
},
"params": {},
"query": {},
"body": {
"job_id": "5db6a039-9f21-4662-abe6-0dfd2899405c",
"api_key": "kh9Q0iPZgoUpEmUCzw8aDhhXHDHW9AOgBH6PdIJWRJiHH329",
"content": "This is my proposal for the job. I have experience with..."
},
"webhookUrl": "https://n8n-uswork.apps.thedailyaistudio.com/webhook/job_proposals/create",
"executionMode": "production"
}
]
},
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "d4013cd2553a8175175b59d192b506cb636ed9d719b621634b1dcf31941cb2e5"
}
}