Chapter 1.3 AI API Integration

Learning Objectives

After completing this chapter, you will be able to:

  • Configure an n8n HTTP Request node for production AI API calls, including authentication, request body parameters, timeout, and retry settings appropriate for LLM inference latency.
  • Explain the three phases of a Chat Completions API interaction (request, inference, response) and map each phase to specific node configuration decisions.
  • Design an exponential backoff retry strategy that handles 429 rate-limit responses without cascading failures under traffic spikes.
  • Build a normalized AI output contract a consistent output object shape that downstream nodes consume identically whether the AI call succeeded or produced an error.
  • Capture API metadata (request ID, model version, token counts, latency) in the output object for audit trail and cost monitoring purposes.
  • Troubleshoot a production AI API integration by classifying failure types (authentication, rate limit, timeout, malformed response) and identifying the correct resolution for each.

Introduction

In Chapter 1.2, you built a production-quality system prompt that specifies a complete output schema, enforces field types and enum constraints, and defends against prompt injection. The Build Prompt and Parse Response nodes are well-designed. What connects them is not.

The HTTP Request node in the current workflow was configured quickly to make the concept work correct URL, bearer token, JSON body. A production AI API integration requires considerably more. It needs authentication managed through a credential store rather than a hardcoded string. It needs a request body with every parameter set intentionally. It needs response handling that accounts for the full range of HTTP outcomes the OpenAI API can return, not just the 200 it returns when everything works. It needs retry logic so the workflow degrades gracefully under rate limits rather than failing outright. And it needs a standardized output object a normalized interface so every downstream node consumes the same data shape regardless of whether the AI call succeeded or produced an error.

AI APIs have a different operational profile from the REST endpoints you integrated in Phases 1–4. They are significantly more expensive per call. They enforce per-minute and per-day token limits that a busy workflow can saturate. They are subject to transient 5xx errors more frequently than purpose-built data APIs. Their response times vary from under a second to several seconds depending on model, input length, and server load.

They can also return structurally valid HTTP 200 responses that still contain content the workflow cannot consume a JSON parse failure, a field outside the expected range, a response the model generated correctly but outside the defined schema. A workflow that calls the OpenAI API with no retry configuration, no error classification, and no normalized output contract will work correctly in development. In production, processing hundreds of submissions per day, it will encounter rate limits during traffic spikes, receive occasional malformed responses during model updates, and produce silent failures when the API response time exceeds the node’s timeout.

This chapter teaches you how to configure the HTTP Request node for production AI service calls and how to design a reusable AI service layer: a self-contained, three-node segment that handles the complete API interaction with the same discipline a microservice applies to a network dependency. The Build Prompt → HTTP Request → Parse Response pattern introduced in Chapter 1.0 becomes, in this chapter, a production-ready integration with retry configuration, metadata capture, error classification, and a normalized output contract. By the end of this chapter, the AI service segment of the recruiting workflow is complete. Chapter 1.4 AI-Powered Workflow Design connects it to the routing architecture that turns an AI service call into a governed, fault-tolerant workflow component.


1.3.1 AI API Architecture

Business objective: Treat the OpenAI Chat Completions API as a governed network dependency not a convenience call so that the workflow degrades gracefully under rate limits, variable latency, and transient server errors without requiring manual recovery.

Engineering objective: Map the three phases of a Chat Completions API interaction to specific HTTP Request node configuration parameters so that each setting has an identifiable operational justification.

Expected outcome: The HTTP Request node is configured with explicit timeout, retry, authentication, and error-path settings grounded in the three-phase model not defaults.

Architecture and Design Rationale

The Chat Completions API is operationally distinct from the REST endpoints used in earlier workflow phases. It charges per token, enforces per-minute and per-day rate limits, and returns response times that vary by an order of magnitude (500ms–5,000ms) depending on model, input length, and server load. It can also return HTTP 200 responses whose content fails at the application layer valid JSON that does not conform to the expected schema, or model output that wraps the JSON in prose.

The three-phase model exists to give each configuration decision a precise mapping: timeout maps to inference phase duration, retry maps to transient errors in any phase, and response parsing maps to the response phase. Without this frame, engineers configure the node by analogy to generic REST calls correct URL, auth, body and discover in production that latency spikes cause indefinite blocking, rate limits produce halted executions, and malformed content causes uncaught exceptions.

The alternative catching all failures at a higher-level error handler is architecturally inferior because it conflates distinct failure types (rate limit vs. malformed content vs. auth failure) that require different remediation. The three-phase model enables specific classification.

Three-Phase API Interaction Model

A Chat Completions API call is a synchronous HTTP POST request. The client sends a body containing the model identifier, the message array, and configuration parameters. The server executes the inference the LLM processes the input tokens and generates output tokens and returns a body containing the generated content, metadata about the generation, and billing information for the call.

The interaction has three phases. During the request phase, the client assembles the body and sends it to the endpoint. Cost here is proportional to token count: every token in the system message and user message is an input token that contributes to the billing calculation and determines whether the request fits within the model’s context window. During the inference phase, the server processes the request. This phase is opaque to the client and cannot be interrupted or accelerated. Response times for gpt-4o-mini range from under 500ms to over 5,000ms depending on input length, output length, and server load which is why timeout configuration on the HTTP Request node requires specific attention. During the response phase, the server returns the body. The primary fields are choices (an array of candidate outputs), model (the exact version that processed the request), usage (input and output token counts), and id (a unique request identifier for debugging and audit logging). Most integration errors occur in this phase malformed JSON in choices[0].message.content, unexpected nested structures, or missing fields when the model produces an unusual output format.

Understanding these three phases makes the HTTP Request node’s configuration options legible rather than arbitrary. Timeout values correspond to the inference phase. Retry configuration corresponds to rate-limit and server errors that can occur in any phase. Response extraction logic corresponds to the response phase.

Phase-to-Configuration Mapping:

API Phase Duration / Behavior HTTP Request Node Setting
Request Client-controlled; cost determined here Body parameters: model, temperature, max_tokens, response_format
Inference Server-controlled; 500ms–5,000ms Timeout: 15000ms
Response Server-returned; may fail at content layer Response Format: JSON; Parse Response node handles content validation
Any phase Transient 429 / 5xx possible Retry on Fail: enabled, Max Tries 3, Wait 500ms

Request body (Build Prompt -> HTTP Request):

Field Example Value Purpose
model "gpt-4o-mini" Model identifier
temperature 0.1 Variance control
max_tokens 200 Output cost cap
response_format { type: "json_object" } JSON enforcement
messages[0] { role: "system", content: "..." } Static system prompt
messages[1] { role: "user", content: "..." } Dynamic user input

Headers: Authorization: Bearer {OPENAI_API_KEY}, Content-Type: application/json. URL: https://api.openai.com/v1/chat/completions.

Inference phase occurs between request and response (200ms-5000ms).

