Getting Started with NLDS API

Welcome to the Natural Language Document Search (NLDS) API. This guide walks you through integrating NLDS into your system step by step, explaining every concept before diving into the reference documentation.

Table of Contents


1. Overview

NLDS is a platform that ingests mortgage documents (PDFs) from your system, validates and indexes them using OCR and AI extraction, and lets you query them using a structured document checklist.

What you can do:

Two integration flows:

  1. Ingest flow: You upload documents via the case upload API; NLDS validates and indexes them asynchronously
  2. Query flow: You submit natural language queries against a case's indexed documents; NLDS searches and returns results asynchronously

Async design: Upload validation and ingestion run asynchronously in the backend. Your API calls return quickly; poll the case and document status endpoints to track progress.


2. Authentication

NLDS uses OAuth 2.0 machine-to-machine (M2M) authentication. Your integration system acts as a confidential OAuth client, obtaining a token from the AuthX service and passing it in the Authorization header for all API calls.

Token Request

Request a token from AuthX using the client_credentials grant type.

Endpoint: POST https://id.digilytics.solutions/authx/oauth2/token

Request (form-encoded):

grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=nldocsearch.api

cURL example:

curl -X POST https://id.digilytics.solutions/authx/oauth2/token \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "scope=nldocsearch.api"

Token Response (JSON):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 1800
}
Field Meaning
access_token JWT token to use in all API calls
token_type Always Bearer
expires_in Token lifetime in seconds (1800 = 30 minutes)

Using the Token

Include the token in the Authorization header of every API call:

Authorization: Bearer {access_token}

Example:

curl -X GET https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/products \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Token Lifecycle

Security Notes


3. Company ID and Pagination

Your company is identified by a companyId - a long integer provisioned during onboarding.

Example: companyId = 123456

Every API path includes your companyId as a required segment:

/api/v1/{companyId}/products
/api/v1/{companyId}/cases

The API validates that the companyId in the path matches the companyId claim in your JWT token. A mismatched or missing companyId returns 403 Forbidden.

Pagination for List APIs

The product list, case list/search, and case document list APIs are paginated.

Use these optional query parameters:

Query Parameter Default Validation Meaning
page 0 Must be 0 or greater Zero-based page number
size 50 Must be greater than 0 and at most 100 Number of records to return
sort Endpoint-specific Format: field,asc or field,desc; only one sort field is supported Sort order

Each paginated response returns records under data.content[] and page details under data.

{
  "meta": {
    "code": 200,
    "type": "success",
    "message": "success"
  },
  "data": {
    "content": [
      {
        "...": "..."
      }
    ],
    "page": 0,
    "size": 50,
    "totalElements": 123,
    "totalPages": 3,
    "first": true,
    "last": false
  }
}

Use camelCase sort field names, such as createdAt and lastModifiedAt. Do not send snake_case database column names such as created_at or last_modified_at.

Invalid pagination or sort input returns 400 Bad Request.

4. Products

List Available Products

Endpoint: GET /api/v1/{companyId}/products

This endpoint returns active products available to the company. The response is paginated, and product records are returned in data.content[].

Query parameters:

Query Parameter Required Default Validation Meaning
page No 0 Must be 0 or greater Zero-based page number
size No 50 Must be greater than 0 and at most 100 Number of records to return
sort No productCode,asc Format: field,asc or field,desc; only one sort field is supported Sort order

Allowed sort fields:

Example requests:

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/products" \
  -H "Authorization: Bearer {access_token}"

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/products?page=0&size=50&sort=productCode,asc" \
  -H "Authorization: Bearer {access_token}"

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/products?page=1&size=25&sort=displayName,asc" \
  -H "Authorization: Bearer {access_token}"

Response (200 OK):

