{
  "name": "DeepfakePolicy - Cross-check a case and return its result",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "deepfakepolicy-cross-check-v1",
        "authentication": "headerAuth",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "dfp-recipe-1",
      "name": "Incoming case",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        0,
        0
      ],
      "webhookId": "07b33a8f-f85c-4c13-9611-7277d30f2095",
      "notes": "Select a Header Auth credential with X-Workflow-Key and a new inbound secret. Never use your DeepfakePolicy API key for inbound authentication.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "function prepareRequest(body, now = Date.now()) {\n  const state = { action: 'invalid', externalCaseId: null, submissionId: null, jobId: null, operation: 'submit', request: null, attempts: 0, pollCount: 0, deadline: now + 15 * 60_000, responded: false, done: false, replyReady: false, deliver: false, waitSeconds: 10 };\n  const fail = (message) => ({ ...state, done: true, replyReady: true, responseCode: 400, output: { schemaVersion: 'deepfakepolicy.integration.v1', externalCaseId: state.externalCaseId, status: 'invalid_request', error: { code: 'invalid_request', message } } });\n  if (!body || typeof body !== 'object' || Array.isArray(body)) return fail('Send a JSON object.');\n  const allowed = ['action', 'externalCaseId', 'submissionId', 'jobId', 'comparison'];\n  if (Object.keys(body).some((key) => !allowed.includes(key))) return fail('Unsupported envelope field. Use action, externalCaseId, submissionId, jobId and comparison only.');\n  if (typeof body.externalCaseId !== 'string' || !/^[^\\u0000-\\u001f\\u007f]{1,160}$/.test(body.externalCaseId) || !body.externalCaseId.trim()) return fail('externalCaseId must be a nonempty identifier of at most 160 characters.');\n  state.externalCaseId = body.externalCaseId;\n  if (!['submit', 'status', 'resume'].includes(body.action)) return fail('action must be submit, status or resume.');\n  state.action = body.action;\n  if (body.action !== 'submit') {\n    if (body.comparison !== undefined || body.submissionId !== undefined) return fail('status and resume accept a jobId, never files or a new submissionId.');\n    if (typeof body.jobId !== 'string' || !/^job_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(body.jobId)) return fail('Supply the jobId returned by an earlier submission.');\n    state.jobId = body.jobId;\n    state.operation = 'read';\n    return state;\n  }\n  if (body.jobId !== undefined) return fail('Use resume to retrieve an existing job without starting analysis.');\n  if (typeof body.submissionId !== 'string' || !/^[A-Za-z0-9_-]{8,96}$/.test(body.submissionId)) return fail('submissionId must contain 8–96 letters, digits, hyphens or underscores; keep it for identical retries.');\n  state.submissionId = body.submissionId;\n  const request = body.comparison;\n  if (!request || typeof request !== 'object' || Array.isArray(request)) return fail('comparison must contain the complete Cross-check API request.');\n  const fields = ['useCase', 'locale', 'description', 'checklist', 'files', 'caseId', 'templateId', 'templateVersion', 'documentRoles', 'previousJobId', 'reuseExtractions'];\n  if (Object.keys(request).some((key) => !fields.includes(key))) return fail('Unsupported comparison field. API keys, URLs and organization IDs do not belong in the request.');\n  if (!Array.isArray(request.files) || request.files.length < 1 || request.files.length > 6) return fail('Supply one to six files.');\n  if (typeof request.description !== 'string' || request.description.length > 6000 || (request.files.length === 1 && !request.description.trim())) return fail('Supply a description of at most 6,000 characters; one-file cases require a description.');\n  if (request.useCase !== undefined && !['general', 'returns_warranty', 'insurance', 'logistics', 'rental', 'procurement', 'contract', 'payment_reconciliation'].includes(request.useCase)) return fail('Unsupported useCase.');\n  if (request.locale !== undefined && !['en', 'de', 'fr', 'nl'].includes(request.locale)) return fail('Unsupported locale.');\n  if (request.checklist !== undefined && (!Array.isArray(request.checklist) || request.checklist.length > 12 || request.checklist.some((item) => typeof item !== 'string' || !item.trim() || item.length > 180))) return fail('checklist allows up to twelve nonempty fields of at most 180 characters.');\n  const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n  if (request.caseId !== undefined && (typeof request.caseId !== 'string' || !uuid.test(request.caseId))) return fail('caseId must be an existing open DeepfakePolicy company-case UUID.');\n  if (request.previousJobId !== undefined && (typeof request.previousJobId !== 'string' || !/^job_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(request.previousJobId))) return fail('previousJobId must identify a completed comparison in the same case.');\n  if (request.previousJobId !== undefined && !request.caseId) return fail('previousJobId requires the same existing caseId as the completed comparison.');\n  if (request.reuseExtractions !== undefined && (typeof request.reuseExtractions !== 'boolean' || !request.previousJobId)) return fail('reuseExtractions requires previousJobId.');\n  if ((request.templateId !== undefined) !== (request.templateVersion !== undefined)) return fail('Supply templateId and templateVersion together.');\n  if (request.templateId !== undefined && (typeof request.templateId !== 'string' || !(['returns_warranty', 'equipment_rental', 'contract_documents', 'invoice_payments'].includes(request.templateId) || uuid.test(request.templateId)) || !Number.isInteger(request.templateVersion) || request.templateVersion < 1 || request.templateVersion > 1000)) return fail('Supply a built-in/company template and its integer version.');\n  if (request.documentRoles !== undefined) {\n    if (!request.documentRoles || typeof request.documentRoles !== 'object' || Array.isArray(request.documentRoles)) return fail('documentRoles must map source IDs to roles.');\n    for (const [id, role] of Object.entries(request.documentRoles)) {\n      if (!/^s[1-6]$/.test(id) || Number(id.slice(1)) > request.files.length || !['supporting', 'primary_contract', 'amendment', 'identity_document', 'company_record', 'invoice', 'acceptance', 'order', 'statement', 'previous_version'].includes(role)) return fail('Invalid document role or source ID.');\n    }\n  }\n  if ((request.useCase === 'contract' || request.templateId === 'contract_documents') && Object.values(request.documentRoles || {}).filter((role) => role === 'primary_contract').length !== 1) return fail('Contract checks require exactly one primary_contract document role.');\n  let bytes = 0;\n  for (const file of request.files) {\n    if (!file || typeof file !== 'object' || Array.isArray(file) || Object.keys(file).some((key) => !['name', 'contentType', 'base64'].includes(key))) return fail('Each file needs name, contentType and canonical base64 bytes; remote URLs are not supported.');\n    if (typeof file.name !== 'string' || !file.name.trim() || file.name.length > 180 || /[/\\\\\\u0000-\\u001f\\u007f]/.test(file.name)) return fail('Use filenames without paths or control characters, up to 180 characters.');\n    if (!['image/jpeg', 'image/png', 'image/webp', 'application/pdf', 'text/csv', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'].includes(file.contentType)) return fail('Unsupported file contentType.');\n    if (typeof file.base64 !== 'string' || !file.base64 || file.base64.length > 6_990_508 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(file.base64)) return fail('Supply canonical base64 without a data: prefix; each decoded file must be at most 5 MiB.');\n    const size = file.base64.length / 4 * 3 - (file.base64.endsWith('==') ? 2 : file.base64.endsWith('=') ? 1 : 0);\n    if (!size || size > 5 * 1024 * 1024) return fail('Each file must be nonempty and at most 5 MiB.');\n    bytes += size;\n  }\n  if (bytes > 10 * 1024 * 1024) return fail('The complete decoded bundle must be at most 10 MiB.');\n  // API validation remains authoritative for actual file signatures/pages and all billing/access gates.\n  state.request = JSON.parse(JSON.stringify(request));\n  return state;\n}\nreturn [{ json: prepareRequest($input.first().json.body), pairedItem: { item: 0 } }];"
      },
      "id": "dfp-recipe-2",
      "name": "Validate case",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "valid-request-",
              "leftValue": "={{ !$json.done }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "dfp-recipe-3",
      "name": "Valid request?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{ json: $input.first().json, pairedItem: { item: 0 } }];"
      },
      "id": "dfp-recipe-4",
      "name": "Request state",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        720,
        -120
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "submit-new-analysis-",
              "leftValue": "={{ $json.operation === 'submit' }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "dfp-recipe-5",
      "name": "Submit new analysis?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        960,
        -120
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://deepfakepolicy.com/v1/comparisons",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 20000,
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "={{ 'n8n-cross-check:' + $json.submissionId }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.request }}"
      },
      "id": "dfp-recipe-6",
      "name": "Submit Cross-check",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1200,
        -240
      ],
      "onError": "continueRegularOutput",
      "notes": "Select the same DeepfakePolicy Header Auth credential on both API nodes: Authorization = Bearer YOUR_LIVE_KEY. jobs:create + jobs:read required. Do not put a token in node parameters.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ 'https://deepfakepolicy.com/v1/jobs/' + $json.jobId }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 20000,
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "id": "dfp-recipe-7",
      "name": "Read existing job",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1200,
        0
      ],
      "onError": "continueRegularOutput",
      "notes": "Select the same DeepfakePolicy Header Auth credential on both API nodes: Authorization = Bearer YOUR_LIVE_KEY. jobs:create + jobs:read required. Do not put a token in node parameters.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "function handleResponse(inputState, response, now = Date.now()) {\n  const state = { ...inputState, attempts: inputState.attempts + 1, replyReady: false };\n  const object = (value) => value && typeof value === 'object' && !Array.isArray(value);\n  let body = response?.body;\n  if (typeof body === 'string') { try { body = JSON.parse(body); } catch { body = null; } }\n  const code = Number(response?.statusCode) || 0;\n  const apiCode = typeof body?.error?.code === 'string' && /^[a-z0-9_]{1,80}$/.test(body.error.code) ? body.error.code : null;\n  const result = (status, extra = {}) => ({ schemaVersion: 'deepfakepolicy.integration.v1', externalCaseId: state.externalCaseId, submissionId: state.submissionId, jobId: state.jobId, status, deliveryKey: state.jobId ? `dfp-cross-check:${state.jobId}:${status}` : `dfp-submission:${state.submissionId}:${status}`, ...extra });\n  const stop = (status, http, error, extra = {}) => {\n    state.done = true;\n    state.replyReady = !state.responded;\n    state.responseCode = http;\n    state.output = result(status, { error, ...extra });\n    state.deliver = state.action !== 'status';\n    state.request = null;\n    return state;\n  };\n  const retryHeader = response?.headers?.['retry-after'] ?? response?.headers?.['Retry-After'];\n  const retrySeconds = retryHeader === undefined ? Math.min(3 * 2 ** (state.attempts - 1), 30) : /^\\d+(?:\\.\\d+)?$/.test(String(retryHeader)) ? Math.ceil(Number(retryHeader)) : Math.ceil((Date.parse(String(retryHeader)) - now) / 1000);\n  const wait = Number.isFinite(retrySeconds) ? Math.max(1, retrySeconds) : 10;\n  const transient = code === 0 || code === 408 || code === 429 || code >= 500 || (code === 409 && ['upload_in_progress', 'comparison_upload_in_progress', 'idempotency_in_progress'].includes(apiCode));\n  if (transient) {\n    // Keep the caller's original body/key. Never change POST to a fresh submission after a timeout.\n    if (state.action !== 'status' && state.attempts < 3 && wait <= (state.responded ? 120 : 15) && now + wait * 1000 < state.deadline) {\n      state.waitSeconds = wait;\n      return state;\n    }\n    return stop('api_unavailable', code === 429 ? 429 : 503, { code: apiCode || 'api_unavailable', message: state.jobId ? 'Retrieve or resume the existing job. No new analysis was requested.' : 'Retry the identical submission with the same submissionId and the same company API key.' }, { retryAfterSeconds: wait });\n  }\n  if (code < 200 || code >= 300) {\n    const unavailable = code === 404 || code === 410;\n    return stop(unavailable ? 'unavailable' : 'api_rejected', code >= 400 && code < 500 ? code : 502, { code: apiCode || 'api_rejected', message: unavailable ? 'The report is unavailable or expired. This workflow will not recreate it.' : 'The API rejected this request. Review its code and your workspace configuration before retrying.' });\n  }\n  if (!object(body) || typeof body.id !== 'string' || !/^job_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(body.id) || (state.jobId && body.id !== state.jobId)) return stop('invalid_api_response', 502, { code: 'invalid_api_response', message: 'Unexpected API response. Retry the same submission or read the existing job; do not use a new submissionId.' });\n  const previousOperation = state.operation;\n  state.jobId = body.id;\n  state.operation = 'read';\n  state.request = null;\n  state.attempts = 0;\n  if (previousOperation === 'read') state.pollCount += 1;\n  const expires = typeof body.retentionExpiresAt === 'string' && Number.isFinite(Date.parse(body.retentionExpiresAt)) ? body.retentionExpiresAt : null;\n  const common = { jobStatus: body.status, retentionExpiresAt: expires, resume: { action: 'resume', externalCaseId: state.externalCaseId, jobId: body.id }, statusRequest: { action: 'status', externalCaseId: state.externalCaseId, jobId: body.id } };\n  if (body.status === 'completed') {\n    const comparison = body.result?.comparison;\n    if (!object(comparison) || comparison.schemaVersion !== 1 || !Array.isArray(comparison.findings) || !object(comparison.counts)) return stop('invalid_api_response', 502, { code: 'missing_comparison', message: 'The saved job did not contain a supported Cross-check result.' });\n    state.done = true;\n    state.replyReady = !state.responded;\n    state.responseCode = 200;\n    state.deliver = state.action !== 'status';\n    state.output = result('completed', { ...common, comparison, reportUrl: `https://deepfakepolicy.com/v1/jobs/${body.id}/report?format=json`, reportRequiresAuthentication: true });\n    return state;\n  }\n  if (body.status === 'failed') return stop('failed', 200, { code: typeof body.error?.code === 'string' && /^[a-z0-9_]{1,80}$/.test(body.error.code) ? body.error.code : 'processing_failed', message: 'Processing failed. The existing job was not resubmitted.' }, common);\n  if (body.status === 'deleted' || body.status === 'deletion_pending') return stop('unavailable', 410, { code: 'report_unavailable', message: 'This job is deleted or pending deletion. It will not be recreated.' }, common);\n  if (body.status === 'awaiting_upload') {\n    // A concurrent submission may have been accepted while its source was\n    // uploading, then reset after an interrupted storage write. GET cannot\n    // repair missing bytes. Do not poll forever or suggest a fresh paid job.\n    const uploadCommon = { jobStatus: common.jobStatus, retentionExpiresAt: common.retentionExpiresAt, statusRequest: common.statusRequest };\n    return stop('upload_required', 409, { code: 'source_upload_required', message: 'The original source upload is incomplete. Retry action=submit with the original submissionId, identical files and parameters, and the same company API key. Status or resume cannot upload missing files. Do not create a new submissionId.' }, uploadCommon);\n  }\n  if (!['queued', 'processing', 'uploading'].includes(body.status)) return stop('invalid_api_response', 502, { code: 'unknown_job_status', message: 'The API returned an unsupported job state.' }, common);\n  state.replyReady = !state.responded;\n  state.responseCode = 202;\n  state.output = result(body.status, common);\n  if (state.action === 'status') { state.done = true; return state; }\n  if (state.pollCount >= 60 || now >= state.deadline) return stop('polling_timeout', 202, { code: 'polling_timeout', message: 'The polling window ended. Use resume with this jobId; a timeout does not mean processing failed.' }, common);\n  state.waitSeconds = 10;\n  return state;\n}\nreturn [{ json: handleResponse($('Request state').item.json, $input.first().json), pairedItem: { item: 0 } }];"
      },
      "id": "dfp-recipe-8",
      "name": "Evaluate API response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        -120
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "reply-to-caller-",
              "leftValue": "={{ !$json.responded && $json.replyReady }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "dfp-recipe-9",
      "name": "Reply to caller?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1680,
        80
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json.output }}",
        "options": {
          "responseCode": "={{ $json.responseCode }}"
        }
      },
      "id": "dfp-recipe-10",
      "name": "Webhook response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.4,
      "position": [
        1920,
        -60
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{ json: { ...$input.first().json, responded: true }, pairedItem: { item: 0 } }];"
      },
      "id": "dfp-recipe-11",
      "name": "Remember response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2160,
        -60
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "continue-polling-",
              "leftValue": "={{ !$json.done }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "dfp-recipe-12",
      "name": "Continue polling?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2400,
        80
      ]
    },
    {
      "parameters": {
        "resume": "timeInterval",
        "amount": "={{ $json.waitSeconds }}",
        "unit": "seconds"
      },
      "id": "dfp-recipe-13",
      "name": "Wait before retry",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        2640,
        -120
      ],
      "webhookId": "75b98c42-81a6-49fa-ab86-70cf26fc8f3e"
    },
    {
      "parameters": {
        "jsCode": "const state = $input.first().json; return [{ json: { ...state.output, deliver: state.deliver }, pairedItem: { item: 0 } }];"
      },
      "id": "dfp-recipe-14",
      "name": "Result for your system",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2640,
        240
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "deliver-result-",
              "leftValue": "={{ $json.deliver === true }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "dfp-recipe-15",
      "name": "Deliver result?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2880,
        240
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://your-company.example/integrations/deepfakepolicy/result",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "={{ $json.deliveryKey || ('dfp-submission:' + ($json.submissionId || 'invalid')) }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json }}",
        "options": {
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          },
          "timeout": 15000
        }
      },
      "id": "dfp-recipe-16",
      "name": "Write result to your system - configure then enable",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3120,
        240
      ],
      "disabled": true,
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "notesInFlow": true,
      "notes": "Optional, disabled. Configure a fixed HTTPS endpoint and its own credential, or replace with your CRM node. Upsert by externalCaseId and deduplicate by deliveryKey. Never auto-approve a case. Failure here must be retried with resume (GET only), not a new submission."
    },
    {
      "parameters": {
        "content": "## Cross-check → your case system\nImport inactive. Configure three separate credentials: inbound webhook; DeepfakePolicy API (both request nodes); your optional result destination.\n\nSend action=submit with externalCaseId, unique stable submissionId and comparison files. Completed live checks are billable; sandbox does not evaluate comparisons.\n\n202 returns a jobId: save it with the external case. action=status reads without delivery. action=resume retrieves the same job and delivers again; never starts analysis.\n\nBounded to 60 status reads / 15 minutes, up to 3 attempts per request. A timeout does not cancel the job. Results expire 7 days after submission.\n\nThe result destination is disabled until you configure it. No CRM is connected automatically. Full guide: https://deepfakepolicy.com/api-examples/n8n-cross-check-guide.md",
        "height": 540,
        "width": 620
      },
      "id": "dfp-recipe-17",
      "name": "Read before activating",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -660
      ]
    }
  ],
  "connections": {
    "Incoming case": {
      "main": [
        [
          {
            "node": "Validate case",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate case": {
      "main": [
        [
          {
            "node": "Valid request?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid request?": {
      "main": [
        [
          {
            "node": "Request state",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reply to caller?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request state": {
      "main": [
        [
          {
            "node": "Submit new analysis?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submit new analysis?": {
      "main": [
        [
          {
            "node": "Submit Cross-check",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Read existing job",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submit Cross-check": {
      "main": [
        [
          {
            "node": "Evaluate API response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read existing job": {
      "main": [
        [
          {
            "node": "Evaluate API response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate API response": {
      "main": [
        [
          {
            "node": "Reply to caller?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reply to caller?": {
      "main": [
        [
          {
            "node": "Webhook response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Continue polling?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook response": {
      "main": [
        [
          {
            "node": "Remember response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remember response": {
      "main": [
        [
          {
            "node": "Continue polling?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Continue polling?": {
      "main": [
        [
          {
            "node": "Wait before retry",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Result for your system",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait before retry": {
      "main": [
        [
          {
            "node": "Request state",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Result for your system": {
      "main": [
        [
          {
            "node": "Deliver result?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deliver result?": {
      "main": [
        [
          {
            "node": "Write result to your system - configure then enable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "executionTimeout": 1200,
    "saveDataSuccessExecution": "none",
    "saveDataErrorExecution": "none",
    "saveManualExecutions": false,
    "saveExecutionProgress": false
  },
  "pinData": {},
  "tags": []
}