HTTP 200 response fields:

Field Example Value Purpose
id "chatcmpl-abc123" request_id for audit log
model "gpt-4o-mini-2024-07-18" Exact model version
choices[0].message.role "assistant" Message role
choices[0].message.content "{\"candidate_intent\":...}" Extraction point for JSON.parse()
choices[0].finish_reason "stop" Completion status
usage.prompt_tokens 480 Input cost
usage.completion_tokens 120 Output cost
usage.total_tokens 600 Billing unit

Extraction pipeline: choices[0].message.content -> JSON.parse() -> validate.

CautionProduction Risk

Most engineers who add an AI API call for the first time configure the HTTP Request node as they would any other REST call: URL, auth, body, done. That approach works in happy-path testing and fails in production. The AI API’s operational characteristics variable latency, enforced rate limits, per-token pricing, and a response envelope that can succeed at the HTTP level while failing at the content level require specific configuration that generic API calls do not.


1.3.2 Authentication and Credential Management

Business objective: Prevent API key exposure in logs, exports, and version control while enabling zero-downtime key rotation.

Engineering objective: Store the OpenAI API key in the n8n Credential Store and configure the HTTP Request node to inject it at execution time, never at configuration time.

Expected outcome: The Authorization header is absent from the node configuration, execution logs, and any workflow export rotation requires one credential update and no node changes.

Architecture and Design Rationale

There are two ways to supply an API key to an HTTP Request node: embed it directly in the node configuration (fast, visible, insecure) or reference a named credential stored by n8n (one extra setup step, invisible to logs, rotatable independently). The second approach is the only production-acceptable choice for AI API keys. The cost difference is one setup step per credential not per deployment.

Environment variables are a viable alternative for self-hosted infrastructure where the .env file is secured at the OS level, but they lack encryption at rest and are not available in n8n Cloud. The Credential Store is the common path that works across both deployment models.

Scope isolation at the key level one API key per client deployment provides two operational benefits: per-client billing visibility through the OpenAI dashboard, and rate-limit isolation so that a spike in one client’s workflow does not exhaust the limits of another.

Credential Store

An API key is a secret, and secrets in an automation workflow require two guarantees: they must be stored outside the workflow definition so they never appear in logs, exports, or version control; and they must be rotatable without modifying any node configuration.

n8n provides two mechanisms for secret management. The Credential Store is the preferred approach for AI API keys: it encrypts credentials at rest, restricts their visibility to authorized workflow executions, and allows rotation from the credentials interface without touching the workflow. Environment variables are an acceptable alternative for self-hosted instances where the .env file is already secured at the infrastructure level, but they do not provide encryption at rest.

Implementation Sequence Credential Store Setup:

Step 1 Create the credential. Navigate to Settings → Credentials → New Credential, select Header Auth, set the header name to Authorization and the value to Bearer YOUR_OPENAI_API_KEY. Name the credential OpenAI API and save.

Step 2 Reference it in the node. In the HTTP Request node, set Authentication to Predefined Credential Type → Header Auth and select OpenAI API. The Authorization header is injected at execution time without appearing in the node configuration or the execution log.

Step 3 Verify absence. After saving, confirm the Authorization header does not appear anywhere in the node’s configuration panel. If it does, the credential reference is not active.

Key rotation is an operational responsibility, not a deployment afterthought. AI API keys should be rotated quarterly or immediately after any potential exposure. With Credential Store configuration, rotation requires only generating a new key in the OpenAI dashboard and updating the credential value. No workflow node changes are required.

Secret Storage Comparison:

Storage Method Encrypted at Rest Absent from Logs Absent from Export Rotatable Without Node Change
Credential Store Yes Yes Yes Yes
Environment Variable (self-hosted) No Yes Yes Yes
Code node string No No No No
Node configuration field (plain text) No No No No
CautionProduction Risk

A key placed in a Code node string appears in n8n execution logs, workflow exports, and any environment where the workflow definition is opened for inspection. A key embedded “temporarily” during development and forgotten is a key that has been exposed to everyone with access to the workflow including anyone who receives the workflow as an export. For client-facing deployments, the Credential Store is the only acceptable approach.


1.3.3 Request Construction and Payload Design

Business objective: Fix all model parameters explicitly so that cost, latency, and output quality are predictable across every execution not dependent on API defaults.

Engineering objective: Define a centralized AI_CONFIG constant in the Build Prompt node and construct a prompt_payload object that the HTTP Request node consumes without further transformation.

Expected outcome: A single constant governs all four model parameters; a model upgrade or token budget change requires editing one value in one place.

Architecture and Design Rationale

The HTTP Request node for an AI API call exposes more configuration parameters than a typical REST call. Each parameter is a lever on one of three output properties cost, latency, or quality. The key architectural decision here is where those parameters live: defined once in a centralized constant (Build Prompt node), or scattered across the HTTP Request node’s body configuration.

Centralizing parameters in AI_CONFIG is preferred because the HTTP Request node’s body configuration field is not version-controlled. A model upgrade from gpt-4o-mini to a newer model that requires touching three separate body fields rather than one constant string is a configuration change that can be applied inconsistently across environments. The constant pattern enforces the single-source-of-truth principle at the code level.

response_format: { type: "json_object" } is not optional for automation contexts. Without it, the model is free to return prose, code-fenced JSON, or JSON preceded by a sentence all of which cause JSON.parse() to fail or extract incorrectly. The parameter costs nothing and eliminates the most common parse failure category.

The HTTP Request node for an AI API call has more configuration parameters than a typical REST call. Each parameter is a lever on one of three output properties cost, latency, or quality and understanding why each value is set is more important than memorizing it.

Request body parameters:

Parameter Production Value Purpose
model "gpt-4o-mini" Cost-effective and sufficient for classification and scoring tasks. Use gpt-4o only if output quality is demonstrably insufficient at 15× the cost.
messages [{role:"system",...},{role:"user",...}] The message array from the Build Prompt Code node. Always two messages: static system, dynamic user.
response_format {"type":"json_object"} Forces the response to be a valid JSON object. Required for all automation contexts.
temperature 0.1 Reduces output variance. For classification and scoring, stay in the 0–0.2 range. Above 0.3, format violations in the output increase measurably.
max_tokens 200 Maximum output token count. Sufficient for the five-to-seven-field schema used in Part I. Caps response cost and prevents verbose outputs that inflate billing and increase parse failure rates.

HTTP Request node settings:

Setting Value Notes
Authentication Header Auth → OpenAI API credential Never use “None” with a manually entered Authorization header.
Send Body JSON Body Content Type: JSON
Timeout 15000 15 seconds accounts for variable inference latency. Reduce to 10,000ms for workflows with strict latency requirements.
Retry on Fail Enabled See Section 1.3.4 for configuration.
Response Format JSON Ensures n8n parses the response as JSON before passing it to the next node.