{
  "meta": {
    "code": 200,
    "message": "success",
    "type": "success"
  },
  "data": {
    "content": [
      {
        "productCode": "HELOC",
        "displayName": "Home Equity Line of Credit",
        "status": "ACTIVE",
        "createdAt": "2024-06-01T00:00:00Z",
        "lastModifiedAt": "2024-06-01T00:00:00Z"
      },
      {
        "productCode": "MORTGAGE",
        "displayName": "Residential Mortgage",
        "status": "ACTIVE",
        "createdAt": "2024-06-01T00:00:00Z",
        "lastModifiedAt": "2024-06-01T00:00:00Z"
      }
    ],
    "page": 0,
    "size": 50,
    "totalElements": 2,
    "totalPages": 1,
    "first": true,
    "last": true
  }
}
Field Meaning
data.content[].productCode Stable string identifier; use this in case creation
data.content[].displayName Human-readable product name
data.content[].status Product availability status
data.content[].createdAt Product creation timestamp
data.content[].lastModifiedAt Product last modification timestamp
data.page Zero-based current page number
data.size Requested page size
data.totalElements Total number of matching products
data.totalPages Total number of pages
data.first true when this is the first page
data.last true when this is the last page

5. Cases

List Cases

Endpoint: GET /api/v1/{companyId}/cases

This endpoint lists or searches cases visible to the company. The response is paginated, and case records are returned in data.content[].

Query parameters:

Query Parameter Required Default Validation Meaning
productCode No None String Optional product filter
q No None String Optional search term. When supplied, NLDS searches case display name, externalCaseId, and casePublicId
page No 0 Must be 0 or greater Zero-based page number
size No 50 Must be greater than 0 and at most 100 Number of records to return
sort No lastModifiedAt,desc Format: field,asc or field,desc; only one sort field is supported Sort order

Allowed sort fields:

Example requests:

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases" \
  -H "Authorization: Bearer {access_token}"

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases?page=0&size=50&sort=lastModifiedAt,desc" \
  -H "Authorization: Bearer {access_token}"

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases?productCode=mortgage-premium&q=smith&page=1&size=25&sort=createdAt,asc" \
  -H "Authorization: Bearer {access_token}"

Response (200 OK):

{
  "meta": {
    "code": 200,
    "message": "success",
    "type": "success"
  },
  "data": {
    "content": [
      {
        "casePublicId": "8d5a3e7c-9f21-4a2c-b8e1-d7c6f5a4e3b2",
        "productCode": "MORTGAGE",
        "externalCaseId": "LOS-998877",
        "displayName": "Smith Mortgage - LOS-998877",
        "status": "ACTIVE",
        "createdAt": "2024-12-15T14:32:00Z",
        "lastModifiedAt": "2024-12-15T14:32:00Z",
        "documentSummary": {
          "total": 3,
          "uploadPending": 0,
          "uploaded": 1,
          "validationPending": 0,
          "validationPassed": 3,
          "validationFailed": 0,
          "ingestionQueued": 2,
          "indexed": 1,
          "ingestionFailed": 0
        }
      }
    ],
    "page": 0,
    "size": 50,
    "totalElements": 1,
    "totalPages": 1,
    "first": true,
    "last": true
  }
}
Field Meaning
data.content[].casePublicId Stable NLDS case identifier used in subsequent case and document APIs
data.content[].productCode Product code for the case
data.content[].externalCaseId Your upstream case identifier, when supplied
data.content[].displayName Human-readable case name
data.content[].status Case lifecycle status: DRAFT, ACTIVE, ARCHIVED, or CLOSED
data.content[].documentSummary Status counts for all documents in the case
data.page Zero-based current page number
data.size Requested page size
data.totalElements Total number of matching cases
data.totalPages Total number of pages
data.first true when this is the first page
data.last true when this is the last page

Create a Case

Endpoint: POST /api/v1/{companyId}/cases

Request:

curl -X POST "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "productCode": "MORTGAGE",
    "externalCaseId": "LOS-998877",
    "displayName": "Smith Mortgage - LOS-998877"
  }'

Request Body:

{
  "productCode": "MORTGAGE",
  "externalCaseId": "LOS-998877",
  "displayName": "Smith Mortgage - LOS-998877"
}
Field Required Meaning
productCode Yes Code from the products list (e.g., "MORTGAGE")
externalCaseId No Your upstream system's case identifier (e.g., loan origination system ID). When supplied, case creation is idempotent: reusing the same externalCaseId returns the existing case instead of creating a duplicate. Blank/whitespace externalCaseId values are treated as absent. Concurrent creation request races are handled by re-fetching the existing case. Recommended for idempotency.
displayName No Human-readable case name for display and traceability

