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
- 8. Query Flow
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 natural-language text, a structured document checklist, or both.
What you can do:
- Upload mortgage documents (closing disclosures, promissory notes, title policies, appraisals, and more) from your system
- Query indexed documents by submitting natural-language text, a structured
checkListof document types with optional attribute filters, or both - 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 text, a structured
checkList, or both 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; use 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 |
8. Query Flow
This flow searches indexed case documents asynchronously. Submit a query, check the returned jobId until the query reaches a terminal status, and then retrieve the query results.
All query API responses use the standard envelope:
{
"meta": {
"code": 200,
"type": "success",
"message": "success"
},
"data": {}
}
Client-facing query status values are:
| Status | Meaning |
|---|---|
ACCEPTED |
Query was accepted for processing |
PROCESSING |
Query is still in progress |
SUCCESS |
Query completed successfully |
PARTIAL |
Query completed with partial results |
FAILED |
Query failed |
CANCELLED |
Query was cancelled |
Submit Query
Endpoint: POST /api/v1/{companyId}/query
Submit natural-language text, a structured checkList, or both. At least one of query or checkList is required. When both are provided, the checkList defines the required document criteria, while the natural-language query is used to refine and rank matching results.
Send the optional idempotency key in the X-NLDS-Idempotency-Key header. Do not send it in the request body. If the same idempotency key is submitted again while the original query is still active, NLDS returns the existing jobId instead of creating a new query job.
Request:
curl -X POST "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/query" \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-H "X-NLDS-Idempotency-Key: query-20260819-183011" \
-d '{
"query": "latest signed Closing Disclosure for Mike",
"casePublicId": "8d5a3e7c-9f21-4a2c-b8e1-d7c6f5a4e3b2",
"checkList": [
{
"itemId": "REQ-CD-SIGNED",
"docType": "closing_disclosure",
"filters": {
"isSigned": true,
"applicantName": "Mike",
"docDateFrom": "2026-01-01",
"docDateTo": "2026-06-30"
}
}
]
}'
| Field | Meaning |
|---|---|
query |
Optional natural-language query text. Required only if checkList is not supplied. Used to express or refine what the client wants to find. |
casePublicId |
Public UUID of the case to search. This is the preferred client-facing case identifier. |
caseId |
Internal case id. Provide either caseId or casePublicId; client integrations should normally use casePublicId. |
checkList |
Optional structured list of requested document requirements. Required only if query is not supplied. |
checkList[].itemId |
Optional client-owned identifier for this checklist row. If omitted, NLDS assigns one like item-0. It is echoed back in results. |
checkList[].docType |
Requested document type. Can be a canonical document type or supported alias, such as closing_disclosure. |
checkList[].filters |
Optional filters applied to that checklist item. Filters inside one item are combined together. |
checkList[].filters.isSigned |
Filters by signature state. true means signed documents; false means unsigned documents. |
checkList[].filters.applicantName |
Filters by applicant/borrower name appearing on the document. |
checkList[].filters.docDateFrom |
Inclusive lower date bound for the document date, in YYYY-MM-DD format. |
checkList[].filters.docDateTo |
Inclusive upper date bound for the document date, in YYYY-MM-DD format. |
conversationId |
Optional correlation identifier. If omitted, NLDS creates one and returns it. |
Response (202 Accepted):
{
"meta": {
"code": 202,
"type": "success",
"message": "accepted"
},
"data": {
"jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"conversationId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "ACCEPTED",
"idempotencyKey": "query-20260819-183011",
"sandbox": false
}
}
Clients should retain the returned jobId for subsequent status and result requests.
| Field | Meaning |
|---|---|
data.jobId |
Stable query job identifier used for status and result requests |
data.conversationId |
Correlation identifier for tracing the query across related systems, when provided |
data.status |
Current client-facing query status |
data.idempotencyKey |
Echoed only when an idempotency key was supplied in the X-NLDS-Idempotency-Key header |
data.sandbox |
true when the query ran in sandbox mode |
Check Query Status
Endpoint: GET /api/v1/{companyId}/query/{jobId}/status
Call this endpoint using an appropriate backoff strategy until status reaches one of the terminal states: SUCCESS, PARTIAL, FAILED, or CANCELLED.
Request:
curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/query/3fa85f64-5717-4562-b3fc-2c963f66afa6/status" \
-H "Authorization: Bearer {access_token}"
Response (200 OK):
{
"meta": {
"code": 200,
"type": "success",
"message": "success"
},
"data": {
"jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"conversationId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "SUCCESS",
"sandbox": false
}
}
| Field | Meaning |
|---|---|
data.jobId |
Stable query job identifier for the status request |
data.conversationId |
Correlation identifier for tracing the query across related systems, when provided |
data.status |
Current client-facing query status |
data.sandbox |
true when the query ran in sandbox mode |
Get Query Results
Endpoint: GET /api/v1/{companyId}/query/{jobId}/results
Read the query answer after the job reaches a terminal status. If the query has not yet completed, the endpoint may return an empty result set rather than an error.
checkListSummary contains one result entry for each submitted checklist item, preserving the original submission order. results contains the documents that matched the submitted query criteria with the client's own externalDocumentId or sourceSystemPath when those were supplied during upload.
Results do not include storage URLs or SAS tokens. Use the document access endpoint for file access.
Request:
curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/query/3fa85f64-5717-4562-b3fc-2c963f66afa6/results" \
-H "Authorization: Bearer {access_token}"
Response (200 OK):
{
"meta": {
"code": 200,
"type": "success",
"message": "success"
},
"data": {
"jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"conversationId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "SUCCESS",
"checkListSummary": [
{
"itemId": "REQ-CD-SIGNED",
"submittedDocType": "closing_disclosure",
"docType": "closing_disclosure",
"status": "FOUND"
}
],
"pendingDocumentCount": 0,
"failedDocumentCount": 0,
"results": [
{
"docType": "closing_disclosure",
"pageRanges": [],
"caseDocumentId": "cd-4471",
"fileName": "closing_package.pdf",
"externalDocumentId": null,
"sourceSystemPath": null
}
],
"queryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"queriedAt": "2026-08-19T18:32:04Z",
"sandbox": false
}
}
| Field | Meaning |
|---|---|
data.jobId |
Stable query job identifier used for the result request |
data.conversationId |
Correlation identifier for tracing the query across related systems, when provided |
data.status |
Terminal or current client-facing query status for the result payload |
data.checkListSummary[] |
One result entry for each submitted checklist item |
data.checkListSummary[].itemId |
Client-supplied checklist item identifier |
data.checkListSummary[].submittedDocType |
Document type requested in the submitted checklist item |
data.checkListSummary[].docType |
Document type matched by NLDS |
data.checkListSummary[].status |
Checklist item status: FOUND, NOT_FOUND, or PENDING |
data.pendingDocumentCount |
Number of relevant in-scope documents still pending processing or review when the answer was produced |
data.failedDocumentCount |
Number of relevant in-scope documents that failed processing. Important when interpreting NOT_FOUND. |
data.results[] |
Matched document records. Does not include download URLs or SAS tokens. |
data.results[].docType |
Document type for the matched result |
data.results[].pageRanges |
Page ranges where evidence was found, when available |
data.results[].caseDocumentId |
Public uploaded-document identifier for the matched file |
data.results[].fileName |
Original uploaded file name |
data.results[].externalDocumentId |
Client-supplied document id from upload or registration, if supplied |
data.results[].sourceSystemPath |
Client-supplied source path from upload or registration, if supplied |
data.queryId |
Same logical query identifier as the job id, retained for compatibility and correlation |
data.queriedAt |
ISO-8601 timestamp when the answer was produced or recorded |
data.sandbox |
true when the query ran in sandbox mode |
Sandbox Quick Start
Sandbox mode lets you test the query workflow without uploading or indexing real documents. Use it to validate request structure, authentication, status, result parsing, query activity filtering, and webhook handling against a predictable response.
Sandbox behavior is controlled by the product's sandbox flag. When you create a case under a sandbox-enabled product and submit a query, NLDS accepts the request through the same query endpoint used in production, but returns a predefined terminal result instead of running the normal document search pipeline.
1. Create a case under the sandbox product
Create a normal case using the sandbox product code.
{
"productCode": "MORTGAGE_SANDBOX",
"externalCaseId": "SANDBOX-CASE-001",
"displayName": "Sandbox Query Test"
}
Store the returned casePublicId.
2. Submit a query
Use the same query endpoint documented above.
curl -X POST "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/query" \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"query": "latest signed Closing Disclosure",
"casePublicId": "{sandbox_case_public_id}",
"checkList": [
{
"itemId": "REQ-CD-SIGNED",
"docType": "closing_disclosure",
"filters": {
"isSigned": true
}
}
]
}'
3. Retrieve the sandbox query result
A sandbox query returns a terminal SUCCESS result immediately.
{
"meta": {
"code": 202,
"type": "success",
"message": "accepted"
},
"data": {
"jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"conversationId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "SUCCESS",
"sandbox": true
}
}
| Field | Meaning |
|---|---|
data.jobId |
Stable sandbox query job identifier used for result retrieval |
data.conversationId |
Correlation identifier for tracing the sandbox query across related systems, when provided |
data.status |
Terminal sandbox query status |
data.sandbox |
true for sandbox query responses |
Then call the results endpoint with the returned jobId.
{
"meta": {
"code": 200,
"type": "success",
"message": "success"
},
"data": {
"jobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"conversationId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "SUCCESS",
"checkListSummary": [
{
"itemId": "REQ-CD-SIGNED",
"submittedDocType": "closing_disclosure",
"docType": "closing_disclosure",
"status": "FOUND"
}
],
"pendingDocumentCount": 0,
"failedDocumentCount": 0,
"results": [
{
"docType": "closing_disclosure",
"pageRanges": [
"1-1"
],
"caseDocumentId": "5d0f2a18-9c41-4f7b-8a63-1e2c4b5d6f70",
"fileName": "closing-disclosure.pdf",
"externalDocumentId": "sandbox-doc-1",
"sourceSystemPath": "sandbox/MORTGAGE_SANDBOX/closing-disclosure.pdf"
}
],
"queryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"queriedAt": "2026-08-19T18:32:04Z",
"sandbox": true
}
}
4. Verify sandbox query activity
Sandbox queries appear in query activity. Use the sandbox=true filter to view only practice queries:
curl -X GET "https://nlsearch-api.digilytics.solutions/nldocsearch/api/v1/123456/queries?sandbox=true&page=0&size=20" \
-H "Authorization: Bearer {access_token}"
5. Verify sandbox webhook delivery
Sandbox queries still reach terminal SUCCESS, so a query.completed webhook can be delivered when a matching webhook subscription is configured. The webhook payload follows the same envelope documented in the Webhooks section and includes eventType: "query.completed" with query identifiers under data. See Webhook HTTP Headers for the headers sent with each delivery.
{
"eventType": "query.completed",
"featureScope": "NLDS_QUERY_COMPLETION",
"data": {
"queryJobId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"casePublicId": "{sandbox_case_public_id}",
"status": "SUCCESS",
"completedAt": "2026-08-24T16:07:39.383472613Z"
}
}
Use sandbox mode to validate request structure, response parsing, status handling, idempotency behavior, query activity filtering, and webhook integration.