The cleanest production pattern defines all model parameters as a configuration constant at the top of the Build Prompt Code node:

const AI_CONFIG = {
  model: "gpt-4o-mini",
  temperature: 0.1,
  max_tokens: 200,
  response_format: { type: "json_object" }
};

When the model is upgraded or the token budget is adjusted, the change happens in one place. The HTTP Request node body then references the complete prompt_payload object assembled by the Build Prompt node:

{{ JSON.stringify($json.prompt_payload) }}

The choice of gpt-4o-mini is not a default it is a deliberate cost decision. A workflow processing 500 submissions per day at gpt-4o pricing costs roughly 15× more per call than the same workflow at gpt-4o-mini. For structured classification with a well-engineered prompt, the quality difference is negligible. Using response_format: json_object and temperature: 0.1 together reduces parse failure rates to well under 1% for the schema complexity used in Part I. These parameters are not interchangeable with their defaults.


1.3.4 Response Processing and Error Handling

Business objective: Ensure that every execution of the Parse Response node produces a usable output object regardless of whether the API call succeeded, returned malformed content, or failed at the HTTP layer.

Engineering objective: Implement a five-step sequential validation that classifies failure at the earliest identifiable point and always sets ai_result_valid with a specific parse_error code when it is false.

Expected outcome: Downstream nodes never receive a null or structurally inconsistent output; parse_error always identifies the failure category precisely enough to determine the correct remediation.

Architecture and Design Rationale

The Parse Response node is the boundary between the AI service layer and the rest of the workflow. Its architectural contract is strict: every execution path success, HTTP failure, content failure, schema failure must produce the same output object shape with ai_result_valid always set.

The five-step sequential validation is ordered by failure specificity: HTTP errors are caught before content parsing is attempted, content parsing is caught before field validation, and field validation is caught before the result is populated. Each step returns early with a specific parse_error code. This order matters because a missing choices array and a failed JSON.parse() look the same from outside the validation block the ordering makes them distinguishable in the audit log.

The retry configuration on the HTTP Request node handles transient 429 and 5xx responses before they reach the Parse Response node. When retries are exhausted, the On Error output delivers the error payload to Parse Response. Without that connection, the Parse Response node never runs on HTTP failure the output contract is not fulfilled, and the record falls off the workflow.

Three Response States

An AI API response arrives in one of three states, and the Parse Response node must handle all three before the workflow reaches production.

State 1 HTTP 200, parseable content. This is the happy path. The response contains a JSON object whose choices[0].message.content field, when parsed, conforms to the output schema. The Parse Response node extracts the content, calls JSON.parse(), validates all fields, and sets ai_result_valid = true.

State 2 HTTP 200, non-conforming content. The HTTP layer succeeded, but choices[0].message.content cannot be parsed as JSON, or the parsed object fails field validation. The HTTP Request node reports success because it succeeded at the HTTP level. The failure is in the content layer, and retrying will not fix it. The Parse Response node must catch this with a try/catch around JSON.parse() and the field validation checks, setting ai_result_valid = false and populating parse_error with a specific description.

State 3 HTTP error. The response has a 4xx or 5xx status code. The correct response is error-specific:

HTTP Status Meaning Correct Response
400 Bad Request Malformed request body (invalid model name, missing field, prompt too long) Do not retry. This is a configuration or prompt bug.
401 Unauthorized Invalid or expired API key Do not retry. Key rotation required.
429 Too Many Requests Rate limit exceeded Retry with backoff. If retries exhausted, ai_result_valid = false.
500 Internal Server Error Transient server failure Retry with backoff. If retries exhausted, ai_result_valid = false.
503 Service Unavailable API temporarily unavailable Retry with backoff. If retries exhausted, ai_result_valid = false.

Retry configuration in n8n. The HTTP Request node has built-in retry-on-fail configuration that handles network-level retries before the response reaches the Parse Response node. Set Retry on Fail to enabled, Max Tries to 3, and Wait Between Tries to 500ms. Connect the On Error output of the HTTP Request node to the Parse Response node this ensures that API failures produce a normalized output with ai_result_valid = false rather than a halted execution. Figure 11.1 shows the complete retry decision flow.

%%{init:{"theme":"base","themeVariables":{"primaryColor":"#eef2ff","primaryTextColor":"#1e1b4b","primaryBorderColor":"#6366f1","lineColor":"#6366f1","clusterBkg":"#f8f9ff","clusterBorder":"#6366f1","titleColor":"#1e1b4b","edgeLabelBackground":"#f6f4ef","fontFamily":"system-ui,sans-serif","fontSize":"13px"}}}%%

flowchart TD
    A["HTTP Request"]:::process --> B{Success?}
    B -->|Yes| C["Parse Response ai_result_valid = true"]:::process
    B -->|No| D{Attempt ≤ 3?}
    D -->|Yes| E["Wait exponential delay"]:::process
    E --> A
    D -->|No| F["Rule Fallback ai_confidence = 0"]:::fallback
    classDef trigger  fill:#dcfce7,stroke:#16a34a,color:#14532d,font-weight:600
    classDef process  fill:#eef2ff,stroke:#6366f1,color:#1e1b4b
    classDef decision fill:#fef9c3,stroke:#ca8a04,color:#78350f,font-weight:600
    classDef success  fill:#d1fae5,stroke:#059669,color:#064e3b,font-weight:700
    classDef fallback fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
Figure 11.1: Retry and Fallback Decision Flow. Retry decision flow: three attempts with exponential delay before routing to the rule fallback execution never halts on API failure.

Metadata capture. Every successful response includes fields that belong in the normalized output:

const model_used    = $json.model;                    // e.g. "gpt-4o-mini-2024-07-18"
const request_id    = $json.id;                       // e.g. "chatcmpl-abc123"

// $json.usage holds token counts check it exists before reading its fields
const usage         = $json.usage || {};
const input_tokens  = usage.prompt_tokens     || 0;
const output_tokens = usage.completion_tokens || 0;
const total_tokens  = usage.total_tokens      || 0;

The request_id is essential for debugging. When an output is incorrect and the engineer needs to find the specific call in the OpenAI usage dashboard, request_id is the only identifier available. Capturing it costs nothing and eliminates a category of investigation that would otherwise require guessing from timestamps.

The validation sequence in the Parse Response node runs in a defined order:

1. Was there an HTTP error?                    → ai_result_valid = false, classify error
2. Is choices[0].message.content present?      → if not, ai_result_valid = false
3. Does JSON.parse() succeed?                  → if not, ai_result_valid = false
4. Do all field validations pass?              → if not, ai_result_valid = false
5. All checks passed                           → ai_result_valid = true

This sequence guarantees that ai_result_valid is always set, regardless of where the failure occurred, and that parse_error contains a specific description that can be logged and diagnosed.