Response (201 Created):

{
  "meta": {
    "code": 201,
    "message": "success",
    "type": "success"
  },
  "data": {
    "casePublicId": "8d5a3e7c-9f21-4a2c-b8e1-d7c6f5a4e3b2",
    "productCode": "MORTGAGE",
    "externalCaseId": "LOS-998877",
    "displayName": "Smith Mortgage - LOS-998877",
    "status": "ACTIVE",
    "createdAt": "2024-12-15T14:32:00Z",
    "documentSummary": {
      "total": 0,
      "uploadPending": 0,
      "uploaded": 0,
      "validationPending": 0,
      "validationPassed": 0,
      "validationFailed": 0,
      "ingestionQueued": 0,
      "indexed": 0,
      "ingestionFailed": 0
    }
  }
}
Field Meaning
casePublicId Your stable reference for this case in subsequent upload calls
status Case lifecycle status: DRAFT, ACTIVE, ARCHIVED, or CLOSED
documentSummary Status counts for all documents in the case

Get Case Details

Endpoint: GET /api/v1/{companyId}/cases/{casePublicId}

Request:

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases/8d5a3e7c-9f21-4a2c-b8e1-d7c6f5a4e3b2" \
  -H "Authorization: Bearer {access_token}"

Response (200 OK): Same shape as case creation response, with current document status counts.


6. Documents

This section covers client-facing document APIs for lookup, listing, registration, upload completion, and status retrieval.

Lookup Documents

What is a document ID?

externalDocumentId is your system's identifier for a source document (e.g., "DOC-CD-2024-001"). When supplied, NLDS uses it to identify the document within the same company, product, and case.

documentPublicId is the NLDS document identifier returned during registration. Store this value and use it for lookup, retry, or re-register flows when externalDocumentId is not available.

Endpoint: POST /api/v1/{companyId}/cases/{casePublicId}/documents:lookup

Request:

Lookup by externalDocumentIds:

curl -X POST "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases/ab3cb669-3df7-4c8a-beaa-a7ccafd76e90/documents:lookup" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "externalDocumentIds": ["DOC-001", "DOC-002"]
  }'

Lookup by documentPublicIds:

{
  "documentPublicIds": [
    "660e8400-e29b-41d4-a716-446655440001",
    "660e8400-e29b-41d4-a716-446655440002"
  ]
}

Lookup with both identifier lists:

{
  "externalDocumentIds": ["DOC-001"],
  "documentPublicIds": ["660e8400-e29b-41d4-a716-446655440001"]
}
Field Required Meaning
externalDocumentIds Conditional List of external document IDs to look up within the case
documentPublicIds Conditional List of NLDS document UUIDs to look up within the case

At least one identifier list must be supplied. Empty request bodies and empty identifier lists are not allowed. Use GET /api/v1/{companyId}/cases/{casePublicId}/documents to list documents in a case.

Validation rules:

Response (200 OK):

{
  "meta": {
    "code": 200,
    "message": "success",
    "type": "success"
  },
  "data": [
    {
      "externalDocumentId": "DOC-001",
      "found": true,
      "documentPublicId": "660e8400-e29b-41d4-a716-446655440001",
      "casePublicId": "ab3cb669-3df7-4c8a-beaa-a7ccafd76e90",
      "originalFilename": "Closing_Disclosure_signed.pdf",
      "contentSize": 1024576,
      "contentHash": "sha256:9f1c2a4b...",
      "uploadStatus": "UPLOADED",
      "validationStatus": "PASSED",
      "ingestionStatus": "INDEXED",
      "lastModifiedAt": "2026-05-28T10:00:00Z"
    },
    {
      "externalDocumentId": "DOC-002",
      "found": false,
      "documentPublicId": null,
      "casePublicId": null,
      "originalFilename": null,
      "contentSize": null,
      "contentHash": null,
      "uploadStatus": null,
      "validationStatus": null,
      "ingestionStatus": null,
      "lastModifiedAt": null
    }
  ]
}

The API returns one lookup result per requested value. Existing documents return found=true with document details. Missing values return found=false. Results are scoped to the case in the path and do not return matches from another case.

