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
- 2. Authentication
- 3. Company ID and Pagination
- 4. Products
- 5. Cases
- 6. Documents
- 7. Webhooks
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:
- Upload mortgage documents (closing disclosures, promissory notes, title policies, appraisals, and more) from your system
- Query indexed documents by submitting a structured
checkListof document types with optional attribute filters (borrower name, date range, signature status) - Retrieve search results with extracted metadata (borrower name, document date, and signature status)
Two integration flows:
- Ingest flow: You upload documents via the case upload API; NLDS validates and indexes them asynchronously
- 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
- Token lifetime: 30 minutes
- No refresh token is issued; when the token expires, request a new one using the same
client_credentialsflow - Always cache the token locally; reuse it for all requests until expiry
- Store
client_id,client_secret, and tokens in secure secret storage (e.g., environment variables, HashiCorp Vault, Azure Key Vault) — never store them in source code
Security Notes
- Only confidential systems should store
client_secret. Public/browser-based clients cannot use this flow - For token inspection and validation, you can verify the JWT signature using the AuthX JWKS endpoint (
/.well-known/jwks.json). See the AuthX developer guide for details - Tokens are bearer tokens; anyone with the token can make API calls on your behalf. Treat tokens as sensitive as passwords
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:
productCodedisplayNamecreatedAtlastModifiedAt
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:
lastModifiedAtcreatedAtdisplayNameexternalCaseIdproductCodestatus
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:
externalDocumentIdscan contain up to 200 entriesdocumentPublicIdscan contain up to 200 entriesexternalDocumentIdcan contain letters, digits, dot (.), underscore (_), hyphen (-), colon (:), and slash (/)documentPublicIdmust be a UUID- Duplicate
documentPublicIdsin the same lookup request are rejected
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:
createdAtlastModifiedAtoriginalFilenameuploadStatusvalidationStatusingestionStatus
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:
documentsis required and cannot be empty- Maximum 50 documents per request
- Only PDF upload extension is currently supported
- Duplicate
externalDocumentIdvalues inside the same register request are rejected per document - Duplicate
documentPublicIdvalues inside the same register request are rejected per document pageCount > 1000for a PDF fails per documentcontentLengthabove the configured limit fails per document
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 |