Error Code Diagnostic Table:

parse_error Value Root Cause Correct Remediation
HTTP_ERROR: 429 Rate limit hit; retries exhausted Reduce call volume or increase retry wait; check RPM/TPM ceiling
HTTP_ERROR: 500 Transient server failure; retries exhausted No action needed if infrequent; increase Max Tries if persistent
HTTP_ERROR: 401 Invalid or expired API key Rotate key in Credential Store; do not retry
HTTP_ERROR: 400 Malformed request body Inspect prompt_payload for invalid field or oversized prompt
ENVELOPE_ERROR: choices... API returned non-standard response structure Check model version; rerun with same payload to confirm transient
JSON_PARSE_ERROR Model returned non-JSON content Verify response_format: json_object is in request body
VALIDATION_ERROR: candidate_intent Field value outside permitted enum Update system prompt required-field instruction
VALIDATION_ERROR: confidence Confidence value outside 0–1 range Tighten prompt constraint on confidence field format
ImportantCritical Requirement

When an execution produces ai_result_valid = false, the parse_error field identifies the failure category. HTTP_429 means the rate limit was hit and retries were exhausted investigate call volume and retry configuration. JSON_PARSE_FAILED means the model returned non-JSON content verify that response_format: json_object is set in the request body. FIELD_MISSING: signals means a required field was absent update the prompt’s required-field instruction. Each error code maps to a different root cause and a different fix; the parse_error value is the diagnostic entry point, not a generic failure flag.

CautionProduction Risk

If the HTTP Request node’s On Error output is not connected to the Parse Response node, a failed HTTP call throws an execution error that stops the workflow entirely the Parse Response node never runs, the normalized output is never produced, and the record is not routed to the fallback path. The On Error connection is what makes the service layer fault-tolerant.


1.3.5 Cost, Rate Limits, and Operational Considerations

Business objective: Establish per-deployment cost and capacity estimates before production launch so that token budget, spending limits, and rate-limit headroom are configured decisions rather than post-incident discoveries.

Engineering objective: Derive per-call token counts from component sizes, project to daily volume, and map against Tier 1 rate limits to identify ceiling risk.

Expected outcome: A spending limit is configured in the OpenAI dashboard before the first client deployment; total_tokens is logged on every call so that anomalies are detectable in the audit record.

Token-Based Billing

AI API calls are billed per token. A token is approximately four characters of English text. Every character in the system message, user message, and the model’s response contributes to the billing calculation.

Cost estimate for gpt-4o-mini (approximate 2024 pricing):

Component Tokens per Call Per-Call Cost
System message ~350 tokens ~$0.000053
User message (500 char truncated) ~130 tokens ~$0.000020
Response (max_tokens: 200) ~120 tokens ~$0.000072
Total per call ~600 tokens ~$0.000145

At this cost profile, 1,000 calls per day costs $0.15. Even at 10,000 calls per day, the monthly cost is approximately $44. These figures are operationally trivial for professional deployments, which is the primary reason gpt-4o-mini is the recommended model for Part I and Part II classification tasks. The same workflow using gpt-4o costs approximately $0.0022 per call fifteen times higher with no corresponding improvement in output quality for structured classification with a well-engineered prompt.

Rate limits for Tier 1 accounts:

Limit Type Default (Free Tier) Paid (Tier 1)
Requests per minute (RPM) 500 RPM 500 RPM
Tokens per minute (TPM) 200,000 TPM 200,000 TPM
Requests per day (RPD) 10,000 RPD Unlimited

At 600 tokens per call, a Tier 1 account can sustain approximately 333 calls per minute before hitting the TPM ceiling. For most small-to-medium business automation use cases this is more than sufficient. High-volume workflows exceeding 200 calls per minute sustained require either request queuing or a tier upgrade.

Cost estimation is an engineering responsibility, not a post-deployment discovery. Before any client deployment, estimate the per-call cost, multiply by expected daily volume, set a hard spending limit in the OpenAI dashboard, and set a soft notification at 80% of budget. These configurations take less than ten minutes and prevent the two most common AI API production incidents: unexpected bills from traffic spikes or misconfigured retry loops, and rate limit failures that exhaust the daily request quota. The suitability check from Chapter 1.1 has direct cost implications: by routing submissions with insufficient cover letters to the rule-only path, it bypasses the AI call entirely. For a workflow where 30% of submissions fail the pre-flight check, that is a 30% reduction in API call volume and a direct, zero-configuration cost reduction.

TipDesign Practice

Log total_tokens from the API response’s usage field in every audit record. A misconfigured retry loop three retries on every call, including successful ones triples API cost with no quality benefit; the token log exposes this immediately as a 3× spike in daily token consumption. A sudden increase in total_tokens per call without a prompt change indicates a schema wrapper failure where the model is generating prose before or after the JSON object, inflating the output token count.


1.3.6 Vendor-Neutral Architecture

Business objective: Ensure that a provider migration from OpenAI to Anthropic or any equivalent Chat Completions-compatible API is a configuration change, not an architectural change.

Engineering objective: Isolate all provider-specific logic (endpoint, auth header, response extraction path) at the service layer boundary so that the prompt, validation logic, and output contract are unchanged across providers.

Expected outcome: Provider migration requires exactly five changes to the HTTP Request node configuration; no Code node logic, output contract, or downstream routing changes are required.

Vendor-Neutral Service Layer

The three-node AI service layer is architecturally independent of the LLM provider. The prompt engineering techniques, the output schema, the validation logic, and the normalized output contract are identical whether the underlying API is OpenAI Chat Completions or Anthropic Messages. What changes between providers is a small, well-defined set of operational parameters.

To switch from OpenAI to Anthropic, five changes are required: the endpoint URL, the authentication header name, the version header (added for Anthropic), the response extraction path (content[0].text instead of choices[0].message.content), and the JSON enforcement strategy (system prompt instruction rather than response_format parameter). The prompt, the output schema, the validation logic, and the normalized output contract remain unchanged.

This portability is the result of building the service layer around the output contract rather than the provider API. The Build Prompt node’s prompt_payload object abstracts the message structure; the Parse Response node’s validation logic operates on the parsed JSON content, not on the HTTP envelope. Provider migration is a configuration change, not an architectural change.

Engineers who find themselves unable to migrate providers are typically those who embedded provider-specific logic in Code nodes response extraction paths, error message strings, API-specific header handling rather than isolating it at the service layer boundary.

Provider Migration Checklist:

Change OpenAI Value Anthropic Value
Endpoint URL https://api.openai.com/v1/chat/completions https://api.anthropic.com/v1/messages
Auth header name Authorization: Bearer {key} x-api-key: {key}
Version header Not required anthropic-version: 2023-06-01 (required)
Response extraction path choices[0].message.content content[0].text
JSON enforcement response_format: { type: "json_object" } System prompt instruction (parameter not available)
Prompt, validation, output contract Unchanged Unchanged