List Case Documents

Endpoint: GET /api/v1/{companyId}/cases/{casePublicId}/documents

This endpoint returns documents belonging to the specified case. The response is paginated, and document records are returned in data.content[].

Query parameters:

Query Parameter Required Default Validation Meaning
page No 0 Must be 0 or greater Zero-based page number
size No 50 Must be greater than 0 and at most 100 Number of records to return
sort No createdAt,asc Format: field,asc or field,desc; only one sort field is supported Sort order

Allowed sort fields:

Example requests:

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases/ab3cb669-3df7-4c8a-beaa-a7ccafd76e90/documents" \
  -H "Authorization: Bearer {access_token}"

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases/ab3cb669-3df7-4c8a-beaa-a7ccafd76e90/documents?page=0&size=50&sort=createdAt,asc" \
  -H "Authorization: Bearer {access_token}"

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases/ab3cb669-3df7-4c8a-beaa-a7ccafd76e90/documents?page=2&size=20&sort=originalFilename,desc" \
  -H "Authorization: Bearer {access_token}"

Response (200 OK):

{
  "meta": {
    "code": 200,
    "message": "success",
    "type": "success"
  },
  "data": {
    "content": [
      {
        "documentPublicId": "660e8400-e29b-41d4-a716-446655440001",
        "casePublicId": "ab3cb669-3df7-4c8a-beaa-a7ccafd76e90",
        "originalFilename": "Closing_Disclosure_signed.pdf",
        "contentType": "application/pdf",
        "contentSize": 1024576,
        "uploadStatus": "UPLOADED",
        "validationStatus": "PASSED",
        "ingestionStatus": "INDEXED",
        "diagnostics": [],
        "createdAt": "2026-05-28T10:00:00Z",
        "lastModifiedAt": "2026-05-28T10:00:00Z"
      }
    ],
    "page": 0,
    "size": 50,
    "totalElements": 1,
    "totalPages": 1,
    "first": true,
    "last": true
  }
}
Field Meaning
data.content[].documentPublicId NLDS document identifier
data.content[].casePublicId Case where the document is registered
data.content[].originalFilename Original file name
data.content[].contentType File content type
data.content[].contentSize File size in bytes, present after upload completion
data.content[].uploadStatus Current upload lifecycle status
data.content[].validationStatus Current validation status
data.content[].ingestionStatus Current ingestion status
data.content[].diagnostics Validation diagnostic details when validation produced warnings or errors
data.content[].createdAt Document creation timestamp
data.content[].lastModifiedAt Document last modification timestamp
data.page Zero-based current page number
data.size Requested page size
data.totalElements Total number of matching documents in the case
data.totalPages Total number of pages
data.first true when this is the first page
data.last true when this is the last page

Batch Register Documents

Endpoint: POST /api/v1/{companyId}/cases/{casePublicId}/documents:register

Request:

curl -X POST "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases/8d5a3e7c-9f21-4a2c-b8e1-d7c6f5a4e3b2/documents:register" \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "documents": [
      {
        "originalFilename": "Closing_Disclosure_signed.pdf",
        "documentPublicId": "660e8400-e29b-41d4-a716-446655440001",
        "externalDocumentId": "DOC-CD-2024-001",
        "contentLength": 1024576,
        "pageCount": 48,
        "declaredContentType": "application/pdf",
        "sourceSystemPath": "closing-package/CD_signed_final.pdf"
      }
    ]
  }'

Request Body:

{
  "documents": [
    {
      "originalFilename": "Closing_Disclosure_signed.pdf",
      "documentPublicId": "optional UUID, used for retry/re-register when externalDocumentId is not available",
      "externalDocumentId": "optional case-scoped external id",
      "contentLength": 1024576,
      "pageCount": 48,
      "declaredContentType": "application/pdf",
      "sourceSystemPath": "closing-package/CD_signed_final.pdf"
    }
  ]
}
Field Meaning
originalFilename Required original file name, 1-512 characters
documentPublicId Optional NLDS document UUID for retry or re-register flows when externalDocumentId is not available
externalDocumentId Optional case-scoped external document ID, 1-256 characters if supplied
contentLength Optional file size in bytes. When supplied, NLDS validates it against the configured size limit
pageCount Optional PDF page count. Minimum 1 when supplied
declaredContentType Optional MIME type, max 128 characters
sourceSystemPath Optional human-readable path in your system, max 1024 characters

Validation rules:

The API always returns documentPublicId for successful registrations. Store this value. If externalDocumentId is not available, use documentPublicId later to look up the document, re-register or retry the document, and request a fresh SAS URL after failed upload or failed validation.

Response (201 Created - full success):

{
  "meta": {
    "code": 201,
    "type": "success",
    "message": "Documents registered successfully"
  },
  "data": [
    {
      "documentPublicId": "660e8400-e29b-41d4-a716-446655440001",
      "originalFilename": "Closing_Disclosure_signed.pdf",
      "externalDocumentId": "DOC-CD-2024-001",
      "sourceSystemPath": "closing-package/CD_signed_final.pdf",
      "uploadStatus": "UPLOAD_PENDING",
      "validationStatus": "PENDING",
      "ingestionStatus": "PENDING",
      "createdAt": "2026-05-28T10:00:00Z",
      "lastModifiedAt": "2026-05-28T10:00:00Z",
      "sasUploadUrl": "https://...sas...",
      "uploadMethod": "PUT",
      "sasExpiresAt": "2026-05-28T10:15:00Z",
      "existingDocument": false
    }
  ]
}

Response (207 Multi-Status - partial success):

{
  "meta": {
    "code": 207,
    "type": "partial",
    "message": "Some documents registered successfully; some failed"
  },
  "data": {
    "overallStatus": "PARTIAL",
    "successfulDocuments": [
      {
        "documentPublicId": "660e8400-e29b-41d4-a716-446655440001",
        "originalFilename": "Closing_Disclosure_signed.pdf",
        "externalDocumentId": "DOC-CD-2024-001",
        "sourceSystemPath": "closing-package/CD_signed_final.pdf",
        "uploadStatus": "UPLOAD_PENDING",
        "validationStatus": "PENDING",
        "ingestionStatus": "PENDING",
        "createdAt": "2026-05-28T10:00:00Z",
        "lastModifiedAt": "2026-05-28T10:00:00Z",
        "sasUploadUrl": "https://...sas...",
        "uploadMethod": "PUT",
        "sasExpiresAt": "2026-05-28T10:15:00Z",
        "existingDocument": false
      }
    ],
    "failedDocuments": [
      {
        "externalDocumentId": "DOC-BAD-001",
        "documentPublicId": null,
        "errorCode": "PDF_PAGE_COUNT_EXCEEDED",
        "errorMessage": "Document pageCount 1001 exceeds maximum allowed 1000 pages"
      }
    ]
  }
}

Response (400 Bad Request - all failed):

{
  "meta": {
    "code": 400,
    "type": "error",
    "message": "Documents registration failed"
  },
  "data": {
    "overallStatus": "FAILED",
    "successfulDocuments": [],
    "failedDocuments": [
      {
        "externalDocumentId": "DOC-BAD-001",
        "documentPublicId": null,
        "errorCode": "PDF_PAGE_COUNT_EXCEEDED",
        "errorMessage": "Document pageCount 1001 exceeds maximum allowed 1000 pages"
      }
    ]
  }
}

Complete Upload

Endpoint: POST /api/v1/{companyId}/cases/{casePublicId}/documents/{documentPublicId}:complete-upload

This endpoint does not accept a request body.

Request:

curl -X POST "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases/8d5a3e7c-9f21-4a2c-b8e1-d7c6f5a4e3b2/documents/660e8400-e29b-41d4-a716-446655440001:complete-upload" \
  -H "Authorization: Bearer {access_token}"

Response (202 Accepted):

{
  "meta": {
    "code": 202,
    "type": "success",
    "message": "Upload completion accepted"
  },
  "data": {
    "documentPublicId": "660e8400-e29b-41d4-a716-446655440001",
    "uploadStatus": "UPLOADED",
    "validationStatus": "ENQUEUED",
    "ingestionStatus": "PENDING",
    "detectedType": null,
    "contentSize": 1024576,
    "contentHash": null,
    "diagnostics": [],
    "processedAt": "2026-05-28T10:00:00Z"
  }
}