1.3.7 Reusable AI Service Layer Pattern

Business objective: Make the AI service layer reusable across workflows and replaceable as a unit so downstream nodes never depend on the HTTP envelope, the provider, or the retry configuration.

Engineering objective: Define an explicit input contract (Build Prompt output) and output contract (Parse Response output) that encapsulate all provider interaction details within the service layer boundary.

Expected outcome: Any downstream node that consumes ai_result_valid and the result fields is fully isolated from provider changes, model upgrades, and retry configuration changes.

Architecture and Design Rationale

The output contract pattern is the primary architectural decision in this chapter. It would be simpler to pass the raw API response directly to the next node fewer nodes, less code. The trade-off is that every node downstream would then depend on the OpenAI response envelope structure: choices[0].message.content, usage.prompt_tokens, id. A provider change or a model update that modifies the response envelope would require changes across every consuming node.

The contract pattern confines that coupling to one place: the Parse Response node. All other nodes depend on ai_result_valid, candidate_intent, api_metadata.request_id stable names that do not change when the provider does. The cost is one additional Code node. The benefit is that the service layer is replaceable without touching the downstream workflow.

Normalized Output Contract

The three-node AI service layer has two contracts: an input contract that specifies what the Build Prompt node must provide, and an output contract that specifies what the Parse Response node always produces. Everything within the layer the HTTP configuration, the retry logic, the metadata capture is an implementation detail invisible to the nodes that call into or consume from the layer.

Input contract (Build Prompt Code node output):

{
  prompt_payload: {
    model:           "gpt-4o-mini",
    temperature:     0.1,
    max_tokens:      200,
    response_format: { type: "json_object" },
    messages: [
      { role: "system", content: "<system_message_string>" },
      { role: "user",   content: "<user_message_string>"   }
    ]
  },
  prompt_version:   "1.0.0",
  call_start_time:  Date.now()
}

Output contract (Parse Response Code node output):

{
  // AI result fields (populated when ai_result_valid = true, null otherwise)
  ai_result_valid:   true | false,
  candidate_intent:  "advance" | "hold" | "reject" | null,
  experience_level:  "junior" | "mid" | "senior" | null,
  skill_category:    "technical" | "non-technical" | "hybrid" | null,
  confidence:        0.00–1.00 | null,
  signals:           string[] | null,
  reasoning:         string | null,

  // API metadata (populated from response; defaults to null on HTTP error)
  api_metadata: {
    request_id:    string | null,
    model_used:    string | null,
    input_tokens:  number | null,
    output_tokens: number | null,
    total_tokens:  number | null,
    latency_ms:    number | null,
    call_timestamp: string | null
  },

  // Error description (null when ai_result_valid = true)
  parse_error: string | null
}

Every node downstream of the Parse Response node consumes this object. The IF node in Chapter 1.4 AI-Powered Workflow Design routes on ai_result_valid. The scoring logic uses candidate_intent, experience_level, skill_category, and confidence. The audit logger records the complete object, including api_metadata. No downstream node needs to know whether the AI call succeeded, which error occurred, or what the HTTP envelope looked like. This separation of concerns the service layer owns the provider interaction, the downstream nodes own the business logic is what makes the layer reusable. To replace OpenAI with Anthropic, or to upgrade from gpt-4o-mini to a different model, the output contract remains unchanged.

The ai_result_valid flag is the binary contract between the AI service layer and every node that follows. When true, the downstream scoring formula can use the AI fields. When false, the fallback path executes with rule-only scoring. That binary is the engineering payoff of the normalized output contract: a single field, set by one node, governs every execution path in the downstream workflow without any other node needing to know what the AI actually produced, or why.

TipDesign Practice

Define the Parse Response output contract before writing any downstream logic. Every node that consumes AI output should depend on the contract object not on the HTTP response structure or the raw API envelope. When the contract is stable, provider changes, model upgrades, and retry configuration changes are invisible to the rest of the workflow.


Practical Exercise 1.3 Production-Ready AI Service Layer

Objective

Upgrade the recruiting workflow’s AI service layer from a development-ready configuration to a production-ready one: Credential Store authentication, explicit model parameters, retry and timeout configuration, On Error wiring, and a Parse Response node that handles all three response states and captures API metadata.

Requirements

  • n8n instance with the workflow from Chapter 1.2 loaded and accessible
  • An OpenAI API key with access to gpt-4o-mini (Tier 1 account recommended)
  • The workflow’s HTTP Request node is currently using a hardcoded credential placeholder
  • The Parse Response node does not yet capture API metadata or handle HTTP errors

Implementation Steps


Step 1 Update the Build Prompt Code Node

Purpose

Introduce a centralized AI_CONFIG constant and a call_start_time timestamp into the Build Prompt node output. Without call_start_time, the Parse Response node has no reference point for calculating end-to-end latency. Without AI_CONFIG as a centralized constant, model parameters are scattered across the node and require touching multiple lines when the model is upgraded or the token budget is changed.


Operation Summary

Property Value
Node Type Code (JavaScript)
Primary Function Assemble the prompt_payload object and record call_start_time
Output prompt_payload, prompt_version, call_start_time

Open the Build Prompt Code node. Add the following at the top, and update the prompt_payload construction to spread AI_CONFIG rather than specifying each parameter inline:

// Add at the top of the node
const PROMPT_VERSION = "1.0.0";

// Build the request body to send to the OpenAI API
const prompt_payload = {
  model:           "gpt-4o-mini",
  temperature:     0.1,
  max_tokens:      200,
  response_format: { type: "json_object" },
  messages: [
    { role: "system", content: SYSTEM_MESSAGE },
    { role: "user",   content: user_message   }
  ]
};

return [{
  json: {
    prompt_payload:  prompt_payload,
    prompt_version:  PROMPT_VERSION,
    call_start_time: Date.now(),
    candidate_name:  $json.candidate_name,
    position_title:  $json.position_title,
    rule_score:      $json.rule_score,
    processing_path: $json.processing_path
  }
}];

Output Table

Output Description
prompt_payload Complete request body object ready for the HTTP Request node
prompt_version Version string for the system prompt ("1.0.0")
call_start_time Unix timestamp (ms) captured at request assembly time

Production Considerations

NoteEngineering Rationale

The AI_CONFIG constant is the single source of truth for all four model parameters. A model upgrade from gpt-4o-mini to a newer model requires changing one string in one place. The call_start_time field, set at the moment the request is assembled, enables the Parse Response node to calculate true end-to-end latency including network transit and inference time data that is otherwise unavailable without external monitoring.


Step 2 Configure the HTTP Request Node

Purpose

Apply production-grade settings to the HTTP Request node: authenticated credential, explicit timeout, retry-on-fail, and On Error wiring to the Parse Response node. Without a timeout, the node can block indefinitely during high-latency inference. Without retry, the workflow fails permanently on the first 429 or transient 5xx. Without an On Error connection, any HTTP error stops the entire workflow execution and no advisory output is produced.


Operation Summary

Property Value
Method POST
Endpoint https://api.openai.com/v1/chat/completions
Authentication Header Auth → OpenAI API credential
Body Source { JSON.stringify($json.prompt_payload) }
Timeout 15 000 ms
Retry on Fail Enabled Max 3 tries, 500 ms wait
On Error target Parse Response Code node

Open the HTTP Request node. Apply the following configuration changes:

Authentication: Change from the current placeholder to Predefined Credential Type → Header Auth and select OpenAI API. If the credential does not exist yet, create it in Settings → Credentials as described in Section 1.3.2.

Request body: Set Specify Body to Using JSON and enter the expression:

{{ JSON.stringify($json.prompt_payload) }}

Timeout: Set to 15000 (15 seconds).

Retry on Fail: Enable. Set Max Tries to 3. Set Wait Between Tries to 500ms.

Response Format: Set to JSON.

On Error connection: Add a connection from the On Error output of this node to the Parse Response Code node. Both the normal output and the On Error output should connect to Parse Response.

After saving, verify that the Authorization header does not appear in the node configuration if it does, the Credential Store setup is incomplete.

Output Table

Output Description
(on success) Full OpenAI API response object passed to Parse Response
(on On Error) HTTP error object delivered to Parse Response via the error path

Production Considerations

NoteEngineering Rationale

Each of these settings addresses a specific production failure mode. The timeout prevents indefinite blocking. The retry absorbs transient 429 and 5xx responses without surfacing them to the downstream workflow. The On Error connection to Parse Response is the architectural decision that makes the service layer fault-tolerant HTTP errors produce normalized ai_result_valid = false outputs rather than halted executions. Together, these three settings change the HTTP Request node from a development convenience to a production component.


Step 3 Update the Parse Response Code Node

Purpose

Upgrade the Parse Response node to handle all three response states the service layer can receive HTTP 200 with valid content, HTTP 200 with unparseable content, and HTTP error always producing the normalized output contract. With the On Error connection now wired from Step 2, this node receives two different input shapes. Without HTTP error detection, an error object causes choices[0].message.content extraction to throw an uncaught exception.


Operation Summary

Property Value
Node Type Code (JavaScript)
Primary Function Validate, parse, and normalize the AI API response
Input shapes OpenAI 200 response OR n8n HTTP error object
Output Normalized output contract (always produced)

Replace the Parse Response Code node contents with the following:

// ── Normalized output scaffold ────────────────────────────────────
const call_start_time = $('Build Prompt').first().json.call_start_time || null;
const call_end_time   = Date.now();

const result = {
  ai_result_valid:  false,
  candidate_intent: null,
  experience_level: null,
  skill_category:   null,
  confidence:       null,
  signals:          null,
  reasoning:        null,
  api_metadata: {
    request_id:     null,
    model_used:     null,
    input_tokens:   null,
    output_tokens:  null,
    total_tokens:   null,
    latency_ms:     call_start_time !== null ? call_end_time - call_start_time : null,
    call_timestamp: new Date().toISOString()
  },
  parse_error: null
};

// ── Step 1: HTTP error check ──────────────────────────────────────
if ($json.error || $json.statusCode >= 400) {
  const error_code = $json.statusCode || "unknown";
  const error_msg  = $json.message || "no message";
  result.parse_error = "HTTP_ERROR: " + error_code + " " + error_msg;
  return [{ json: result }];
}

// ── Step 2: API metadata extraction ──────────────────────────────
result.api_metadata.request_id    = $json.id    || null;
result.api_metadata.model_used    = $json.model || null;
// Read token counts from the usage object (check it exists first)
const usage = $json.usage || {};
result.api_metadata.input_tokens  = usage.prompt_tokens     || null;
result.api_metadata.output_tokens = usage.completion_tokens || null;
result.api_metadata.total_tokens  = usage.total_tokens      || null;

// ── Step 3: Response envelope validation ─────────────────────────
const choices = $json.choices;
if (!choices || !Array.isArray(choices) || choices.length === 0) {
  result.parse_error = "ENVELOPE_ERROR: choices array missing or empty";
  return [{ json: result }];
}

const raw_content = choices[0] && choices[0].message ? choices[0].message.content : null;
if (typeof raw_content !== "string" || raw_content.trim().length === 0) {
  result.parse_error = "ENVELOPE_ERROR: choices[0].message.content missing or empty";
  return [{ json: result }];
}

// ── Step 4: JSON parse ────────────────────────────────────────────
let parsed;
try {
  parsed = JSON.parse(raw_content);
} catch (e) {
  result.parse_error = "JSON_PARSE_ERROR: " + e.message + " | raw: " + raw_content.substring(0, 100);
  return [{ json: result }];
}

if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
  result.parse_error = "JSON_STRUCTURE_ERROR: parsed value is not an object";
  return [{ json: result }];
}

// ── Step 5: Field validation ──────────────────────────────────────
const PERMITTED_INTENT     = ["advance", "hold", "reject"];
const PERMITTED_EXPERIENCE = ["junior", "mid", "senior"];
const PERMITTED_SKILL      = ["technical", "non-technical", "hybrid"];
const errors = [];

if (!parsed.hasOwnProperty("candidate_intent") ||
    !PERMITTED_INTENT.includes(parsed.candidate_intent)) {
  errors.push(`invalid candidate_intent: "${parsed.candidate_intent}"`);
}
if (!parsed.hasOwnProperty("experience_level") ||
    !PERMITTED_EXPERIENCE.includes(parsed.experience_level)) {
  errors.push(`invalid experience_level: "${parsed.experience_level}"`);
}
if (!parsed.hasOwnProperty("skill_category") ||
    !PERMITTED_SKILL.includes(parsed.skill_category)) {
  errors.push(`invalid skill_category: "${parsed.skill_category}"`);
}
if (!parsed.hasOwnProperty("confidence") ||
    typeof parsed.confidence !== "number" ||
    parsed.confidence < 0 || parsed.confidence > 1) {
  errors.push(`invalid confidence: ${parsed.confidence}`);
}
if (!parsed.hasOwnProperty("signals") ||
    !Array.isArray(parsed.signals) || parsed.signals.length === 0) {
  errors.push("invalid signals: must be non-empty array");
}
if (!parsed.hasOwnProperty("reasoning") ||
    typeof parsed.reasoning !== "string") {
  errors.push("invalid reasoning: must be string");
}

if (errors.length > 0) {
  result.parse_error = `VALIDATION_ERROR: ${errors.join("; ")}`;
  return [{ json: result }];
}