Get Document Status

Endpoint: GET /api/v1/{companyId}/cases/{casePublicId}/documents/{documentPublicId}

Request:

curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/cases/8d5a3e7c-9f21-4a2c-b8e1-d7c6f5a4e3b2/documents/660e8400-e29b-41d4-a716-446655440001" \
  -H "Authorization: Bearer {access_token}"

Response (200 OK):

{
  "meta": {
    "code": 200,
    "message": "Document status retrieved",
    "type": "success"
  },
  "data": {
    "documentPublicId": "660e8400-e29b-41d4-a716-446655440001",
    "casePublicId": "8d5a3e7c-9f21-4a2c-b8e1-d7c6f5a4e3b2",
    "originalFilename": "Closing_Disclosure_signed.pdf",
    "contentType": "application/pdf",
    "contentSize": 1024576,
    "uploadStatus": "UPLOADED",
    "validationStatus": "PASSED",
    "ingestionStatus": "QUEUED",
    "diagnostics": [],
    "createdAt": "2026-05-28T10:00:00Z",
    "lastModifiedAt": "2026-05-28T10:00:00Z"
  }
}

Validation failed example:

{
  "meta": {
    "code": 200,
    "message": "Document status retrieved",
    "type": "success"
  },
  "data": {
    "documentPublicId": "660e8400-e29b-41d4-a716-446655440001",
    "casePublicId": "8d5a3e7c-9f21-4a2c-b8e1-d7c6f5a4e3b2",
    "originalFilename": "not-a-pdf.txt",
    "contentType": "application/pdf",
    "contentSize": null,
    "uploadStatus": "UPLOADED",
    "validationStatus": "FAILED",
    "ingestionStatus": "NOT_APPLICABLE",
    "diagnostics": [
      {
        "code": "FILE_TOO_LARGE",
        "severity": "ERROR",
        "message": "File exceeds maximum allowed size of 200 MB"
      }
    ],
    "createdAt": "2026-05-28T10:00:00Z",
    "lastModifiedAt": "2026-05-28T10:00:00Z"
  }
}
Status Field Values
uploadStatus REGISTERED, UPLOAD_PENDING, UPLOADED, UPLOAD_FAILED
validationStatus PENDING, ENQUEUED, PROCESSING, PASSED, FAILED, NOT_RUN
ingestionStatus PENDING, QUEUED, PROCESSING, INDEXED, FAILED, NOT_APPLICABLE

7. Webhooks

NLDS webhooks are asynchronous notifications sent to a client-configured endpoint when selected events occur. NLDS sends webhook deliveries as HTTP POST requests with a JSON payload.

Use the webhook payload to identify the event, then call NLDS APIs if your system needs full details or final results. Webhooks may be retried, so clients must handle duplicate deliveries safely.

Supported Webhook Event Types

The table below lists the webhook events. Query completion uses a single event type; clients should read data.status to determine the outcome.

Event Type Feature Scope Version Meaning
query.completed NLDS_QUERY_COMPLETION 1.0 Query processing finished with a terminal outcome
case.ready NLDS_CASE_READINESS 1.0 Case is ready for downstream/query use

For query.completed, the outcome is returned in data.status.

Status Meaning
SUCCESS Query finished successfully
PARTIAL Query finished with partial results
FAILED Query failed
CANCELLED Query was cancelled

Webhook HTTP Headers

NLDS sends the following headers with webhook deliveries. Clients should use these headers for routing, tracing, version handling, and deduplication.