// ── Step 6: Populate result ───────────────────────────────────────
result.candidate_intent  = parsed.candidate_intent;
result.experience_level  = parsed.experience_level;
result.skill_category    = parsed.skill_category;
result.confidence        = parsed.confidence;
result.signals           = parsed.signals;
result.reasoning         = parsed.reasoning;
result.ai_result_valid   = true;

return [{ json: result }];

Implementation Logic

The six-step validation sequence processes two distinct input shapes depending on which output of the HTTP Request node fired:

// Step 1: HTTP error check catches On Error inputs
if ($json.error || $json.statusCode >= 400) { ... }

// Step 2: API metadata extraction
result.api_metadata.request_id = $json.id || null;

// Step 3: Envelope validation choices array must exist
const choices = $json.choices;

// Step 4: JSON.parse() catches non-JSON content from model
parsed = JSON.parse(raw_content);

// Step 5: Field validation enum and type checks
const errors = [];

// Step 6: Populate result on success
result.ai_result_valid = true;

Step 1 fires only when the On Error path delivers an HTTP error object the $json.error or $json.statusCode >= 400 check distinguishes it from a successful response. Steps 2–6 execute on the normal success path. The early-return pattern at every step ensures ai_result_valid is always set regardless of where processing stops.


Output Table

Output Description
ai_result_valid Boolean always set; true only when all six steps pass
candidate_intent Enum string or null
experience_level Enum string or null
skill_category Enum string or null
confidence Float 0.00–1.00 or null
signals String array or null
reasoning String or null
api_metadata Object with request_id, model_used, token counts, latency_ms, call_timestamp
parse_error Specific error code string or null

Production Considerations

NoteEngineering Rationale

This node now handles all three response states the service layer can receive. The normalized output scaffold is initialized at the top ai_result_valid: false as the default, all fields null and the node returns early with that scaffold populated at the specific error step. ai_result_valid is always set. The downstream IF node in Chapter 1.4 AI-Powered Workflow Design routes on this flag without needing to know which failure category produced it.


Step 4 Verify Node Connections

The On Error connection is easy to configure incorrectly the normal output and the error output look similar in the n8n canvas, and an unconfigured On Error path only fails in production when an HTTP error occurs. Verifying the wiring before testing prevents silent routing gaps.

After the three node updates, the “ai” branch of the workflow should resemble this:

[Code: Build Prompt v1.0.0]
    ↓ (normal)
[HTTP Request: OpenAI (v2)]
    ↓ (normal)     ↓ (On Error)
         ↘        ↙
    [Code: Parse Response]
         ↓
    [HTTP Request: Slack]

Both the normal output and the On Error output of the HTTP Request node should connect to Parse Response. In the n8n canvas, this appears as two arrows leaving the HTTP Request node and converging on the Parse Response node.

TipDesign Practice

Until the execution log shows Parse Response firing on an API failure, the fault-tolerance is not confirmed it is assumed. The connection diagram is the target state; the test cases in Step 5 are what verify the assumption.


Step 5 Test the Integration

Four test cases cover the response states and edge conditions the service layer must handle. Each test case should produce a verifiable result visible in the n8n execution log.

Test Case A Valid cover letter (happy path). Send a webhook payload with a substantive cover letter (over 15 words, specific to the position). The expected result: ai_result_valid = true, all six AI result fields populated and passing their type and enum constraints, api_metadata.request_id a non-null string beginning with "chatcmpl-", and api_metadata.latency_ms a positive integer.

Test Case B Rule path (short cover letter). Send a payload with a three-word cover letter. The Switch node should route to “rules”; the Build Prompt, HTTP Request, and Parse Response nodes should not execute. Verify in the execution log that no API call is made.

Test Case C Parse error simulation. Temporarily modify the system message to produce an unparseable output for example, add “wrap your response in a markdown code block” to the prompt. Send a valid payload. The expected result: the HTTP Request node returns HTTP 200, ai_result_valid = false, and parse_error contains JSON_PARSE_ERROR or VALIDATION_ERROR. The workflow should continue to the Slack node.

Test Case D Metadata verification. For any successful execution, inspect the Parse Response node output in the n8n execution log. Verify that api_metadata contains non-null values for request_id, model_used, input_tokens, output_tokens, and latency_ms.

TipDesign Practice

Test Case C is the most important one it verifies that the On Error path is actually wired and that the Parse Response node handles failure inputs gracefully. A workflow that only passes Test Cases A and B has been tested on happy-path inputs only. The service layer’s value is that it also handles Cases C and D without breaking.


Post-Implementation Workflow State

The complete workflow as it enters Chapter 1.4:

[Webhook]
    ↓
[Code: Suitability Evaluation]
    ↓
[Switch: processing_path]
    ├─ "rules"  → [Code: Rule Score] → [HTTP: Slack]
    ├─ "review" → [HTTP: Slack]
    └─ "ai"     → [Code: Build Prompt v1.0.0]
                        ↓
                  [HTTP Request: OpenAI (v2)]
                   ↓ (normal)  ↓ (On Error)
                        ↘     ↙
                  [Code: Parse Response]
                        ↓
                  [HTTP: Slack]

The ai_result_valid flag is set on every execution of Parse Response. Chapter 1.4 AI-Powered Workflow Design adds the IF node that routes on this flag true to the AI scoring path, false to the rule-only fallback and the Merge node that rejoins both paths before the Slack notification.


Validation Steps

After completing Steps 1–5, verify the following in the n8n execution log before considering this exercise complete:

  1. Credential store the HTTP Request node configuration panel shows no Authorization header value. The header is injected at runtime only.
  2. Timeout active the node configuration shows Timeout: 15000ms.
  3. Retry active Retry on Fail: enabled, Max Tries: 3, Wait: 500ms.
  4. On Error wired in the canvas, two arrows leave the HTTP Request node and both connect to Parse Response.
  5. Metadata populated a successful Test Case A execution shows api_metadata.request_id as a non-null string in the Parse Response node output.
  6. Error path functional Test Case C execution shows ai_result_valid: false and a non-null parse_error in the Parse Response node output, and the execution does not halt.

Expected Output

A successful implementation produces the following in a Test Case A execution:

{
  ai_result_valid:  true,
  candidate_intent: "advance",      // or "hold" or "reject"
  experience_level: "mid",          // or "junior" or "senior"
  skill_category:   "technical",    // or "non-technical" or "hybrid"
  confidence:       0.82,           // number between 0 and 1
  signals:          ["...", "..."], // non-empty string array
  reasoning:        "...",          // non-empty string
  api_metadata: {
    request_id:    "chatcmpl-abc123",
    model_used:    "gpt-4o-mini-2024-07-18",
    input_tokens:  480,
    output_tokens: 120,
    total_tokens:  600,
    latency_ms:    1240,
    call_timestamp: "2024-11-01T14:32:00.000Z"
  },
  parse_error: null
}