Header Required Description
Content-Type Yes Always application/json
User-Agent Yes Identifies the sender as nldocsearch-webhooks/1.0
X-NLDS-Event-ID Yes Stable unique business event identifier. Same value as payload eventId
X-NLDS-Event-Type Yes Event type, for example query.completed or case.ready
X-NLDS-Event-Version Yes Event contract version. Current documented value is 1.0
X-NLDS-Subscription-ID Yes Identifier of the webhook subscription configuration that matched and triggered this event. Use this if your system registers multiple subscriptions and needs to route incoming deliveries by subscription
X-NLDS-Delivery-ID Yes Identifier for a delivery instance. Automatic retries reuse the same X-NLDS-Delivery-ID. A manual replay creates a new X-NLDS-Delivery-ID. Use this for tracing a delivery, not for deduplication
X-NLDS-Idempotency-Key Yes Stable key clients should use to deduplicate automatic retries for the same delivery
X-NLDS-Correlation-ID No Trace/correlation id when available. This value is copied from the payload correlationId when present
Authorization No Present only if the configured webhook auth profile requires it
X-NLDS-Signature No Present only when HMAC signing is enabled

Identifier behavior:

Identifier Behavior Client Use
eventId / X-NLDS-Event-ID Stable identifier for the business event Identify the event
X-NLDS-Idempotency-Key Stable across automatic retries for the same delivery. Manual replay may generate a replay-specific idempotency key Deduplicate processing
X-NLDS-Delivery-ID Stable across automatic retries for the same delivery. Manual replay generates a new delivery id Trace a delivery instance
X-NLDS-Subscription-ID Identifies the subscription configuration that matched the event Route deliveries when you have multiple subscriptions
X-NLDS-Correlation-ID Optional trace id copied from correlationId when available Support and troubleshooting

Do not use X-NLDS-Delivery-ID for deduplication. Use X-NLDS-Idempotency-Key instead.

Common Webhook Payload Envelope

All webhook event payloads use the same top-level envelope. Event-specific details are returned under data.

{
  "eventId": "bc1a4d7f-f162-4c7b-a77d-9a4c39e7a111",
  "eventType": "query.completed",
  "eventVersion": 1.0,
  "occurredAt": "2026-07-24T09:00:00Z",
  "companyId": 101,
  "productCode": "MORTGAGE",
  "featureScope": "NLDS_QUERY_COMPLETION",
  "idempotencyKey": "whk_idem_8f4b3c92a7d64e0b9c1a2f33",
  "correlationId": "trace-query-001",
  "data": {}
}
Field Required Description
eventId Yes Stable unique webhook event id
eventType Yes Event name
eventVersion Yes Event contract version. Current documented value is 1.0
occurredAt Yes UTC timestamp when the event occurred
companyId Yes NLDS company id
productCode Conditional NLDS product code when the event is associated with a product
featureScope Yes Functional area for the event
idempotencyKey Yes Stable key for duplicate/retry handling. Same value as X-NLDS-Idempotency-Key
correlationId No Trace id when available
data Yes Event-specific details

Clients should ignore unknown fields so that new optional fields can be added without breaking existing integrations.

Example Payloads

query.completed with SUCCESS:

{
  "eventId": "bc1a4d7f-f162-4c7b-a77d-9a4c39e7a111",
  "eventType": "query.completed",
  "eventVersion": 1.0,
  "occurredAt": "2026-07-24T09:00:00Z",
  "companyId": 101,
  "productCode": "MORTGAGE",
  "featureScope": "NLDS_QUERY_COMPLETION",
  "idempotencyKey": "whk_idem_8f4b3c92a7d64e0b9c1a2f33",
  "correlationId": "trace-query-001",
  "data": {
    "queryJobId": "b4c7d9e2-f5a8-4c1d-9e3f-5a2b8c4d7e6f",
    "casePublicId": "cf3e7ee4-c359-4d72-af81-b88854c5733f",
    "externalCaseId": "LOS-998877",
    "status": "SUCCESS",
    "completedAt": "2026-07-24T09:00:00Z"
  }
}

query.completed with PARTIAL:

{
  "eventId": "d5bd5b25-933c-47d6-9cf9-04cf13468732",
  "eventType": "query.completed",
  "eventVersion": 1.0,
  "occurredAt": "2026-07-24T09:05:00Z",
  "companyId": 101,
  "productCode": "MORTGAGE",
  "featureScope": "NLDS_QUERY_COMPLETION",
  "idempotencyKey": "whk_idem_4d2a9f0c6e814b73a58d1c20",
  "data": {
    "queryJobId": "b4c7d9e2-f5a8-4c1d-9e3f-5a2b8c4d7e6f",
    "casePublicId": "cf3e7ee4-c359-4d72-af81-b88854c5733f",
    "externalCaseId": "LOS-998877",
    "status": "PARTIAL",
    "completedAt": "2026-07-24T09:05:00Z"
  }
}