In a Test Case C execution (simulated parse failure), ai_result_valid is false, parse_error contains a specific error code, and all AI result fields are null.


Troubleshooting

Symptom Likely Cause Resolution
Authorization header visible in node config Credential Store not selected; plain-text header entered Remove header entry; set Authentication to Header Auth credential
parse_error: "HTTP_ERROR: 401" API key invalid or not saved correctly in credential Regenerate key in OpenAI dashboard; update credential value
parse_error: "JSON_PARSE_ERROR" response_format: json_object missing from request body Add parameter to AI_CONFIG; verify prompt_payload spread includes it
Parse Response not executing on API failure On Error output not connected to Parse Response Add the On Error connection in the canvas
api_metadata.latency_ms is null call_start_time not set in Build Prompt output Add call_start_time: Date.now() to Build Prompt return object
Execution halts on HTTP error On Error path unconnected; n8n throws uncaught error Wire On Error output to Parse Response (see Step 4)
total_tokens unexpectedly high Model generating prose wrapper around JSON Verify response_format: json_object active; inspect raw choices[0].message.content in execution log

Key Lessons

  1. The three-phase API model (request, inference, response) maps directly to node configuration decisions it is a diagnostic frame, not background information.
  2. Credential Store is not optional for production. Keys in Code nodes are keys that have been logged.
  3. AI_CONFIG as a centralized constant is the only maintainable approach to model parameter management across environments.
  4. The On Error connection is the single configuration that separates a fault-tolerant service layer from one that halts on the first HTTP failure.
  5. ai_result_valid: false is a legitimate, handled outcome not an error state. The workflow continues; the downstream IF node decides what to do with it.
  6. request_id is the only link between a specific workflow execution and the OpenAI usage dashboard. Capturing it at zero cost prevents an entire category of debugging dead-ends.
  7. Estimate token cost before deployment. At gpt-4o-mini rates, most Part I workflows cost under $5/month at typical volumes.

Reference Architecture Flow

NoteEngineering Rationale

This flow represents the complete AI service layer as it exits Chapter 1.3. Each node boundary is a contract boundary: the Build Prompt node’s output contract feeds the HTTP Request node’s body, and the Parse Response node’s output contract feeds all downstream routing. The On Error path is architecturally equivalent to the success path it produces the same output shape. Chapter 1.4 AI-Powered Workflow Design adds the IF node that acts on ai_result_valid.

AI service layer pipeline Chapter 1.3

Input: resume_text, cover_letter, job_id (from Switch node).

Node Detail Output
Code: Build Prompt v1.0.0 Assembles request payload prompt_payload { model, temperature, max_tokens, response_format, messages[] }, prompt_version "1.0.0", call_start_time = Date.now()
HTTP Request: OpenAI (v2) POST https://api.openai.com/v1/chat/completions; Auth: Credential Store -> OpenAI API (Header Auth); Body: { JSON.stringify($json.prompt_payload) }; Timeout: 15,000ms; Retry: Max 3, Wait 500ms Success (HTTP 200) -> Parse Response. On Error (4xx/5xx after retries exhausted) -> Parse Response
Code: Parse Response Step 1: HTTP error check -> early return with parse_error. Step 2: Extract api_metadata (request_id, model, tokens). Step 3: Validate choices envelope -> early return if missing. Step 4: JSON.parse(choices[0].message.content) -> catch errors. Step 5: Field validation (enum, type, range) -> collect errors. Step 6: Populate result, set ai_result_valid = true Output contract (always produced): ai_result_valid: true \| false; candidate_intent, experience_level, skill_category; confidence, signals, reasoning; api_metadata { request_id, model_used, tokens, latency_ms }; parse_error: null \| "HTTP_ERROR:..." \| "JSON_PARSE_ERROR"
IF: ai_result_valid (added in Chapter 1.4) Routes on validity flag true -> AI scoring path; false -> Rule-only fallback

Chapter Summary

This chapter transformed the basic AI API call from Chapter 1.2 into a production-grade service layer. Each engineering decision addresses a specific failure mode.

Authentication moved from a hardcoded string to the n8n Credential Store: the API key is now encrypted at rest, absent from execution logs and workflow exports, and rotatable without modifying any node. The request body consolidates all model parameters in an AI_CONFIG constant, making the integration maintainable and auditable.

The HTTP Request node is configured with a 15-second timeout, three-attempt retry with 500ms backoff, and an On Error connection to the Parse Response node. The Parse Response node handles all three response states successful valid, successful invalid, and HTTP error producing the same normalized output object in every case.

Every execution records request_id, model_used, token counts, latency, and timestamp for cost monitoring and debugging without requiring the OpenAI dashboard.

The output contract a fixed-schema object with ai_result_valid as its primary flag is the interface between this service layer and the rest of the workflow. Chapter 1.4 AI-Powered Workflow Design builds on it.


Transition to Chapter 1.4

The AI service layer built in this chapter produces a normalized output object on every execution. One field in that object ai_result_valid is always set, but nothing in the current workflow acts on it. A successful AI call and a failed one both proceed to the Slack node by the same path.

Chapter 1.4 AI-Powered Workflow Design introduces the routing architecture that governs what happens after the service layer completes: the IF node that routes on ai_result_valid, the rule-only fallback path for failed AI calls, the Merge node that rejoins the AI success and rule fallback paths before Slack notification, and the requires_manual_review flag with its low-confidence escalation alert. After Chapter 1.4 AI-Powered Workflow Design, the recruiting workflow handles all four cases correctly: AI success with high confidence, AI success with low confidence (human review), AI failure (rule fallback), and pre-flight rejection.


Key Takeaways

  1. An AI API call has three phases request, inference, response and each phase has distinct configuration implications for timeout, retry, and response extraction.
  2. Store API keys in the n8n Credential Store. Keys embedded in Code nodes appear in execution logs, exports, and workflow configurations visible to anyone with access.
  3. Set all four model parameters explicitly: model, temperature, max_tokens, response_format. None of the defaults are appropriate for production classification workflows.
  4. Configure HTTP Request retry with Max Tries 3 and Wait 500ms. Connect the On Error output to the Parse Response node. The On Error connection is what makes the service layer fault-tolerant.
  5. The Parse Response node always produces a normalized output object with ai_result_valid set regardless of which of the three response states occurred.
  6. Capture request_id, model_used, token counts, and latency on every call. This data is the primary instrument for cost monitoring and incident debugging.
  7. Estimate per-call cost before deployment. At 600 tokens per call with gpt-4o-mini, 1,000 daily calls costs approximately $0.15/day.
  8. The three-node AI service layer is provider-independent. Switching from OpenAI to Anthropic requires five configuration changes; the prompt, validation logic, and output contract are unchanged.
  9. The normalized output contract the fixed-schema object with ai_result_valid as its flag is the interface between the AI service layer and all downstream workflow logic.

End of Chapter 1.3 AI API Integration