query.completed with FAILED:

{
  "eventId": "8bb421b8-0211-4bb0-a2be-845635104395",
  "eventType": "query.completed",
  "eventVersion": 1.0,
  "occurredAt": "2026-07-24T09:10:00Z",
  "companyId": 101,
  "productCode": "MORTGAGE",
  "featureScope": "NLDS_QUERY_COMPLETION",
  "idempotencyKey": "whk_idem_91c7e3b58a604f2d83a9e441",
  "correlationId": "trace-query-003",
  "data": {
    "queryJobId": "b4c7d9e2-f5a8-4c1d-9e3f-5a2b8c4d7e6f",
    "casePublicId": "cf3e7ee4-c359-4d72-af81-b88854c5733f",
    "externalCaseId": "LOS-998877",
    "status": "FAILED",
    "failedAt": "2026-07-24T09:10:00Z"
  }
}

case.ready:

{
  "eventId": "4dc7067e-4431-4ed3-b2ce-c6ac7f24d118",
  "eventType": "case.ready",
  "eventVersion": 1.0,
  "occurredAt": "2026-07-24T08:45:00Z",
  "companyId": 101,
  "productCode": "MORTGAGE",
  "featureScope": "NLDS_CASE_READINESS",
  "idempotencyKey": "whk_idem_c65e1b7429a94f8e9d30a627",
  "correlationId": "trace-case-001",
  "data": {
    "casePublicId": "cf3e7ee4-c359-4d72-af81-b88854c5733f",
    "externalCaseId": "LOS-998877",
    "caseStatus": "ACTIVE",
    "documentSummary": {
      "total": 2, 
      "indexed": 2,
      "failed": 0
    }
  }
}

Retry and Delivery Semantics

NLDS may retry webhook delivery if the client endpoint is temporarily unavailable, times out, or returns retryable errors.

Webhooks are delivered at least once. Consumers must handle duplicate deliveries using X-NLDS-Idempotency-Key. Any HTTP 2xx response is considered successful.

By default, NLDS attempts webhook delivery up to 4 total times: 1 initial attempt plus 3 retries. Retry delays increase using exponential backoff, up to a maximum delay of 5 minutes between attempts. Any HTTP 2xx response is considered successful and stops further retries. If all delivery attempts fail, NLDS stops retrying and marks the delivery as failed.

Deduplicate deliveries using X-NLDS-Idempotency-Key. If the same idempotency key is received more than once, process it once and safely ignore duplicates. Return a 2xx response only after your endpoint has safely accepted the event.

Automatic retries reuse the same X-NLDS-Delivery-ID and X-NLDS-Idempotency-Key. Manual replay creates a new delivery instance and may use a replay-specific idempotency key. Clients that need to detect that a replay belongs to the same business event should compare eventId / X-NLDS-Event-ID.

Default retry behavior:

Condition Retry Behavior
2xx response Delivery is acknowledged; no retry
408 Request Timeout Retried
429 Too Many Requests Retried. Retry-After is honored when present
500, 502, 503, 504, and other 5xx responses Retried with backoff
401 Unauthorized / 403 Forbidden Not retried, except during configured auth rotation grace windows
Other 4xx responses Treated as permanent rejection; no retry
Network, connection, or timeout failure Retried with backoff

Response Expectation

Return a 2xx status only after your endpoint has safely accepted the event. For example, store the event or enqueue it durably before responding if downstream processing is asynchronous.

Response Meaning NLDS Behavior
200 OK Event accepted No retry
204 No Content Event accepted with no response body No retry
400 Bad Request Malformed request or permanent rejection No retry
401 Unauthorized Authorization rejected No retry, except during configured auth rotation grace windows
403 Forbidden Authorization rejected No retry, except during configured auth rotation grace windows
408 Request Timeout Temporary timeout Retried
429 Too Many Requests Endpoint overloaded Retried; Retry-After is honored when present
500, 502, 503, 504 Temporary server failure Retried with backoff