# Connect an Agent to DataLinks over A2A
Source: https://docs.landfall.ai/a2a/general-a2a
Connect any A2A-compliant client or agent framework to a DataLinks agent using the Agent2Agent protocol.
DataLinks exposes its agents over the **Agent2Agent (A2A)** protocol, an open standard for programs and agents to talk to each other over HTTP. Every DataLinks agent is a standard **A2A 1.0 server**, so any A2A-compliant client or framework can discover it, authenticate, and stream answers back, without a DataLinks-specific client library.
A2A support is in **preview**. It's available to every account with no extra rate limits, but the surface may still change. Prefer native Python without a protocol layer? The DataLinks [Python SDK](/pythonsdk/index) has its own `ask` API.
## Prerequisites
Before starting, make sure you have:
* A DataLinks API token. See [Get an API token](/how-to/get-api-token). The same token you use for the REST API and SDK works for A2A.
* The **namespace** you want the agent to work against. You can find it under **My Data**.
* Python 3.10+ (this guide uses the official `a2a-sdk` client; any A2A-compliant client works).
## Install Required Dependencies
The reference client uses the official A2A Python SDK and an async HTTP client:
```bash theme={null}
uv pip install a2a-sdk httpx
```
If you also use the DataLinks Python SDK for configuration, install it as well:
```bash theme={null}
uv pip install datalinks
```
## Configure Your Environment
The client reads standard DataLinks settings from the environment (or a `.env` file):
* `DL_HOST` - the DataLinks host that serves the A2A endpoints: `https://api.datalinks.com/api/v1`
* `DL_API_KEY` - your DataLinks API token
* `DL_NAMESPACE` - the namespace the agent should query
* `DL_A2A_AGENT` - *(optional)* the agent to use; defaults to the first agent listed by the server
Alternatively, place these values in a `.env` file at the root of your project.
## How the DataLinks A2A Endpoints Work
A2A lives under the API base URL. With `DL_HOST` set to `https://api.datalinks.com/api/v1`, DataLinks exposes every agent through three endpoints:
| Endpoint | Auth | Purpose |
| - | - | - |
| `GET {host}/a2a` | Public | List the agents this server exposes |
| `GET {host}/a2a/{agent}/.well-known/agent-card.json` | Public | Fetch the agent's **AgentCard** (capabilities and security scheme) |
| `POST {host}/a2a/{agent}` | Bearer token | Send messages (JSON-RPC, streaming) |
DataLinks currently exposes two agents:
| Agent | What it does |
| - | - |
| `nl2ql` | Turns a plain-language question into a structured query, runs it against your namespace, and returns the generated query plus the result rows. |
| `assistant` | Plans a multi-step query strategy, runs the queries, and synthesises an answer. This is the same assistant behind the `ask` endpoint. |
The **AgentCard** is the key idea: it's a self-describing manifest that tells your client what the agent can do and how to authenticate. Your client reads the card first, then uses it to configure the connection, so nothing about the auth flow needs to be hardcoded.
For the full endpoint, AgentCard, and event specification, see the [A2A Protocol Reference](/reference/a2a-protocol).
## Connect and Ask a Question
The flow has five steps: discover agents, resolve the chosen agent's card, register your API key as the bearer credential, send the message with the target namespace, and stream the answer.
```python theme={null}
import httpx
async def list_agents(http: httpx.AsyncClient, host: str) -> list[dict]:
resp = await http.get(f"{host}/a2a")
resp.raise_for_status()
return resp.json()
```
Your API key is sent as a bearer token for whatever security scheme(s) the card declares (DataLinks declares a single scheme named `bearer`, sent as `Bearer `).
```python theme={null}
from a2a.client import (
A2ACardResolver, AuthInterceptor, ClientConfig, ClientFactory,
ClientCallContext, InMemoryContextCredentialStore,
)
SESSION_ID = "datalinks-a2a-example"
agent_base = f"{host}/a2a/{agent_name}"
card = await A2ACardResolver(http, base_url=agent_base).get_agent_card()
store = InMemoryContextCredentialStore()
for requirement in card.security_requirements:
for scheme_name in requirement.schemes:
await store.set_credentials(SESSION_ID, scheme_name, api_key)
```
Iterating over the card's declared schemes (rather than hardcoding `"bearer"`) keeps your client working if the scheme name ever changes.
```python theme={null}
factory = ClientFactory(ClientConfig(httpx_client=http, streaming=True))
client = factory.create(card, interceptors=[AuthInterceptor(store)])
```
The namespace travels in the message metadata; the server reads `message.metadata.namespace`. You can optionally add `model` and `provider` to the same metadata to steer which LLM the agent uses.
```python theme={null}
from uuid import uuid4
from google.protobuf.struct_pb2 import Struct
from a2a.types import (
Message, Part, Role, SendMessageConfiguration, SendMessageRequest,
)
metadata = Struct()
metadata.update({"namespace": namespace}) # optionally: "model", "provider"
request = SendMessageRequest(
message=Message(
message_id=str(uuid4()),
role=Role.ROLE_USER,
parts=[Part(text=question)],
metadata=metadata,
),
configuration=SendMessageConfiguration(accepted_output_modes=["text", "data"]),
)
```
```python theme={null}
context = ClientCallContext(state={"sessionId": SESSION_ID})
async for response in client.send_message(request, context=context):
render(response) # see "Handling the streamed response" below
```
A complete, runnable version of this client is in the Python SDK examples under [A2A Client (Agent2Agent Protocol)](/pythonsdk/examples#a2a-client-agent2agent-protocol).
## Handling the Streamed Response
A2A streaming yields a sequence of typed events, not a single answer. Each event's `payload` is one of `task`, `message`, `status_update`, or `artifact_update`. Within a message or artifact, the content arrives as **parts**, and each part is either a **TextPart** or a **DataPart**.
The two agents return their results as named artifacts:
* `nl2ql` emits a `generated-query` artifact (the query, as a `TextPart`) and a `result-data` artifact (the rows, as a `DataPart`).
* `assistant` streams `working` status updates while it plans and runs steps, a `step-N` artifact per step (the step's query plus its rows), and a final `answer` artifact (the synthesised answer, as a `TextPart`).
Handle **both** part types. The query and answer arrive as `TextPart`s, but the **rows come back as a `DataPart`** (under a `rows` key). If you only read text parts, the rows are silently dropped and you'll see the query but not the data.
```python theme={null}
from google.protobuf.json_format import MessageToDict
from a2a.types import TaskState
def render(response) -> None:
kind = response.WhichOneof("payload")
if kind == "message":
_print_parts(response.message.parts)
elif kind == "artifact_update":
_print_parts(response.artifact_update.artifact.parts)
elif kind == "status_update":
status = response.status_update.status
print(f"[status: {TaskState.Name(status.state)}]")
if status.HasField("message"):
_print_parts(status.message.parts)
elif kind == "task":
task = response.task
print(f"[task {task.id}: {TaskState.Name(task.status.state)}]")
for artifact in task.artifacts:
_print_parts(artifact.parts)
def _print_parts(parts) -> None:
for part in parts:
content = part.WhichOneof("content")
if content == "text":
print(part.text)
elif content == "data":
payload = MessageToDict(part.data) # protobuf -> native dict/list
rows = payload.get("rows") if isinstance(payload, dict) else None
print(rows if rows is not None else payload)
```
## Verify the Connection
To confirm everything is working:
* `GET {host}/a2a` returns a non-empty list of agents (`nl2ql` and `assistant`).
* Fetching the AgentCard for your chosen agent succeeds.
* A simple question streams back a `status_update` reaching a completed state, followed by an answer.
* No `401`/`403` authorization errors appear.
## Troubleshooting
**Authorization errors (401 / 403)**
* Confirm your DataLinks API token is correct and current.
* Make sure the token is sent as `Bearer ` with no extra spaces.
* Confirm the scheme name you registered matches the one the AgentCard declares (`bearer`).
**No agents returned**
* Confirm `DL_HOST` points at `https://api.datalinks.com/api/v1` (A2A lives under the API base, not the root host).
**`message metadata.namespace is required`**
* The namespace must live in the **message** metadata (`message.metadata.namespace`), not in the URL, the request-level metadata, or the body.
**You see the query but not the data**
* You're only handling `TextPart`s. Handle `DataPart`s too (see the warning above); the rows arrive under `part.data.rows`.
**Empty or hanging responses**
* Increase the HTTP client timeout; some agent runs take longer than the default.
* Confirm the `namespace` in the message metadata exists, has at least one dataset, and you have access to it.
## When to Use A2A, MCP, or the SDK
DataLinks offers three ways to reach the same data. Pick by how you're building:
| You want to... | Use |
| - | - |
| Talk to a DataLinks **agent** as a peer, from any A2A-compliant
framework | **A2A** (this guide) |
| Expose DataLinks as a **tool** inside an AI client (Claude Desktop,
IDEs, agent frameworks that speak MCP) | [MCP](/mcp/general-mcp) |
| Write native **Python** with no protocol layer | [Python SDK `ask`](/pythonsdk/examples) |
## Next Steps
* Read the [A2A Protocol Reference](/reference/a2a-protocol) for the full endpoint, AgentCard, and event details.
* Try the [complete runnable client](/pythonsdk/examples#a2a-client-agent2agent-protocol).
* Combine the agent's answers with your own workflow or multi-agent system.
# A2A JSON-RPC endpoint for an agent
Source: https://docs.landfall.ai/api-reference/a2a/a2a-json-rpc-endpoint-for-an-agent
/api-reference/openapi.yml post /a2a/{agentName}
JSON-RPC 2.0 endpoint implementing the A2A methods `SendMessage`, `SendStreamingMessage`, `GetTask`,
`CancelTask` and `GetExtendedAgentCard`. `SendStreamingMessage` responds with `text/event-stream`; the others
with a single JSON-RPC response. `GetExtendedAgentCard` returns the authenticated, richer AgentCard.
Protocol errors are returned as JSON-RPC error objects with HTTP 200. The target namespace is supplied
in the message `metadata.namespace`; the acting user is taken from the bearer token.
Request and response bodies follow the A2A 1.0 spec (https://a2a-protocol.org).
An agent whose card advertises the caller-context extension also accepts context established before it was
called, as `data` parts tagged with the extension URI; a malformed or oversized payload, or context sent to an
agent that does not advertise it, is an `InvalidParams` error. See docs/a2a-caller-context.md.
# Fetch an agent's AgentCard
Source: https://docs.landfall.ai/api-reference/a2a/fetch-an-agents-agentcard
/api-reference/openapi.yml get /a2a/{agentName}/.well-known/agent-card.json
Returns the A2A AgentCard describing the agent's skills, capabilities, security schemes and JSON-RPC
endpoint. Public (unauthenticated) discovery document.
# List the agents exposed over A2A
Source: https://docs.landfall.ai/api-reference/a2a/list-the-agents-exposed-over-a2a
/api-reference/openapi.yml get /a2a
Convenience listing of the available A2A agents and their AgentCard URLs. Public, no authentication.
# Delete an agent run and any resume cache associated with it
Source: https://docs.landfall.ai/api-reference/agentruns/delete-an-agent-run-and-any-resume-cache-associated-with-it
/api-reference/openapi.yml delete /query/runs/{runId}
Permanently removes the run record and its checkpoint blob. Returns 404 for both
"never existed" and "owned by someone else" to avoid leaking existence of other
users' conversations.
# Get the status of an agent run
Source: https://docs.landfall.ai/api-reference/agentruns/get-the-status-of-an-agent-run
/api-reference/openapi.yml get /query/runs/{runId}
Returns the current status of an agent run, the original question, and (for completed
runs) the rendered result. Use this to discover whether a run is still running, completed,
failed, or expired (e.g. after a dropped SSE connection).
# Resume a previously paused or crashed agent run
Source: https://docs.landfall.ai/api-reference/agentruns/resume-a-previously-paused-or-crashed-agent-run
/api-reference/openapi.yml post /query/runs/{runId}/resume
Resumes a run from its last checkpoint. The response is an SSE stream whose first
event is `run-started`, followed by the same domain events as the original streaming
endpoint (e.g. `plan`, `step`, `answer` for AutoRag).
Concurrency: each call rotates the run's session id. If a second resume request
arrives while a first is in flight, the first wrapper exits silently on its next
checkpoint write; only the latest resume continues streaming events to its caller.
Only runs whose agent checkpoints its progress can be replayed. The AutoRag agent
currently writes no checkpoints, so resume answers 409 for every run still in
progress and the run continues to completion on its original connection.
Terminal states are surfaced via HTTP status codes:
- 410 Gone: run is Completed (the final answer is in the body) OR Expired
(its resume cache is gone or no longer loadable). The body carries the original
question and, for Completed runs, the rendered result.
- 409 Conflict: run has no checkpoint to replay from. It is left untouched — poll
`GET /api/v1/query/runs/{runId}` for its outcome.
- 422 Unprocessable Content: run is in a Failed state
- 404 Not Found: run does not exist or is not owned by the caller
# Submit thumbs up/down feedback on a completed agent run
Source: https://docs.landfall.ai/api-reference/agentruns/submit-thumbs-updown-feedback-on-a-completed-agent-run
/api-reference/openapi.yml post /query/runs/{runId}/feedback
Records user feedback (thumbs up/down plus an optional comment) as a score on the
Langfuse trace that produced the run's answer. Only completed runs can be rated.
Resubmitting overwrites the previous feedback for the same run.
# Get code files for a cleaning task
Source: https://docs.landfall.ai/api-reference/agents/get-code-files-for-a-cleaning-task
/api-reference/openapi.yml get /agents/cleaning/{cleaningTaskId}/code
Retrieve metadata for all code files generated by the cleaning agent for the specified task.
# Request cleaning on a dataset
Source: https://docs.landfall.ai/api-reference/agents/request-cleaning-on-a-dataset
/api-reference/openapi.yml post /agents/{username}/{namespace}/{datasetName}/requestCleaning
Request a cleaning job for the specified dataset.
# Run AutoRAG agent over all data
Source: https://docs.landfall.ai/api-reference/autorag/run-autorag-agent-over-all-data
/api-reference/openapi.yml post /query/autorag
Uses the AutoRAG agent to answer a question about any data that the user can access.
# Run AutoRAG agent with streaming response
Source: https://docs.landfall.ai/api-reference/autorag/run-autorag-agent-with-streaming-response
/api-reference/openapi.yml post /query/ask
Uses the AutoRAG agent to answer a question about any data that the user can access.
Returns results as a Server-Sent Events (SSE) stream with incremental updates.
The stream emits the following event types in order:
- `plan`: The execution plan with a list of steps to be performed.
- `step`: One event per step, containing the instruction, query, and result data.
- `answer`: The final synthesized answer to the user's question.
- `error`: Emitted if an error occurs during processing (instead of answer).
# Add a file to a batch file-input slot
Source: https://docs.landfall.ai/api-reference/buildspecs/add-a-file-to-a-batch-file-input-slot
/api-reference/openapi.yml post /build-specs/{buildSpecId}/file-inputs/{slot}/members
Adds one uploaded file to the slot's bound set (for batch slots).
# Create a BuildSpec
Source: https://docs.landfall.ai/api-reference/buildspecs/create-a-buildspec
/api-reference/openapi.yml post /build-specs
Create a derived-dataset transformation definition. Datasets are addressed by
user/namespace/dataset name. The caller must have access to every source and every destination.
# Delete a BuildSpec
Source: https://docs.landfall.ai/api-reference/buildspecs/delete-a-buildspec
/api-reference/openapi.yml delete /build-specs/{buildSpecId}
# Edit a BuildSpec
Source: https://docs.landfall.ai/api-reference/buildspecs/edit-a-buildspec
/api-reference/openapi.yml put /build-specs/{buildSpecId}
Replace the definition and bump its version. Datasets are addressed by name.
# Fetch a preview output's download URL
Source: https://docs.landfall.ai/api-reference/buildspecs/fetch-a-preview-outputs-download-url
/api-reference/openapi.yml get /build-runs/{buildRunId}/outputs/{destinationName}
Returns a time-limited presigned S3 URL to download one destination slot's produced rows (JSONL) directly from the bucket. Only available for preview runs.
# Get a BuildRun
Source: https://docs.landfall.ai/api-reference/buildspecs/get-a-buildrun
/api-reference/openapi.yml get /build-runs/{buildRunId}
# Get a BuildSpec
Source: https://docs.landfall.ai/api-reference/buildspecs/get-a-buildspec
/api-reference/openapi.yml get /build-specs/{buildSpecId}
# List a BuildSpec's runs
Source: https://docs.landfall.ai/api-reference/buildspecs/list-a-buildspecs-runs
/api-reference/openapi.yml get /build-specs/{buildSpecId}/runs
# List a pipeline repo's files
Source: https://docs.landfall.ai/api-reference/buildspecs/list-a-pipeline-repos-files
/api-reference/openapi.yml get /build-specs/{buildSpecId}/files
The paths of every file in the GitPipeline repo at the default branch's HEAD.
# List a pipeline's file-input slot bindings
Source: https://docs.landfall.ai/api-reference/buildspecs/list-a-pipelines-file-input-slot-bindings
/api-reference/openapi.yml get /build-specs/{buildSpecId}/file-inputs
The uploaded files currently bound to each file-input slot of a GitPipeline BuildSpec.
# List a preview run's outputs
Source: https://docs.landfall.ai/api-reference/buildspecs/list-a-preview-runs-outputs
/api-reference/openapi.yml get /build-runs/{buildRunId}/outputs
A code-blob preview returns one entry per destination slot (each with an href to fetch its download URL). A GitPipeline preview returns every produced output (declared, computed and discovered), each with an inline presigned download URL. Only available for preview runs.
# List the caller's BuildSpecs
Source: https://docs.landfall.ai/api-reference/buildspecs/list-the-callers-buildspecs
/api-reference/openapi.yml get /build-specs
Returns every BuildSpec owned by the authenticated user.
# Preview a build (dry run)
Source: https://docs.landfall.ai/api-reference/buildspecs/preview-a-build-dry-run
/api-reference/openapi.yml post /build-specs/{buildSpecId}/preview
Run the BuildSpec against a small sample of each source (capped at 100 rows) and present the
output to the caller WITHOUT ingesting it into any dataset. Requires read (QUERY) access to the
sources and destination datasets. The saved code may be overridden with inline code for this preview.
At most one preview may be active per spec. Returns the new run in status `Scheduled`; poll the run and then
fetch its outputs once it reaches `Finished`.
# Read one file from a pipeline repo
Source: https://docs.landfall.ai/api-reference/buildspecs/read-one-file-from-a-pipeline-repo
/api-reference/openapi.yml get /build-specs/{buildSpecId}/file
The content and commit sha of a single file at the default branch's HEAD.
# Remove a file from a file-input slot
Source: https://docs.landfall.ai/api-reference/buildspecs/remove-a-file-from-a-file-input-slot
/api-reference/openapi.yml delete /build-specs/{buildSpecId}/file-inputs/{slot}/members/{fileId}
Removes one uploaded file from the slot's bound set.
# Save edits to a pipeline repo
Source: https://docs.landfall.ai/api-reference/buildspecs/save-edits-to-a-pipeline-repo
/api-reference/openapi.yml put /build-specs/{buildSpecId}/files
Commits an atomic set of file upserts and deletions to the default branch. Carries the base commit sha for optimistic concurrency; a stale base is rejected with 409. `.dvc/config` is platform-owned and cannot be written or deleted.
# Schedule a build (create a BuildRun)
Source: https://docs.landfall.ai/api-reference/buildspecs/schedule-a-build-create-a-buildrun
/api-reference/openapi.yml post /build-specs/{buildSpecId}/run
Schedule a build of the BuildSpec. Re-checks the caller's access to the spec's datasets and
rejects the request if the spec already has an active run. Returns the new run in status `scheduled`.
# Set (replace) the files bound to a file-input slot
Source: https://docs.landfall.ai/api-reference/buildspecs/set-replace-the-files-bound-to-a-file-input-slot
/api-reference/openapi.yml put /build-specs/{buildSpecId}/file-inputs/{slot}
Replaces the slot's bound files with the given set. An empty set unbinds the slot.
# Unbind a file-input slot
Source: https://docs.landfall.ai/api-reference/buildspecs/unbind-a-file-input-slot
/api-reference/openapi.yml delete /build-specs/{buildSpecId}/file-inputs/{slot}
Removes all files bound to the slot.
# Validate a pipeline repo
Source: https://docs.landfall.ai/api-reference/buildspecs/validate-a-pipeline-repo
/api-reference/openapi.yml post /build-specs/{buildSpecId}/files/validate
Runs advisory checks on the repo at HEAD (dvc.yaml present, manifests parse, sources reconcile). Advisory only; it never blocks saving.
# Get a conversation and its turns
Source: https://docs.landfall.ai/api-reference/conversations/get-a-conversation-and-its-turns
/api-reference/openapi.yml get /query/conversations/{conversationId}
Returns the conversation's turns (oldest first), each with the original question, its
status, and — for completed turns — the rendered result. Returns 404 for both
"never existed" and "owned by someone else" to avoid leaking existence of other
users' conversations.
# List the caller's conversations within a namespace
Source: https://docs.landfall.ai/api-reference/conversations/list-the-callers-conversations-within-a-namespace
/api-reference/openapi.yml get /query/conversations
Returns the caller's conversations in the given namespace, most-recently-updated first.
A conversation groups the agent runs (turns) the user sees as a single thread.
# List ingestions for a dataset
Source: https://docs.landfall.ai/api-reference/data/list-ingestions-for-a-dataset
/api-reference/openapi.yml get /data/{username}/{namespace}/{datasetName}/ingest/attempts
Lists ingestion attempts for a dataset.
# Clear all data for a dataset
Source: https://docs.landfall.ai/api-reference/dataset/clear-all-data-for-a-dataset
/api-reference/openapi.yml post /data/{username}/{namespace}/{datasetName}/clear
Remove all data and links from the specified dataset. This action is irreversible.
# Delete all data and metadata for a dataset (balefire)
Source: https://docs.landfall.ai/api-reference/dataset/delete-all-data-and-metadata-for-a-dataset-balefire
/api-reference/openapi.yml delete /data/{username}/{namespace}/{datasetName}/balefire
Remove all data, links, and metadata for the specified dataset. This action is irreversible.
# Get dataset information
Source: https://docs.landfall.ai/api-reference/dataset/get-dataset-information
/api-reference/openapi.yml get /data/{username}/{namespace}/{datasetName}
Get dataset metadata for the specified user, namespace, and dataset.
# Rename a dataset
Source: https://docs.landfall.ai/api-reference/dataset/rename-a-dataset
/api-reference/openapi.yml post /data/{username}/{namespace}/{datasetName}/rename
Rename the specified dataset.
# Rename a namespace
Source: https://docs.landfall.ai/api-reference/dataset/rename-a-namespace
/api-reference/openapi.yml post /data/{username}/{namespace}/rename
Rename the specified namespace.
# Update dataset sort order
Source: https://docs.landfall.ai/api-reference/dataset/update-dataset-sort-order
/api-reference/openapi.yml put /schema/{username}/{namespace}/{datasetName}/sort-order
Update the sort order of columns in a dataset.
# Update inference definition
Source: https://docs.landfall.ai/api-reference/dataset/update-inference-definition
/api-reference/openapi.yml put /data/{username}/{namespace}/{datasetName}/inferDefinition
Update the saved inference definition for the dataset.
# Fetch datasets
Source: https://docs.landfall.ai/api-reference/datasets/fetch-datasets
/api-reference/openapi.yml get /data
Retrieve all available datasets.
# Fetch datasets in namespace
Source: https://docs.landfall.ai/api-reference/datasets/fetch-datasets-in-namespace
/api-reference/openapi.yml get /data/{namespace}
Retrieve all datasets for the specified namespace.
# Abort file upload
Source: https://docs.landfall.ai/api-reference/fileupload/abort-file-upload
/api-reference/openapi.yml post /file-upload/{sessionId}/abort
Abort a single file upload
# Finish file upload
Source: https://docs.landfall.ai/api-reference/fileupload/finish-file-upload
/api-reference/openapi.yml post /file-upload/{sessionId}/files/{fileId}/finish
Finish a single file upload
# List completed files in an upload session
Source: https://docs.landfall.ai/api-reference/fileupload/list-completed-files-in-an-upload-session
/api-reference/openapi.yml get /file-upload/{sessionId}/files
Returns S3 bucket and key for every completed file in the session. Caller must own the session.
# Prepare session for file upload
Source: https://docs.landfall.ai/api-reference/fileupload/prepare-session-for-file-upload
/api-reference/openapi.yml post /file-upload/prepare
Initiate a file upload session and get the upload URLs.
# API Reference
Source: https://docs.landfall.ai/api-reference/index
Welcome to the DataLinks API. These endpoints let you bring in data, connect it intelligently, and ask questions that yield grounded results.
Expand any group in the left navigation to view its endpoints.
Some endpoints require a `username` for authorization. The easiest way to verify your account `username` is to open a dataset in the web platform and inspect the URL slug. For example, `https://datalinks.com/dashboard/USERNAME/testing/testing123`
## Typical end-to-end flow
1. **Create a dataset**\
Use Create new dataset. Supplying a new namespace creates it implicitly.
`POST` [Create new dataset](/api-reference/ingest/create-new-dataset)
2. **Ingest data**\
Use Ingest data or the multipart flow for large files. Use Preview to check parsing before committing.
`POST` [Ingest data](/api-reference/ingest/ingest-data)\
`POST` [Preview](/api-reference/ingest/preview)
Multipart flow:\
`POST` [Prepare multipart upload](/api-reference/multipart-upload/prepare-multipart-upload)\
`POST` [Finish multipart upload](/api-reference/multipart-upload/finish-multipart-upload)\
`POST` [Abort multipart upload](/api-reference/multipart-upload/abort-multipart-upload)
3. **Review links**\
Use Load links and Preview links. Promote useful suggestions to active links with Add a new link or from the app UI. Rebuild links when data changes.
`GET` [Load links](/api-reference/links/load-links)\
`POST` [Preview links](/api-reference/links/preview-links)\
`POST` [Add a new link](/api-reference/links/add-a-new-link)\
`POST` [Rebuild links](/api-reference/links/rebuild-links)
4. **Query**\
Use Query data to retrieve grounded results across connected datasets.
`POST` [Query data](/api-reference/query/query-data)
## Notes and best practices
* **Namespaces**: a namespace appears when you create the first dataset that references it.
* **Large files**: prefer the multipart upload trio to improve reliability.
* **Performance**: Rebuild links after large ingests so suggestions reflect the latest data.
If you're looking for background to get started, see the explanatory articles for [Ingest](/concepts/ingest-clean), [Interconnect](/concepts/interconnection), and [Inquire](/concepts/query-insights), then return here to try automating those steps using the API.
# Create new dataset
Source: https://docs.landfall.ai/api-reference/ingest/create-new-dataset
/api-reference/openapi.yml post /ingest/new/{namespace}/{datasetName}
Creates a new dataset within a specified namespace.
# Infer dataset description
Source: https://docs.landfall.ai/api-reference/ingest/infer-dataset-description
/api-reference/openapi.yml post /infer/description
Ask an agent to infer a data description based on sampled data.
# Ingest data
Source: https://docs.landfall.ai/api-reference/ingest/ingest-data
/api-reference/openapi.yml post /ingest/{namespace}/{datasetName}
Load data into the specified dataset.
# Preview
Source: https://docs.landfall.ai/api-reference/ingest/preview
/api-reference/openapi.yml post /infer/preview
Process data using ingestion logic without saving it to a dataset.
# Schema
Source: https://docs.landfall.ai/api-reference/ingest/schema
/api-reference/openapi.yml post /infer/schema
Ask an agent to infer a schema based on sampled data.
# Mark ingestion as seen
Source: https://docs.landfall.ai/api-reference/ingestiontracking/mark-ingestion-as-seen
/api-reference/openapi.yml post /ingestionTracking/{ingestionId}/seen
Update seenAt timestamp for the specified ingestion.
# Retry ingestion
Source: https://docs.landfall.ai/api-reference/ingestiontracking/retry-ingestion
/api-reference/openapi.yml post /ingestionTracking/{ingestionId}/retry
Creates a new ingestion record copying data from the original and starts the ingestion process in the background. Returns the new ingestion ID.
# Add a new link
Source: https://docs.landfall.ai/api-reference/links/add-a-new-link
/api-reference/openapi.yml post /links/add
Create a new link between two dataset columns.
# Load links
Source: https://docs.landfall.ai/api-reference/links/load-links
/api-reference/openapi.yml get /links/{username}/{namespace}/{datasetName}
Load the links for the specified dataset.
# Preview links
Source: https://docs.landfall.ai/api-reference/links/preview-links
/api-reference/openapi.yml post /links/preview/{username}/{namespace}/{datasetName}
Shows what recalculating links would look like without saving changes.
# Rebuild links
Source: https://docs.landfall.ai/api-reference/links/rebuild-links
/api-reference/openapi.yml post /links/{username}/{namespace}/{datasetName}
Recalculate links for a specified dataset.
# Abort multipart upload
Source: https://docs.landfall.ai/api-reference/multipart-upload/abort-multipart-upload
/api-reference/openapi.yml post /upload/multipart/{username}/{namespace}/{datasetName}/abort
Abort a multipart upload and clean up partial data.
# Finish multipart upload
Source: https://docs.landfall.ai/api-reference/multipart-upload/finish-multipart-upload
/api-reference/openapi.yml post /upload/multipart/{username}/{namespace}/{datasetName}/finish
Finish a multipart upload by providing all part ETags.
# Prepare multipart upload
Source: https://docs.landfall.ai/api-reference/multipart-upload/prepare-multipart-upload
/api-reference/openapi.yml post /upload/multipart/{username}/{namespace}/{datasetName}/prepare
Initiate a multipart upload and get presigned URLs for all parts.
# Delete all data and metadata for a namespace (balefire)
Source: https://docs.landfall.ai/api-reference/namespace/delete-all-data-and-metadata-for-a-namespace-balefire
/api-reference/openapi.yml delete /data/{username}/{namespace}/balefire
Remove all data, links, and metadata for the specified namespace. This action is irreversible. Can remove dataset if flag 'deleteDatasets' is set to true
# Curate ontology links
Source: https://docs.landfall.ai/api-reference/ontology/curate-ontology-links
/api-reference/openapi.yml post /ontology/curate-links
Run the OntologyCurator agent to analyze computed links for a dataset or all datasets in a namespace. When activate is true, the curated links are added to the ontology. When activate is false (default), only returns the curated links without activating them.
# Get dataset ontology
Source: https://docs.landfall.ai/api-reference/ontology/get-dataset-ontology
/api-reference/openapi.yml get /ontology/{username}/{namespace}/{datasetName}
Load the ontology for the specified dataset.
# Save dataset ontology
Source: https://docs.landfall.ai/api-reference/ontology/save-dataset-ontology
/api-reference/openapi.yml post /ontology/{username}/{namespace}/{datasetName}
Save the ontology for the specified dataset.
# List users with access to a dataset
Source: https://docs.landfall.ai/api-reference/permissions/list-users-with-access-to-a-dataset
/api-reference/openapi.yml get /permissions/datasets/{username}/{namespace}/{datasetName}/shares
List the per-user grants on a dataset. Only the dataset owner may list.
# List users with access to a namespace
Source: https://docs.landfall.ai/api-reference/permissions/list-users-with-access-to-a-namespace
/api-reference/openapi.yml get /permissions/namespaces/{username}/{namespace}/shares
List the per-user grants on a namespace. Only the namespace owner may list.
# Revoke a user's access to a dataset
Source: https://docs.landfall.ai/api-reference/permissions/revoke-a-users-access-to-a-dataset
/api-reference/openapi.yml delete /permissions/datasets/{username}/{namespace}/{datasetName}/shares
Remove a previously granted viewer or editor access from a user. Only the dataset owner may revoke.
# Revoke a user's access to a namespace
Source: https://docs.landfall.ai/api-reference/permissions/revoke-a-users-access-to-a-namespace
/api-reference/openapi.yml delete /permissions/namespaces/{username}/{namespace}/shares
Remove a previously granted viewer or editor access from a user. Only the namespace owner may revoke.
# Share a dataset with a user
Source: https://docs.landfall.ai/api-reference/permissions/share-a-dataset-with-a-user
/api-reference/openapi.yml post /permissions/datasets/{username}/{namespace}/{datasetName}/shares
Grant another user viewer or editor access to a dataset. Only the dataset owner may share.
# Share a namespace with a user
Source: https://docs.landfall.ai/api-reference/permissions/share-a-namespace-with-a-user
/api-reference/openapi.yml post /permissions/namespaces/{username}/{namespace}/shares
Grant another user viewer or editor access to a namespace. The grant cascades to all datasets within the namespace. Only the namespace owner may share.
# Query data
Source: https://docs.landfall.ai/api-reference/query/query-data
/api-reference/openapi.yml post /query
Execute an ontology query.
# List all datasets
Source: https://docs.landfall.ai/api-reference/schema/list-all-datasets
/api-reference/openapi.yml get /schema/datasets/all
Retrieve all datasets visible to the authenticated user.
# List datasets within namespace
Source: https://docs.landfall.ai/api-reference/schema/list-datasets-within-namespace
/api-reference/openapi.yml get /schema/{user}/{namespace}/datasets
Retrieve datasets within a specific namespace for a user. Use 'self' for the current user.
# List user namespaces
Source: https://docs.landfall.ai/api-reference/schema/list-user-namespaces
/api-reference/openapi.yml get /schema/{user}/namespaces
Retrieve namespaces for a specific user. Use 'self' for the current user.
# Add a new user token
Source: https://docs.landfall.ai/api-reference/user/add-a-new-user-token
/api-reference/openapi.yml post /user/token
Create a new token for the authenticated user.
# Delete user token
Source: https://docs.landfall.ai/api-reference/user/delete-user-token
/api-reference/openapi.yml delete /user/token/{tokenId}
Delete the specified user token.
# Get the current user
Source: https://docs.landfall.ai/api-reference/user/get-the-current-user
/api-reference/openapi.yml get /user/me
Returns the identity of the authenticated user resolved from the presented bearer token.
# Get user usage aggregated by day
Source: https://docs.landfall.ai/api-reference/user/get-user-usage-aggregated-by-day
/api-reference/openapi.yml get /user/usage/by-day
Retrieve usage data aggregated by day for the authenticated user, with timezone support.
# Get user usage history
Source: https://docs.landfall.ai/api-reference/user/get-user-usage-history
/api-reference/openapi.yml get /user/usage/history
Retrieve historical usage data for the authenticated user.
# List token permissions
Source: https://docs.landfall.ai/api-reference/user/list-token-permissions
/api-reference/openapi.yml get /user/tokens/{tokenId}/permissions
List all permissions assigned to the specified token.
# List user tokens
Source: https://docs.landfall.ai/api-reference/user/list-user-tokens
/api-reference/openapi.yml get /user/tokens
List all tokens for the authenticated user.
# Datasets and Namespaces
Source: https://docs.landfall.ai/concepts/datasets-namespaces
In DataLinks, all information lives within a simple and flexible structure built around **namespaces** and **datasets**. Together, they organize your data, define ownership, and make it easier to manage access and relationships across the platform.
Understanding how namespaces and datasets fit together is the first step in working effectively with DataLinks, whether you use the web platform or the API.
## What a namespace is
A namespace is a logical container that groups datasets under a shared context. It defines *who* the data belongs to and *where* it lives inside your workspace.
You can think of a namespace as the digital equivalent of a project or folder. Each user can have multiple namespaces. Within them, datasets can be organized by department, use case, or business function.
Namespaces are also used to manage access and visibility. A dataset in a private namespace is visible only to its owner or designated users, while a dataset in a public namespace can be viewed and connected to by anyone on the DataLinks platform.
Namespaces can be listed and reviewed using the API:
* [GET List user namespaces](/api-reference/schema/list-user-namespaces)
* [GET List datasets within namespace](/api-reference/schema/list-datasets-within-namespace)
## What a dataset is
A dataset is a structured collection of data stored within a namespace. It can contain information that was ingested from a file, a database export, an API feed, or even unstructured text that has been cleaned and structured by DataLinks.
Every dataset has:
* A dataset **name** (unique within its namespace)
* A **namespace** that the dataset sits in
* A **visibility level** (private or public)
Datasets are the foundation of all downstream actions in DataLinks. They feed the linking process that connects related information, and they serve as the building blocks for AI-driven queries.
When using the API, you can view and manage datasets using:
* [GET Fetch datasets](/api-reference/datasets/fetch-datasets)
* [GET Fetch datasets in namespace](/api-reference/datasets/fetch-datasets-in-namespace?utm_source=chatgpt.com)
* [GET Get dataset information](/api-reference/dataset/get-dataset-information)
## How namespaces and datasets work together
Namespaces organize datasets, and datasets give structure to the data itself.
When you create a dataset, you must specify which namespace it belongs to. This ensures that ownership, permissions, and context are always clear. A single namespace might contain several datasets, for example, `finance_revenue`, `finance_expenses`, and `finance_forecasts`.
Namespaces prevent naming conflicts between teams while allowing easy sharing across a shared environment. They also make it possible to apply consistent access controls and data governance rules.
## Creating namespaces and datasets
You can create and manage both namespaces and datasets through the web platform or using the API.
For step-by-step instructions, see [How To Create a Namespace](/how-to/create-namespace) and [How To Create a Dataset](/how-to/create-dataset).
### Using the web platform
The DataLinks web platform provides a visual interface for creating namespaces and datasets. From the dashboard, you can:
* Create a new namespace and set its visibility
* Add datasets within that namespace
* Upload data files directly or start ingestion through the interface
This method is ideal for quick setup or when you want to explore data interactively.
### Using the API
For automated workflows or large-scale operations, the DataLinks API is the preferred option. The key endpoints include:
* [POST Create new dataset](/api-reference/ingest/create-new-dataset)
* [POST Ingest data](/api-reference/ingest/ingest-data)
* [POST Preview](/api-reference/ingest/preview)
* [POST Rename a dataset](/api-reference/dataset/rename-a-dataset)
* [PUT Update infer definition](/api-reference/dataset/update-infer-definition)
When you create a dataset through the API, you specify the target namespace in the request. Once the dataset exists, you can ingest data directly into it, preview results, or adjust its structure programmatically.
## Managing and maintaining your structure
Over time, you may need to adjust your namespace and dataset configuration. The following endpoints support maintenance and cleanup:
* [PUT Update dataset sort order](/api-reference/dataset/update-dataset-sort-order)
* [POST Clear all data for a dataset](/api-reference/dataset/clear-all-data-for-a-dataset)
* [DEL Delete all data and metadata for a dataset (balefire)](/api-reference/dataset/delete-all-data-and-metadata-for-a-dataset-balefire)
These ensure your workspace stays organized and that older or redundant data can be removed safely without affecting other namespaces.
## Why namespaces and datasets matter
Namespaces and datasets bring clarity to complex data environments. They provide consistent organization, clear ownership, and a path for controlled sharing.
Whether you use the web platform for simplicity or the API for scale, namespaces and datasets are the foundation for everything that happens in DataLinks, from ingestion to interconnection to inquiry.
# Data Ingestion and Cleaning
Source: https://docs.landfall.ai/concepts/ingest-clean
Organizations of all sizes are seeing their data grow faster than their ability to use it, with much of that data living in spreadsheets, databases, PDFs, emails, and countless other siloed sources. Yet when businesses try to apply it to AI or automation, the results disappoint. The reason is simple: the data exists, but it is messy, inconsistent, and disconnected.
At DataLinks, ingestion and cleaning are not viewed as separate chores. They are seen as the first stages of intelligence. Together, they transform unstructured or disconnected information into structured, standardized data that can be understood by people and by AI systems.
## Why traditional ingestion and cleaning fall short
In most environments, ingestion simply means collecting data from one place and storing it in another. Cleaning is handled later, usually through manual scripts that remove errors, rename columns, or standardize formats. These steps are repetitive and lack an understanding of the data being ingested.
Even after cleaning, the result can be technically correct but still inconsistent. A company name might appear in several different forms, or a date field might use mixed formats. When this happens, relationships between datasets are hidden, and insights are lost.
Treating ingestion and cleaning as separate stages causes delay and duplication. Teams spend time fixing the same problems in slightly different ways, and valuable information remains disconnected.
## Using the DataLinks web platform or the API
DataLinks offers two ways to ingest and clean data: through the web platform or through the API. Both use the same underlying infrastructure and produce identical results, but each suits a different kind of workflow.
### The web platform
The DataLinks web platform is the fastest way to start. It provides a guided experience for creating datasets, uploading files, and running ingestion directly from your browser. You can preview data, apply cleaning options via prompts, and view validation results in real time.
This interface is ideal when:
* You are testing new data sources or experimenting with formats
* You want to see immediate visual feedback on how cleaning and extraction work
* You are working with a limited number of files or moderate data volumes
The platform automatically applies the same cleaning, validation, and normalization logic used by the API. It also records metadata so your results stay consistent if you later decide to automate ingestion.
### The DataLinks API
For larger or recurring workloads, the DataLinks API is the best choice. The API provides endpoints for every step in the ingestion and cleaning process, from creating datasets and previewing files to ingesting, validating, and managing them at scale.
This approach is best when:
* You need to ingest large or frequent data uploads
* You want to integrate DataLinks with an existing data pipeline or workflow
* You prefer automation over manual uploads
* You are handling high-volume or sensitive datasets where precision and repeatability matter
The API supports multipart uploads and asynchronous processing for efficiency.
## How DataLinks improves ingestion and cleaning
DataLinks combines ingestion, cleaning, and validation so that every dataset is structured correctly from the beginning. The process uses a simple sequence of API calls, all of which are available in the DataLinks API documentation.
### Step 1. Create a dataset
The ingestion process begins by defining where data will live. You use the **Create new dataset** endpoint ([POST Create new dataset](/api-reference/ingest/create-new-dataset)) to register a dataset within a namespace. This step establishes ownership, visibility, and a consistent place for data to be ingested.
You can review existing datasets or namespaces using:
* [GET Fetch datasets](/api-reference/datasets/fetch-datasets)
* [GET Fetch datasets in namespace](/api-reference/datasets/fetch-datasets-in-namespace?utm_source=chatgpt.com)
* [GET List all datasets](/api-reference/schema/list-all-datasets)
* [GET List user namespaces](/api-reference/schema/list-user-namespaces)
* [GET List datasets within namespace](/api-reference/schema/list-datasets-within-namespace)
These endpoints ensure teams know exactly where each dataset lives before ingestion begins.
### Step 2. Preview your data
Before uploading large or complex files, you can use the
[POST Preview](/api-reference/ingest/preview)
endpoint to inspect a sample. This confirms that fields, headers, and formats look correct before the full load.
If data requires cleaning instructions, you can add prompts or definitions at this stage. For example, you might request that all dates be standardized or that field names follow a specific convention.
### Step 3. Ingest and clean data
Once the dataset and preview look correct, use the
[POST Ingest data](/api-reference/ingest/ingest-data)
endpoint to bring data into the platform.
During ingestion, DataLinks automatically:
* Extracts structure from both structured and unstructured inputs
* Applies normalization to align fields to the target schema
* Flags missing or mismatched values
* Validates that data fits expected patterns
### Step 4. Review and manage datasets
Once data is ingested, it can be inspected and maintained through several dataset-level APIs:
* [GET Get dataset information](/api-reference/dataset/get-dataset-information)
Retrieves full metadata, including owner, namespace, and visibility.
* [POST Rename a dataset](/api-reference/dataset/rename-a-dataset)
Updates dataset names without recreating them.
If cleaning requires a full reset, two endpoints handle dataset clearing or deletion:
* [POST Clear all data for a dataset](/api-reference/dataset/clear-all-data-for-a-dataset)
Empties a dataset while keeping its metadata.
* [DEL Delete all data and metadata for a dataset (balefire)](/api-reference/dataset/delete-all-data-and-metadata-for-a-dataset-balefire)
Completely removes a dataset and its associated records.
### Step 5. Maintain structure and relationships
While link creation and ontology management are covered in later articles, ingestion depends on their foundations. For reference, related endpoints include:
* [GET Load links](/api-reference/links/load-links)
* [POST Rebuild links](/api-reference/links/rebuild-links)
* [POST Preview links](/api-reference/links/preview-links)
* [POST Add a new link](/api-reference/links/add-a-new-link)
* [GET Get ontology](/api-reference/ontology/get-ontology-)
* [POST Post ontology](/api-reference/ontology/post-ontology-)
These interfaces are how ingested and cleaned data later become connected and semantically rich.
### Step 6. Authentication and tokens
All API operations use token-based authentication. Tokens are managed through:
* [GET Get usertokens](/api-reference/user/get-usertokens)
* [POST Post usertoken](/api-reference/user/post-usertoken)
* [DEL Delete usertoken](/api-reference/user/delete-usertoken)
These endpoints allow secure access while keeping control in the hands of dataset owners.
## Why this matters
AI systems and analytics tools depend on consistent data. If data arrives in different shapes, or if key fields vary from file to file, even the best models will produce uncertain results.
By combining ingestion and cleaning, DataLinks ensures that data is immediately usable. Clean structure, standardized labels, and reliable formatting make it easier to connect information later and to build accurate insights.
This process also reduces the time and technical skill needed to prepare data. Instead of spending weeks building scripts, teams can upload data, add simple cleaning instructions, and trust the output.
## Example scenarios
* **Financial data**: Normalize columns and clean date formats before analysis to ensure accuracy across multiple reports.
* **Procurement data**: Ingest vendor lists from different departments, fix naming inconsistencies, and prepare them for future linking.
* **Unstructured reports**: Convert raw text into structured rows, apply date formatting, and validate results automatically.
Each of these cases shows how intelligent ingestion removes repetitive cleanup and gives teams data they can use immediately.
## The foundation for what comes next
Ingestion and cleaning form the first phase of the broader DataLinks model: **Ingest, Interconnect, Inquire.**
* Ingest prepares and cleans the data.
* Interconnect builds relationships between datasets.
* Inquire allows people and AI to explore the results.
Without clean, consistent ingestion, the rest of the process cannot succeed. Ingestion and cleaning is where raw information first starts to become meaningful.
# Interconnection (Knowledge Maps)
Source: https://docs.landfall.ai/concepts/interconnection
Once your data is ingested and organized into datasets, DataLinks automatically begins to find relationships between them. This process is known as **interconnection**, and it is the foundation of what makes DataLinks a true ontological system rather than just another data warehouse.
## From datasets to knowledge maps
Each dataset in DataLinks represents a collection of related facts, such as a registry of companies, a list of shareholders, or a table of tariffs. When multiple datasets exist within the same private namespace, or across public namespaces, DataLinks can detect and create **links** between them based on shared entities such as company names, IDs, or other attributes.
These relationships form a **knowledge map**: a living network of data that shows how your information connects across different contexts.
The screenshot above is an example of this visualization in the DataLinks web platform. On the left, you can see datasets represented as nodes, with active links connecting them. On the right, you see the list of **Suggested links** and **Active links:** the relationships that have been automatically discovered or confirmed.
## Why interconnection matters
Most organizations struggle not because they lack data, but because their data lives in silos. Each dataset tells part of the story, but the real value emerges only when those pieces connect.
Interconnection turns data into context. It allows DataLinks to:
* Surface hidden relationships between entities
* Enrich AI and analytics with broader context
* Enable complex reasoning across datasets, such as tracing ownership networks or identifying risk exposure
This connected structure also provides the semantic memory that makes AI agents intelligent and consistent. When data is linked through knowledge maps, AI systems can recall facts, understand context, and reason across multiple domains, just like a human analyst.
## How it works
When a dataset is connected to others, the platform performs entity matching and link inference using both rule-based logic and AI-assisted reasoning. It identifies similar fields, overlapping records, and meaningful associations that may not be obvious in the raw data.
For instance:
* A registry/companies dataset might connect to registry/shareholders through shared company IDs or names.
* The same company could also link to sanctions/individuals or supply chain/tariffs datasets if related entities are detected.
* Each connection is scored, labeled (for example, “exact match” or “partial match”), and stored for reuse across the platform.
This linking process can be managed through the web platform or the API:
* [GET Load links](/api-reference/links/load-links)
* [POST Add a new link](/api-reference/links/add-a-new-link)
* [POST Rebuild links](/api-reference/links/rebuild-links)
* [POST Preview links](/api-reference/links/preview-links)
Through these endpoints, developers can programmatically create, rebuild, or preview connections, making it possible to automate updates as new data is ingested.
## Visualizing connections
The DataLinks web platform provides an interactive **Connections** view where each node represents a dataset and each line represents an active or potential link. Hovering over a node reveals its relationships, and clicking a link shows the fields or entities involved.
This visualization helps users explore data relationships intuitively, trace where insights come from, and understand how their information fits within the wider enterprise knowledge map.
# Querying Data and Insights
Source: https://docs.landfall.ai/concepts/query-insights
Once data is ingested and cleaned, then connected into knowledge maps, the next step is understanding it. Querying is how users turn structured data into clear, actionable insights.
In traditional systems, querying requires technical skill and a precise knowledge of where data lives. At DataLinks, this process is transformed. The platform combines the power of structured data with the reasoning ability of large language models (LLMs) so that anyone can ask questions and get accurate, meaningful answers.
## From data to questions
Most enterprise data is too complex for manual exploration. Each table might contain thousands of rows, dozens of columns, and connections across multiple datasets. Writing SQL to join and filter all of that is time-consuming and error-prone.
DataLinks bridges that gap by using LLMs to interpret questions and generate the right structure automatically. When a user types a question, for example, *“Show me all suppliers who serve both our European and Asian operations”* , the system translates that intent into an optimized SQL query that runs across the connected datasets.
This is not just keyword matching. The model understands context, recognizes relevant entities, and uses the knowledge map to locate the necessary tables and fields. It can reason about relationships such as *companies → shareholders → regions* and construct multi-step queries that would otherwise require a data analyst.
## How querying works in DataLinks
Querying is built into both the **web platform** and the **API**, giving flexibility to business users, analysts, and developers alike.
1. **Web Platform**
Through the Preview & Query interface, users can type natural language prompts or manually write SQL. The platform runs the query, displays results, and highlights the underlying datasets and links used.
2. **API**
Developers can submit queries directly using:
* [POST Query data](/api-reference/query/query-data)
This endpoint accepts structured query requests and returns results as JSON, which can be integrated into dashboards, reports, or applications.
## The role of the LLM in structuring queries
At the core of DataLinks' querying is the LLM-powered reasoning engine. Its role is not to replace databases but to make them more accessible and intelligent.
Unlike generic chat-based LLM systems that generate freeform answers, DataLinks uses its models to reason *within* the boundaries of verified enterprise data. Every answer is grounded in facts that come directly from your connected datasets.
Here is how the process works:
* **Interpretation:** The model reads your natural language question and identifies the relevant entities and metrics.
* **Translation:** It converts that intent into query syntax, joining the correct datasets within the namespace.
* **Optimization:** It selects the most efficient path through the knowledge map, prioritizing the strongest or most relevant links.
### How hallucinations are mitigated
Because the LLM operates on top of DataLinks’ structured semantic layer, it cannot produce results outside of what the data actually contains. The model does not “guess” or fill in gaps. Instead, it constructs queries that are executed against real, verified data.
This design eliminates the most common cause of hallucination in AI-driven analytics: freeform generation without grounding.
The system ensures reliability in several ways:
* **Grounded reasoning:** The model only references information that exists in connected datasets.
* **Schema awareness:** It understands dataset structure and field names, which significantly minimizes the risk of it inventing nonexistent columns or relationships.
* **Query validation:** Every LLM-generated query is checked against the schema and executed through the database, guaranteeing factual output.
* **Traceability:** Users can always view the generated SQL query, giving full transparency into how the result was produced.
By grounding the model in enterprise data and maintaining full transparency, DataLinks gives organizations the power of natural language querying without the risk of misinformation or hallucinated results.
## From queries to business insights
Once data can be queried naturally, insight becomes immediate. Executives and analysts no longer need to wait for data teams to prepare reports. Instead, they can explore information directly, following their questions where they lead.
Some examples of the insights that can be unlocked include:
* **Compliance and risk:** Identify indirect links between entities and sanctioned organizations.
* **Procurement optimization:** Discover overlapping suppliers across business units to consolidate spend.
* **Financial intelligence:** Correlate company performance metrics with supply chain dependencies.
* **Operational monitoring:** Surface anomalies, trends, and relationships across regions or product lines.
These insights come from structured connections between datasets, not isolated tables. The LLM simply makes navigating those connections fast and intuitive.
## Why this matters
Enterprises spend millions each year trying to extract value from data, but the bottleneck often lies between *knowing what to ask* and *how to ask it*. DataLinks removes that barrier by allowing anyone, not just SQL experts, to query their data through plain language.
Because the platform understands both the structure and the meaning of the data, it can generate high-quality queries that reflect real business logic. This combination of semantic structure and LLM interpretation turns querying into an intelligent conversation with your data.
## Example: Deep search across connected datasets
You can use query language to traverse multiple degrees of connections in the knowledge map to uncover indirect relationships that matter for compliance and risk. For example, the following query starts from a specific company in the registry, explores its network, and then looks for any links to sanctions data:
```
Ontology("registry/companies")
.filter("full_name" == "Qualcomm Technologies, Inc.")
.searchAround(5)
.find("sanctions/companies")
```
This query works in several steps:
* `Ontology("registry/companies")` selects the companies registry dataset as the starting point.
* `.filter("full_name" == "Qualcomm Technologies, Inc.")` narrows the focus to a single legal entity based on its full registered name.
* `.searchAround(5)` walks the knowledge map up to five degrees of connections away from that company, following the defined links between datasets such as ownership, subsidiaries, and related entities.
* `.find("sanctions/companies")` returns the companies in the sanctions dataset that are discovered within that five-degree reach.
This kind of deep search can reveal whether a company has direct or indirect exposure to sanctioned entities, without requiring the user to know ahead of time how those relationships are structured.
## The foundation of data-driven decision making
When querying works at this level, AI becomes more than a tool for analysis; it becomes a partner in reasoning. Clean, connected, and well-structured data combined with LLM-driven querying gives organizations the ability to move from static reports to continuous discovery.
With DataLinks, insight is no longer locked behind technical barriers. It becomes a shared capability across the enterprise, available to every team and every question.
# Introduction to DataLinks
Source: https://docs.landfall.ai/get-started/intro-datalinks
DataLinks is a platform that turns scattered data into structured knowledge that both humans and AI can actually use. Instead of forcing you to clean, reshape, merge, or premodel your data before you can start asking questions, DataLinks learns from your data as it is, builds structure where it is missing, and then gives you a simple way to explore and query it with clarity and confidence.
This makes DataLinks a semantic knowledge platform.
Your spreadsheets, CSVs, exports, PDFs, and structured systems become part of one unified knowledge network that humans can query over and AI can reason over without hallucinating or guessing.
## How DataLinks works
DataLinks follows three stages that work together as one workflow.
**Ingest**\
The platform takes in your data from spreadsheets, databases, APIs, uploads, or files. During ingestion, DataLinks uses its cleanup and normalization layer to apply structure and meaning.
Learn more about [Ingestion and Cleaning](/concepts/ingest-clean).
**Interconnect**\
As soon as data is inside the platform, DataLinks automatically finds relationships between values across datasets. These relationships are stored as links. Together, these links form a knowledge map that reflects how your business entities relate in the real world.
Learn more about [Interconnection (Knowledge Maps)](/concepts/interconnection).
**Inquire**\
Once your data is clean and connected, you can ask questions using natural language or by writing queries. DataLinks uses a lightweight language model to generate accurate structured queries based on the shape and relationships inside your data. The important detail is that the model is not inventing answers. It is using your data as truth.
Learn more about [Querying and Insights](/concepts/query-insights).
## What makes DataLinks different
DataLinks does not ask you to solve structure before you get value. It builds it with you.
Most BI (business intelligence) systems require months of schema design before you can start asking questions. Most AI systems simply cannot reason over enterprise-scale data. DataLinks solves both issues by building a semantic layer based on actual data rather than hand-built modeling.
Key differences between DataLinks and traditional solutions include:
* It does not require you to predefine a schema
* It discovers relationships instead of asking you to hard-code them
* It uses an LLM as a *reasoning layer,* not as a source of truth
* It prevents hallucination by forcing all model reasoning to reference real data
* It works with messy real-world data rather than idealized warehouse data
## Vision
DataLinks is building a future in which knowledge is not locked inside specialized tools, departments or people. Data should not require SQL fluency or a data engineering team before you can ask a business question.
The vision is a world where every employee is able to ask questions of the truth without worrying about where the truth lives.
DataLinks exists to give organizations a semantic layer that makes enterprise data understandable by both humans and AI, so insight no longer depends on technical bottlenecks, modeling cycles, or dashboard backlogs.
The goal is simple: turn organizational knowledge into something every person, and every AI agent, can use.
## Looking for platform access?
DataLinks is currently available to select partners and early users. To request access or join the waitlist, [sign up for the waitlist](https://datalinks.com/newsletter).
# DataLinks Web Platform QuickStart Guide
Source: https://docs.landfall.ai/get-started/quickstart
Learn how to ingest data, view connections, and query using the DataLinks web platform.
## Introduction
This guide shows you how to use the DataLinks web platform to find shared sales opportunities across two North American data sources. You will learn by doing: uploading files, connecting records, and exploring the results directly in the platform.
This guide focuses on the web platform only. If you'd like to learn about using the DataLinks API and SDK kit, you can try our [API/SDK QuickStart guide](/get-started/quickstart-api) instead.
## Scenario
You're a sales manager for a dental products company in North America. Your goal is to see how your existing customer base overlaps with members of a major dental society to uncover new prospecting opportunities.
You'll work with two datasets:
* A JSON file that contains your current customers, including names, locations, and contact details
* A dental society member list that includes dentists and clinic owners across the United States and Canada
Using the DataLinks web platform, you'll ingest both datasets, manage how they connect, and query the linked data to find customers who are also society members or have professional ties to them.
## Outcome
By the end of this guide, you will have a table showing which of your current customers also appear in the dental society list. You can explore these matches in the DataLinks platform or export them as a CSV file for follow-up.
This exercise helps you practice the full workflow in DataLinks: importing data, linking records, exploring relationships, and using the insights for focused outreach.
## Prerequisites
To follow along on your machine and complete the steps in this guide, make sure you have the following ready:
1. Access to a DataLinks account.
Don't have an account? Contact [support@datalinks.com](support@datalinks.com) to request one.
2. Downloaded copies of the [example customer list](https://docs.cdn.datalinks.com/docs/quickstart-web/customer_list.json) and [example society list](https://docs.cdn.datalinks.com/docs/quickstart-web/society_list_web.txt). (Click to download.)
With these things in place, you're ready to get started!
## Part 1: Create your namespace and first dataset
1. Log in to the DataLinks platform.
2. Click **Create New Dataset**.
3. Enter the following dataset name: `customer_list`.
Use underscores, not spaces, when naming your datasets.
4. Enter namespace `dental_prospects_web`.
5. Ensure the Dataset visibility is set to **Private** using the toggle.
6. Click **Create dataset and upload data**.
Result: You've created an empty `customer_list` dataset inside the `dental_prospects_web` namespace.
## Part 2: Upload the structured customer list
Now that you've created an empty dataset, you need to populate it with data. To do so, follow these steps:
1. Click anywhere in the **Click to upload** area to open the file explorer.
2. Select the [customer\_list.json](https://docs.cdn.datalinks.com/docs/quickstart-web/customer_list.json) file you downloaded, then click **Open**.
3. Wait while DataLinks ingests your data.
4. Click the **eye icon** under Ingestion Results to review the data and confirm that everything looks correct.
5. Scroll down to the bottom of the page, then click **Upload data**.
6. Click **Append** to confirm.
Result: You've now populated your `customer_list` dataset with data.
## Part 3: Create a second dataset for the society members list
Now that you have your customer list uploaded, you need to upload the list of dental society members you want to compare it against.
1. Return to Home.
2. Click **Create New Dataset**.
3. Enter the following dataset name: `society_members`.
4. Enter namespace `dental_prospects_web`.
5. Ensure the Dataset visibility is set to **Private** using the toggle.
6. Click **Create dataset and upload data**.
## Part 4: Upload the unstructured society members list
Now that you've created your dataset for the society member list, you need to populate it with data. To do so, follow these steps:
Enter the Helper Prompts and Field Definitions before uploading any data. Files that are attached earlier will not have transformations applied.
3. In the **Helper Prompt** field, paste the following:
```
This is the data from a dental societies where the names of members (dental practices), the addresses, the phone numbers, and the websites, are included. The file contains several entries.
```
4. In the **Field Definitions** field, paste the following:
```
MemberName = this is the set of names that are used to define the member entity, which may be a person, or may be an entity like and institution, business, etc. Do the import without academic titles such as "Dr." for Doctor, "Prof." for professor and similar, and without personal honorifics like "Ms.", "Mr.", "Frau", "Herr", and extensions of these like "Miss".
Address = this is the street address belonging to the member name.
city = this is the name of the city to which the address belongs to.
Country = this is the name of the country to which the address belongs to. Normalize all names to English.
Geo = these are the geographical coordinates that you need to calculate from the Address. These include latitude and longitude. The format should be "XX,XX", meaning "latitude,longitude".
Phone = this is the phone number associated with the member name. If it does not contain the country code (+XX) add the respective country code based on country of member. Pay attention to which country we are dealing with and substitute the leading 0 by the country code when appropriate.
WebSite = this is the web address associated with the member name.
```
5. Click anywhere in the **Click to upload** area to open the file explorer.
6. Select the [society\_members\_web.txt](https://docs.cdn.datalinks.com/docs/quickstart-web/society_list_web.txt) file you downloaded, then click **Open**.
7. Wait while DataLinks ingests your data.
8. Click the **eye icon** under Ingestion Results to review the data and confirm that everything looks correct.
9. Scroll down to the bottom of the page, then click **Upload data**.
10. Click **Append** to confirm.
## Part 5: Manage connections
DataLinks does not establish connections by default. Adding connections between your datasets is an important step in maximizing the quality of query and analytics results. To add connections, follow the steps below:
1. Return to **Home** using the left menu.
2. Click the **customer\_list** dataset in the `dental_prospects_web` namespace.
3. Click the **Connections** tab.
4. Click the row containing `customer_list.full_name` and `society_members.membername`.
5. Click **Add connections**.
You've now connected the names in your customer list to the names in the society members list.
## Part 6: Try your first query
Once your data is connected, you are ready to explore it in DataLinks and turn it into meaningful insights. In this example, you will identify customers from your`customer_list `who are also members of the professional society you want to target.
Follow these steps to run your first query:
1. Click **Connections Overview** in the left menu.
2. Ensure the namespace selector in the top-right corner of your screen is set to **dental\_prospects\_web**.
3. Click the **left arrow icon** to expand the chat window.
4. In the chatbox, submit a prompt such as: `Find people associated with my customer Andrew Williams, and let me know why I should engage with them.`
After reviewing the response, experiment with your own questions. The DataLinks chatbot uses your connected data to generate contextual, well-reasoned answers that help you uncover new opportunities.
## Next steps
Congratulations! You've used structured and unstructured data to find a list of prospects and query your data.
Thank you for taking the time to try this guide and explore DataLinks.
To learn more about DataLinks and how we can help your organization, get started by joining the [DataLinks waitlist](https://datalinks.com/newsletter).
# DataLinks API/SDK QuickStart Guide
Source: https://docs.landfall.ai/get-started/quickstart-api
Learn how to ingest data, view connections, and query using both the DataLinks web platform and API.
## Introduction
This guide shows you how to use the DataLinks web platform and SDK kit to find shared sales opportunities across two North American data sources. You will learn by doing: uploading files, connecting records, and exploring the results directly in the platform.
This guide uses both the web platform and the DataLinks SDK kit. If you'd like a simpler introduction to DataLinks that only uses the web platform, you can try our [Web QuickStart guide](/get-started/quickstart) instead.
## Scenario
You're a sales manager for a dental products company in North America. Your goal is to see how your existing customer base overlaps with members of a major dental society to uncover new prospecting opportunities.
You'll work with two datasets:
* A JSON file that contains your current customers, including names, locations, and contact details
* A dental society member list that includes dentists and clinic owners across the United States and Canada
Using the DataLinks web platform, you'll ingest both datasets, manage how they connect, and query the linked data to find customers who are also society members or have professional ties to them.
## Outcome
By the end of this guide, you will have a table showing which of your current customers also appear in the dental society list. You can explore these matches in the DataLinks platform or export them as a CSV file for follow-up.
This exercise helps you practice the full workflow in DataLinks: importing data, linking records, exploring relationships, and using the insights for focused outreach.
## Prerequisites
To follow along on your machine and complete the steps in this guide, make sure you have the following ready:
1. Access to a DataLinks account.
Don't have an account? Contact [support@datalinks.com](support@datalinks.com) to request one.
2. Download a copy of [quickstart\_api.zip](https://docs.cdn.datalinks.com/docs/quickstart_api.zip) to your local machine and extract it.
3. Python installed on your local machine and the package-manager pip available. To download Python, see [the official website](https://www.python.org/downloads/).
4. The DataLinks SDK installed on your machine.
For installation instructions, see the official [DataLinks SDK Installation Steps](https://docs.datalinks.com/pythonsdk/sdk-install).
5. A code editor such as Microsoft Visual Studio Code, which is [free to download on Windows, Linux, or Mac](https://code.visualstudio.com/download).
## Part 1: Create your namespace and first dataset
1. Log in to the DataLinks platform.
2. Click **Create New Dataset**.
3. Enter the following dataset name: `customer_list`.
Use underscores, not spaces, when naming your datasets.
4. Enter namespace `dental_prospects_api`.
5. Ensure the Dataset visibility is set to **Private** using the toggle.
6. Click **Create dataset and upload data**.
Result: You've created an empty `customer_list` dataset inside the `dental_prospects_api` namespace.
## Part 2: Upload the structured customer list
Now that you've created an empty dataset, you need to populate it with data. To do so, follow these steps:
1. Click anywhere in the **Click to upload** area to open the file explorer.
2. Select **customer\_list.json** from the `quickstart_api.zip` folder you downloaded, then click **Open**.
3. Wait while DataLinks ingests your data.
4. Click the **eye icon** under Ingestion Results to review the data and confirm that everything looks correct.
5. Scroll down to the bottom of the page, then click **Upload data**.
6. Click **Append** to confirm.
Result: You've now populated your `customer_list` dataset with data.
## Part 3: Create your DataLinks API token.
1. Log in to the DataLinks platform.
2. Click **Settings** in the left navigation menu.
3. Click **+ Add a new** token.
4. Enter the name `dental_prospects` and set an expiration date for the token (for example, one month).
5. Click **+ Create token**.
6. Copy and save your token. (You’ll need in later steps.)
7. Click **I’ve copied the token** to close.
## Part 4: Use a script to upload and transform unstructured data
For the structured data you uploaded through the DataLinks web platform, you first created a dataset (Part 1) and then uploaded data into it (Part 2). For the unstructured data in this section, you’ll create and populate the dataset at the same time using a Python script.
The unstructured data file you’ll use is a simple text file created by copying and pasting a list of dental society members into a text file. This is the `society_members_api.txt`file you downloaded `quickstart_api.zip` folder you downloaded in the Prerequisites section.
To upload and transform your data, follow the steps below:
1. Open the `ingest_dental.py`file in your code editor.
2. Add the following information to the file:
* On line 17, `apikey`, paste in your API token.
* On line 19, `namespace,` enter the namespace you're using (`dental_prospects_api`)
* On line 20, `objectname`, enter the name for the new dataset you're creating (`society_members`)
* On line 26, `textfile`, enter `society_members_api.txt` and update the location if needed based on where you've placed the file in your directory.
* On line 49, `helper_prompt`, paste in the following prompt:
```
This is the data from a dental societies where the names of members (dental practices), the addresses, the phone numbers, and the websites, are included. The file contains several entries.
# **Instructions**
* Extract **all data** provided.
* **Do not skip any entry.**
Whenever possible abide to the following schema:
MemberName = this is the set of names that are used to define the member entity, which may be a person, or may be an entity like and institution, business, etc. Do the import without academic titles such as "Dr." for Doctor, "Prof." for professor and similar, and without personal honorifics like "Ms.", "Mr.", "Frau", "Herr", and extensions of these like "Miss".
Address = this is the street address belonging to the member name.
city = this is the name of the city to which the address belongs to.
Country = this is the name of the country to which the address belongs to. Normalize all names to English.
Geo = these are the geographical coordinates that you need to calculate from the Address. These include latitude and longitude. The format should be "XX,XX", meaning "latitude,longitude".
Phone = this is the phone number associated with the member name. If it does not contain the country code (+XX) add the respective country code based on country of member. Pay attention to which country we are dealing with and substitute the leading 0 by the country code when appropriate.
WebSite = this is the web address associated with the member name.
```
3. Verify everything looks correct, then save your file.
4. Run your Python program. (In Visual Studio Code, press the **play icon** in the top-right corner of your screen.)
5. Wait for the data to finish uploading. This may take 10-20 minutes.
## Part 5: Manage connections
By default, no connections are established when you create datasets. Creating proper connections between datasets significantly increases the quality of query responses and insights into your data. To manage connections, follow these steps:
1. Open the DataLinks web platform again and return to the **Home** page.
2. Click the **customer\_list** dataset in the `dental_prospects_api` namespace.
3. Click the **Connections** tab.
4. Click **Select all** under active Active Links. This will select all active links and ready them for removal.
5. Click **Save connections**. This will remove all active links.
6. Click the row containing `customer_list.full_name` and `society_members.membername`.
7. Click **Add connections**.
## Part 6: Try your first query
Once your data is connected, you are ready to explore it in DataLinks and turn it into meaningful insights. In this example, you will identify customers from your`customer_list `who are also members of the professional society you want to target.
Follow these steps to run your first query:
1. Click **Connections Overview** in the left menu.
2. Ensure the namespace selector in the top-right corner of your screen is set to **dental\_prospects\_api**.
3. Click the **left arrow icon** to expand the chat window.
4. In the chatbox, submit a prompt such as: `Find people associated with my customer Andrew Williams, and let me know why I should engage with them.`
After reviewing the response, experiment with your own questions. The DataLinks chatbot uses your connected data to generate contextual, well-reasoned answers that help you uncover new opportunities.
## Next steps
Congratulations! You've used structured and unstructured data to find a list of prospects.
Thank you for taking the time to try this guide and explore DataLinks.
To learn more about DataLinks and how we can help your organization, get started by joining the [DataLinks waitlist](https://datalinks.com/newsletter).
# How to Connect DataLinks to a Third-Party Web App Builder
Source: https://docs.landfall.ai/how-to/connect-app-builder
Learn how to connect DataLinks to your preferred web app builder.
This guide shows how to connect a third-party web app builder (e.g. Lovable, Replit, Vercel v0) to DataLinks using a starter prompt and the DataLinks API. The same approach works for chat apps, dashboards, internal tools, and AI assistants.
### What You Will Need
Before you start, make sure you have the following:
* A DataLinks API key
* Your namespace name
* One or more dataset names inside that namespace
* A web app builder that supports API calls or server-side functions
## Step 1: Create a Clear Starter Prompt
Most app builders allow you to define an initial system or starter prompt. This prompt tells the app how it should behave and what data it can access.
Example starter prompt:
```
Create a simple chatbox interface that connects to the and datasets within the namespace. The chatbox should perform AutoRAG queries on the data in DataLinks when queried.
```
Replace the placeholders with your real namespace and dataset names.
Tips for a good prompt:
* Clearly name the namespace and datasets
* State whether the app should use structured queries, natural language queries, or AutoRAG
* Describe the expected output format, such as a text response or table
***
## Step 2: Configure the API Connection
In your web app builder, create a new API connection or backend function with the following settings.
### Base URL
```
https://api.datalinks.com/api/v1
```
### Authentication Header
All requests must include this header:
```
Authorization: Bearer
```
No other headers are required. All requests and responses use JSON.
## Step 3: Choose the Right Endpoint
Use the endpoint that matches how your app needs to interact with the data.
### Option A: Structured or Natural Language Queries
Endpoint:
```
POST /query
```
Use this when the app needs to query a specific dataset.
Example request body for a natural language query:
```
{
"username": "self",
"namespace": "",
"dataset": "",
"naturalLanguageQuery": "Show me the latest 50 records"
}
```
Example response:
```
{
"data": [
{ "field1": "value", "field2": 123, "field3": true }
]
}
```
***
### Option B: AutoRAG Question Answering
Endpoint:
```
POST /query/autorag
```
Use this for open-ended questions that require retrieval and reasoning across datasets.
Example request body:
```
{
"username": "self",
"namespace": "",
"query": "What were our top 3 entities last month?"
}
```
Example response:
```
{
"response": "The top three entities last month were A, B, and C.",
"steps": [
{ "action": "retrieval", "details": {} },
{ "action": "llm_call", "details": {} }
]
}
```
Client behavior guidelines:
* If reasoning is disabled, return only the response string
* If reasoning is enabled, return both the response and the steps
## Step 4: Display the Results in Your App
How you display results depends on your app builder:
* For chat interfaces, render the response text directly
* For dataset queries, loop over the `data` array and show rows in a table or list
* Optionally hide fields that start with `_`, since these are system metadata
## Step 5: Enable Dataset Discovery (Optional)
To help users explore available datasets, you can add discovery features.
List all datasets:
```
GET /data
```
List datasets in a namespace:
```
GET /data/{namespace}
```
Read raw dataset rows:
```
GET /data/self/{namespace}/{objectname}
```
These endpoints are useful for dropdowns, dataset pickers, or admin views.
## Example all-in-one prompt
Some app builders support placing all of the configuration details into a single initial prompt. This approach allows you to set up an external chatbot in one step, followed by a prompt to enter your API token value when required.
Below is an example prompt you can use with Lovable to create the chatbot in a single attempt after filling in your details:
```
Enter your starter prompt here, for example, "Create a simple chatbox interface that connects to the and datasets within the namespace. The chatbox should perform AutoRAG queries on the data in DataLinks when queried."
Here is connection info:
DATALINKS API — CONNECTION INSTRUCTIONS
Base URL https://api.datalinks.com/api/v1
Authentication All requests require a single HTTP header: Authorization: Bearer
No additional headers are required.
All requests and responses use JSON.
AVAILABLE ENDPOINTS
See https://docs.datalinks.com/api-reference for all available endpoints, some examples are included below:
Structured Data Query POST /query
Use this to run explicit or ontology-based queries against a dataset.
Request body: { "username": "self", "namespace": "", "dataset": "", "query": "Ontology(/)" }
Typical response: { "data": [ { "field1": "value", "field2": 123, "field3": true } ] }
Natural Language Data Query POST /query
Use this to ask questions in plain English against a dataset.
Request body: { "username": "self", "namespace": "", "dataset": "", "naturalLanguageQuery": "Show me the latest 50 records" }
Response format is identical to structured queries: { "data": [ ...records... ] }
AutoRAG Question Answering POST /query/autorag
Use this for open-ended questions that require retrieval plus reasoning.
Request body: { "username": "self", "namespace": "", "query": "What were our top 3 entities last month?" }
Response: { "response": "", "steps": [ { "action": "retrieval", "details": {} }, { "action": "llm_call", "details": {} } ] }
Client behavior:
If reasoning is disabled, return only the response string
If reasoning is enabled, return (response, steps)
Read Dataset Rows GET /data/self/{namespace}/{objectname}
Returns raw rows for the specified dataset.
Response: { "data": [ ...records... ] }
Discover Available Datasets GET /data GET /data/{namespace}
Returns dataset listings and metadata for discovery.
METADATA NOTES
Fields starting with "_" are system-generated metadata. Client helpers may optionally filter these fields out.
SUMMARY
POST /query → structured or natural language dataset queries POST /query/autorag → AutoRAG question answering GET /data → dataset discovery GET /data/self/... → dataset row access
```
## Summary
* Use a clear starter prompt to define app behavior
* Connect using the DataLinks API base URL and Bearer token
* Choose `/query` for dataset queries and `/query/autorag` for reasoning-based answers
* Render responses based on your app’s UI pattern
# How To Create a New Dataset
Source: https://docs.landfall.ai/how-to/create-dataset
Create a new dataset using the DataLinks web platform or API.
Datasets are the foundation of how DataLinks organizes and understands your information. Each dataset represents a structured table of data that can later be cleaned, connected, and queried by AI.
If you’re new to DataLinks, you may want to start with the explanatory article [Datasets and Namespaces](/concepts/datasets-namespaces) to learn how datasets fit within namespaces and the broader DataLinks structure.
## Create a dataset using the API
This sections shows how to create a new dataset in DataLinks by calling the REST API directly from Python.
You’ll create a dataset named `employee_records` inside a namespace called `hr_demo`, using the DataLinks **Create new dataset** API endpoint.
### Prerequisites
Before you begin, make sure you have:
* A DataLinks account
* A valid bearer token for the DataLinks API
* A Python environment set up for running scripts
* The ability to install Python packages
You can use any project structure or environment setup that fits your workflow.
### Step-by-step instructions
This guide uses direct HTTP calls with Python. Install the `requests` library if you do not already have it available:
```
pip install requests
```
or
```
pip3 install requests
```
Set the following environment variables so sensitive values are not hardcoded in your script:
**For the current session**
```
$env:DATALINKS_TOKEN="YOUR_BEARER_TOKEN"
$env:DATALINKS_NAMESPACE="hr_demo"
$env:DATALINKS_DATASET="employee_records"
```
**Persist across future sessions**
```
setx DATALINKS_TOKEN "YOUR_BEARER_TOKEN"
setx DATALINKS_NAMESPACE "hr_demo"
setx DATALINKS_DATASET "employee_records"
```
After using `setx`, open a new PowerShell window before running your script.
```
export DATALINKS_TOKEN="YOUR_BEARER_TOKEN"
export DATALINKS_NAMESPACE="hr_demo"
export DATALINKS_DATASET="employee_records"
```
These apply to the current shell session. Add them to `~/.bashrc` or `~/.zshrc` if you want them to persist automatically.
```
export DATALINKS_TOKEN="YOUR_BEARER_TOKEN"
export DATALINKS_NAMESPACE="hr_demo"
export DATALINKS_DATASET="employee_records"
```
These apply to the current terminal session. For persistence, place them in `~/.zshrc`.
## Create the dataset (Python)
Create a Python file, for example `create_dataset.py`, and add the following code:
```
import os
import json
import requests
BASE_URL = "https://api.datalinks.com/api/v1"
token = os.environ["DATALINKS_TOKEN"]
namespace = os.environ["DATALINKS_NAMESPACE"]
dataset_name = os.environ["DATALINKS_DATASET"]
url = f"{BASE_URL}/ingest/new/{namespace}/{dataset_name}"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
payload = {
# Required. Options: "Public" or "Private"
"visibility": "Private",
# Optional but recommended
"inferDefinition": {
"dataDescription": "Employee dataset containing demographics and compensation.",
"fieldDefinition": (
"id=unique employee identifier\n"
"name=full employee name\n"
"age=employee age in years\n"
"department=work department\n"
"salary=annual salary in USD\n"
),
},
}
response = requests.post(
url,
headers=headers,
data=json.dumps(payload),
timeout=30,
)
print("Status:", response.status_code)
if response.headers.get("content-type", "").startswith("application/json"):
print(json.dumps(response.json(), indent=2))
else:
print(response.text)
response.raise_for_status()
print("\nDataset created successfully.")
```
Run the script using your normal Python workflow:
```
python create_dataset.py
```
### How this works
This request:
* Calls `POST /ingest/new/{namespace}/{datasetName}` to create a dataset
* Authenticates using a bearer token in the `Authorization` header
* Sets dataset visibility to `Private`
* Supplies optional metadata to help DataLinks understand the dataset structure
The `inferDefinition` section is optional, though it is strongly recommended when creating datasets programmatically.
### Common issues
**Authentication errors**
If the request fails with a 401 error, confirm that:
* The token environment variable is set correctly
* The `Authorization` header uses the `Bearer` format
**Dataset naming errors**
If you see a 400 or 404 error, check that:
* The namespace and dataset name appear in the request URL
* The dataset name follows standard naming conventions, such as lowercase letters and underscores
## Create a dataset in the web platform
If you prefer a simple, guided setup, you can create a dataset directly in the DataLinks web platform.
To create a dataset in the web platform, follow these steps:
1. **Log in** to your DataLinks account.
2. In the left navigation menu, click **Create a New Dataset**.
3. **Enter a name** for your dataset. Choose something descriptive, such as `customer_transactions_2025`
Use underscores, not spaces, when creating names.
4. **Select or enter a namespace** to assign the dataset to. The namespace helps organize related datasets and controls access and context. For example, `accounting_2025`
5. Use the toggle to set visibility to Private or Public.
6. Click **Create dataset and upload data** to finish.
Your dataset will appear in your list of available datasets and will be ready for ingestion and cleaning.
## Next steps
Once your dataset exists, you can ingest data into it using the data ingestion endpoints. From there, you can explore schema inference, enrichment, and querying workflows in DataLinks.
# How To Create a New Namespace
Source: https://docs.landfall.ai/how-to/create-namespace
A namespace is a container that helps organize related datasets within DataLinks. Think of it as a workspace or project folder where your datasets live and connect to one another.
Namespaces help keep your data organized, secure, and contextually grouped for easier querying and AI analysis.
If you’re new to this concept, read the explanatory article Datasets and Namespaces to see how they work together.
## Create a namespace in the web platform
Namespaces cannot be created on their own. A new namespace is automatically created when you create your first dataset within it.
To create a namespace:
1. **Log in** to your DataLinks account.
2. In the left navigation menu, click **Create a New Dataset**.
3. Enter a name for your dataset.
4. In the **Namespace field**, type the name of the new namespace you’d like to create.
* If the namespace does not already exist, DataLinks will create it automatically.
* If it already exists, the dataset will be added to that namespace instead.
5. Click **Create dataset and upload data** to finish.
Your new namespace will now appear in the list on your homepage, containing the dataset you just created.
## Create a namespace using the API or SDK
Namespaces are created through dataset creation, not as standalone objects. When you create a dataset with a new namespace name, DataLinks automatically provisions that namespace.
**To create via API:**
Use the **POST Create New Dataset** endpoint: [POST Create new dataset](/api-reference/ingest/create-new-dataset)
In your request body, include a field such as: `"namespace": "your_namespace_name"`
If the namespace does not already exist, it will be created during this request.
**To confirm creation:**
Use [GET List user namespaces](/api-reference/schema/list-user-namespaces) to view your namespaces:
**With the SDK:**
You can achieve the same effect by calling the dataset creation method and passing a new namespace name in your parameters. The SDK wraps the API behavior and is ideal for scripting or integrating namespace creation into automated data pipelines.
## Manage namespaces
* You cannot create an empty namespace. Every namespace must contain at least one dataset.
* You cannot delete a namespace that still contains datasets. To remove a namespace, first delete all datasets within it.
* Once all datasets in a namespace are deleted, the namespace will no longer appear on your homepage.
## Best practices
* Use clear, descriptive names for namespaces (for example: `Finance_2025`, `Customer_Data`, or `Compliance_Reports`).
* Group datasets by business domain, department, or project to make navigation and querying easier.
* Keep in mind that datasets within the same namespace can connect and share context more easily.
## Next Steps
After you’ve created your first namespace:
* You can add more datasets to it at any time.
* Begin ingesting data using the [Ingest Data API](/api-reference/ingest/ingest-data) or directly through the web platform.
* Use the Connections view to see how datasets in your namespace link together through DataLinks’ Knowledge Map.
Creating a namespace is your starting point for turning fragmented data into a structured, connected foundation for AI.
# How to Get a DataLinks API Token
Source: https://docs.landfall.ai/how-to/get-api-token
Connect third-party applications to DataLinks using a token.
Getting an API token is the first step toward connecting your systems and applications to DataLinks. Your token lets you authenticate securely, access your workspace, and begin sending data to be structured, linked, and enriched.
Follow the steps below to find your token and how to copy it safely so you can start integrating with the platform.
## How to create a new token
1. Log in to the DataLinks platform.
2. Click **Settings** in the left navigation menu.
3. Click the **Tokens & API** tab.
4. Click **+ Add a new** token.
5. Enter your desired token name and set an expiration date (for example, one month).
Your connected app will stop working when the token expires. Make sure you have enough time to renew it or plan to update the token details before the expiry date.
6. Click **+ Create token**.
7. Copy and save your token.
You can only view your token information during creation. Save a copy in a secure place so you can access it later. Otherwise, you will need to create a new token.
8. Click **I’ve copied the token** to close.
## How to Delete a Token
On the **Tokens & API** tab of the **Settings** page, you can click the red trash icon in a token's row to delete it, as shown in the screenshot below:
# How To View and Manage Connections
Source: https://docs.landfall.ai/how-to/manage-connections
Connections are what make DataLinks powerful. They turn isolated datasets into a web of related knowledge that both humans and AI can reason over. Each connection represents a link between entities or values that share a relationship across datasets.
Viewing and managing these links helps you understand how your data interacts and ensures that your knowledge map stays accurate.
## View all connections in the Connections Overview
The Connections Overview provides a top-level visualization of your knowledge map, helping you see how information flows across datasets. Public datasets will appear only if there is at least one shared or matching entity between your data and theirs.
To see the full picture of how your datasets connect:
1. Log in to your DataLinks account.
2. In the left navigation menu, click **Connections Overview**.
3. This opens a map view showing all of your private datasets, along with any public datasets that have at least one connection to them.
When working with sensitive data, remember that connections to public datasets are visible only if an overlap exists, and your private data remains private and is not visible to others.
## View connections for a specific dataset
To focus on the relationships of one dataset:
1. Click the **dataset name** from your homepage or the navigation list.
2. Click the **Connections** tab at the top of the page.
3. You will see two lists on the right side of your screen:
* **Active links:** connections currently used when querying or traversing data.
* **Suggested links:** potential matches identified during ingestion but not yet confirmed.
By default, all connections are inactive.
Each connection listed shows the dataset it links to, the fields involved, and the number of matching records (the “score”).
Connections may include both **private datasets you own** and **public datasets** that share related entities.
## Manage connections in the web platform
You can move links between **Active** and **Suggested** states to control which connections DataLinks uses when building queries or expanding results.
**To deactivate a link:**
1. In the Active Links table, **click the rows** you want to remove.
2. Selected rows turn red and display a minus sign at the end.
3. Click **Save Connections**.
* The selected links will move to the Suggested Links table.
**To activate a link:**
1. In the Suggested Links table, **click the rows** you want to add.
2. Selected rows turn green and show a plus icon at the end.
3. Click **Save Connections**.
* The selected links will move to the **Active Links** table and become part of your working data model.
This process lets you fine-tune which relationships are included in your namespace’s knowledge map.
## Manage connections through the API or SDK
You can also view and manage connections programmatically using the API or SDK.
* [**GET Load Links**](/api-reference/links/load-links)
Retrieves the existing connections (both active and suggested).
* [**POST Rebuild Links**](/api-reference/links/rebuild-links)
Regenerates suggested links for a dataset or namespace based on current data.
* [**POST Preview Links**](/api-reference/links/preview-links)
Previews how new potential links would appear before committing them.
* [**POST Add a New Link**](/api-reference/links/add-a-new-link)
Adds a specific manual connection between datasets (for example, if you know two fields correspond but were not automatically matched).
These endpoints are ideal for teams that maintain automated pipelines or want to validate and manage links at scale.
You can also use the **DataLinks SDK** to interact with these APIs through scripts, integrating connection management into your workflows.
## Best practices
* Regularly review **Suggested Links** to confirm or remove potential matches.
* Keep **Active Links** focused on meaningful relationships that enhance analysis or AI reasoning.
* When working with the SDK, use the **Rebuild Links** API periodically to refresh suggestions as your data evolves.
# How To Query Your Data
Source: https://docs.landfall.ai/how-to/query-data
Querying in DataLinks lets you turn cleaned, connected datasets into clear answers. You can query through the API or SDK for automation at scale, or use the web platform for an interactive experience.
If you are new to the concepts behind querying in DataLinks, start with: [Ingest](/concepts/ingest-clean), [Interconnect](/concepts/interconnection), and [Inquire](/concepts/query-insights).
## Before you begin
* You have at least one dataset created and ingested with data.
* You know which namespace your dataset belongs to.
* Optional but helpful: You understand that active links can allow queries to traverse to related datasets.
## Query with the API or SDK
Use this route for production workflows, scheduled jobs, or large result sets.
* **API endpoint:** **POST Query data**
See: `Query data` in the API reference.
* **What it does:** accepts a structured request, runs the query across your dataset or namespace, and returns JSON.
* **Using the SDK:** the SDK wraps the same API and is the simplest way to integrate natural language to query translation inside scripts or services.
* **Why choose this:** best for automation, reproducibility, and integrating results into dashboards or applications.
## Query in the Web Platform
Use this route for fast exploration and team demos.
1. Log in to your DataLinks account.
2. Open the dataset you want to explore.
3. Go to the **Preview & Query** tab.
4. On the right panel, in **Query your data**, choose one of two modes:
* **Natural language:** type a plain English question. DataLinks uses an LLM to translate your request into a structured query, then runs it against real data for grounded results.
* **Query language:** click the **Query** tab to write or paste a structured query. The syntax is designed to be readable and close to English.
Learn more about [Query Language commands](/reference/query_commands).
5. Run the query and review the results in the table view.
### Tips
* If results look wider than expected, check if your dataset has active links that allow access to related datasets.
* To refine results, add filters or select specific fields in the Query tab.
* If you have applied a previous query, clear or remove it before running a new one to avoid confusion.
## How DataLinks keeps answers grounded
* Natural language is translated to a structured query, which is executed on your actual data.
* Results are always subsets of your stored datasets.
* This approach reduces hallucination risk because the model does not invent rows or fields.
# How To Share a Dataset or Namespace
Source: https://docs.landfall.ai/how-to/share-data
Grant another DataLinks user read or write access to your data.
By default every dataset you create is private and visible only to you. When you want a teammate to [query your data](/how-to/query-data), or help maintain it, you can share access with them directly.
Sharing is separate from a dataset's public or private visibility, so you can grant access without changing who else can see the data.
You can share at two levels:
* **Share a dataset** to grant access to a single dataset.
* **Share a namespace** to grant access to every dataset inside it. The grant cascades, so any new datasets you add to that namespace later are covered too.
If you're new to how data is organized, read [Datasets and Namespaces](/concepts/datasets-namespaces) first to see how the two fit together.
## Access roles
Every share grants one of two roles to one user:
| Role | What it allows |
| - | - |
| `viewer` | Read-only. The user can list, read, and [query](/how-to/query-data) the data. |
| `editor` | Read and write. The user can do everything a viewer can, plus ingest, update, and modify the data. |
Only the **owner** of a dataset or namespace can share it, list who it's shared with, or revoke access. A viewer or editor can't re-share your data with anyone else.
## Sharing and visibility are independent
Visibility (public or private) and sharing (viewer or editor grants) are two separate controls, and they continue to work exactly as before:
* **Visibility controls who can read.** A private dataset is readable only by its owner and the users it's shared with. A public dataset is readable by everyone on the platform.
* **Sharing grants named users extra access on top of that.** A `viewer` grant adds read access (most useful on a private dataset), and an `editor` grant adds write access.
Because they're independent, an `editor` grant lets a specific user **append to a dataset even when it's public**. Public still means everyone can read it, but only the owner and the users you've granted `editor` can write to it. Everyone else gets a `403` if they try to append.
## Before you begin
* You have an [API token](/how-to/get-api-token) for the account that **owns** the data.
* You've already [created the dataset](/how-to/create-dataset) or [namespace](/how-to/create-namespace) you want to share.
* You know the owner's username, the namespace, and (for dataset sharing) the dataset name.
* You know the **username** of the person you're sharing with. They must already have a DataLinks account, and the role must be either `viewer` or `editor`.
All examples below use the API base URL `https://api.datalinks.com/api/v1` and send your token as a Bearer header.
## Share a dataset
Grant a user access to one dataset with the **Share a dataset** endpoint.
```bash theme={null}
curl -X POST \
"https://api.datalinks.com/api/v1/permissions/datasets/{owner}/{namespace}/{datasetName}/shares" \
-H "Authorization: Bearer $DATALINKS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username": "alice", "role": "viewer"}'
```
A successful request returns `200 OK` with an empty body. The grantee can now read and query the dataset using the **owner's** username in the path, for example `GET /data/{owner}/{namespace}/{datasetName}`.
See [Share a dataset](/api-reference/permissions/share-a-dataset) in the API reference.
### List who a dataset is shared with
```bash theme={null}
curl "https://api.datalinks.com/api/v1/permissions/datasets/{owner}/{namespace}/{datasetName}/shares" \
-H "Authorization: Bearer $DATALINKS_TOKEN"
```
The response is an array of the current grants:
```json theme={null}
[
{ "username": "alice", "role": "viewer" }
]
```
See [List dataset shares](/api-reference/permissions/list-dataset-shares).
### Revoke access to a dataset
To remove someone's access, send a `DELETE` with the same body you used to grant it.
```bash theme={null}
curl -X DELETE \
"https://api.datalinks.com/api/v1/permissions/datasets/{owner}/{namespace}/{datasetName}/shares" \
-H "Authorization: Bearer $DATALINKS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username": "alice", "role": "viewer"}'
```
See [Revoke dataset access](/api-reference/permissions/revoke-dataset-access).
## Share a namespace
Sharing a namespace works the same way, but the grant applies to **every dataset in the namespace** and to any datasets you add to it later. This is the easiest way to give a teammate access to a whole project.
```bash theme={null}
curl -X POST \
"https://api.datalinks.com/api/v1/permissions/namespaces/{owner}/{namespace}/shares" \
-H "Authorization: Bearer $DATALINKS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username": "alice", "role": "editor"}'
```
See [Share a namespace](/api-reference/permissions/share-a-namespace).
### List and revoke namespace access
Listing and revoking mirror the dataset endpoints:
```bash theme={null}
# List who the namespace is shared with
curl "https://api.datalinks.com/api/v1/permissions/namespaces/{owner}/{namespace}/shares" \
-H "Authorization: Bearer $DATALINKS_TOKEN"
# Revoke access to the whole namespace
curl -X DELETE \
"https://api.datalinks.com/api/v1/permissions/namespaces/{owner}/{namespace}/shares" \
-H "Authorization: Bearer $DATALINKS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username": "alice", "role": "editor"}'
```
See [List namespace shares](/api-reference/permissions/list-namespace-shares) and [Revoke namespace access](/api-reference/permissions/revoke-namespace-access).
## How a grantee uses shared data
Once you've shared with someone, they access the data under **your** username, not theirs. A user that you've granted access reads and [queries](/how-to/query-data) your dataset at `GET /data/{owner}/{namespace}/{datasetName}`, and it shows up when they list the datasets in that namespace. If your datasets are connected, they can also traverse those [connections](/how-to/manage-connections) just as you can.
The plain ingest endpoint (`POST /ingest/{namespace}/{datasetName}`) writes to the **caller's** own namespaces, so an editor can't use it to write into your data. To write to data shared with them, an editor uses the endpoints that include the owner's username in the path, such as the multipart upload endpoints under `/upload/multipart/{owner}/{namespace}/{datasetName}/...`.
## Things to know
Access changes are eventually consistent. After you grant or revoke, it can take a few seconds for the change to take effect. If a grantee still sees `404` right after you share, have them retry shortly.
* **Owner only.** If someone who isn't the owner tries to share, list, or revoke, the API returns `403 Forbidden` with the message that access can be managed by the owner only.
* **The grantee must exist.** Sharing with a username that has no DataLinks account is rejected. Double-check the spelling. Usernames are matched in lowercase.
* **Roles are fixed values.** `role` must be exactly `viewer` or `editor`. Any other value returns `422`.
* **Revoke matches on the user.** Removing a grant takes the user's access away regardless of which role you name in the body.
* **Sharing and visibility are separate.** Making a dataset public exposes it to everyone for reading; sharing grants a specific user `viewer` or `editor` access. The two combine: for example, `editor` on a public dataset lets one named user append data while everyone else can still only read.
## Best practices
* Grant the **least access needed**. Use `viewer` for teammates who only need to read or query, and reserve `editor` for people who maintain the data.
* Prefer **namespace sharing** for project-based collaboration so new datasets are covered automatically, and **dataset sharing** when you need to scope access tightly.
* **Audit periodically.** List the grants on your important datasets and namespaces and revoke access that's no longer needed.
# How to Use AutoRAG to query data
Source: https://docs.landfall.ai/how-to/use-autorag
AutoRAG is DataLinks' retrieval-augmented generation agent. It answers natural language questions by searching across all datasets in a namespace, retrieving relevant data, and synthesizing a response using an LLM.
You can use AutoRAG in two ways: through the DataLinks web platform, or programmatically via the API.
## Prerequisites
* A DataLinks account with an API token
* At least one dataset with data ingested into a namespace
* Your username, namespace name, and token ready
## Using AutoRAG in the Web Platform
The simplest way to use AutoRAG is through the DataLinks dashboard. To open the AutoRAG chatbot, follow the steps below:
1. Log in to your DataLinks account.
2. Click **Connections Overview** in the left sidebar.
3. Select the namespace you want to query from the **Namespace in Use** dropdown in the top right corner.
4. (Optional) Click the three dots at the top of the chatbot, then click **Update helper prompt**, enter your prompt, then click **Update prompt**.
Type a natural language question into the "Ask a question about your data..." input at the bottom and click send. AutoRAG will search across all datasets in the selected namespace and return an answer directly in the chat panel.
This is ideal for ad-hoc exploration, verifying that your data is queryable, and testing questions before building them into an automated pipeline.
## Using AutoRAG via the API
### Quick Start (Python)
```python theme={null}
import requests
API_BASE = "https://api.datalinks.com/api/v1"
TOKEN = "your-api-token"
response = requests.post(
f"{API_BASE}/query/autorag",
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
json={
"username": "your-username",
"namespace": "your-namespace",
"query": "Which supplier serves both European and Asian operations?"
},
timeout=120
)
result = response.json()
print(result["response"])
```
### API Reference
**Endpoint:** `POST /query/autorag`
| Parameter | Type | Required | Description |
| - | - | - | - |
| `username` | string | Yes | Your DataLinks username |
| `namespace` | string | Yes | Namespace to search across |
| `query` | string | Yes | Natural language question |
| `helperPrompt` | string | No | System-level instruction to guide the LLM's behavior |
| `model` | string | No | LLM model to use (e.g., `gemini-3-flash-preview`) |
| `provider` | string | No | Model provider (e.g., `gcloud`, `openai`) |
**Response:**
```json theme={null}
{
"response": "Acme Corp serves both European and Asian operations.",
"steps": [
{
"instruction": "Search for suppliers operating in Europe",
"query": "Ontology(\"suppliers\").filter(region == \"Europe\")",
"data": [{"name": "Acme Corp", "region": "Europe"}]
},
{
"instruction": "Check which of these also operate in Asia",
"query": "Ontology(\"suppliers\").filter(name == \"Acme Corp\", region == \"Asia\")",
"data": [{"name": "Acme Corp", "region": "Asia"}]
}
]
}
```
The `steps` array shows AutoRAG's reasoning chain: what it searched for, the query it generated, and the data it retrieved at each step.
**Streaming:** `POST /query/ask` returns Server-Sent Events for real-time progress. Event types are `plan` (the query plan), `step` (each retrieval step as it executes), `answer` (the final response), and `error` (if something goes wrong). Use streaming for user-facing applications where you want to show progress rather than waiting for the full response.
## How AutoRAG Works
When you submit a query, AutoRAG first interprets your question to identify the relevant entities and relationships it needs to find. It then retrieves data by searching across all datasets in your namespace using semantic search, following any links you have set up between datasets to discover related information. Finally, it synthesizes a response grounded in the actual data it retrieved, ensuring the answer is anchored to real records rather than hallucinated.
AutoRAG searches **all datasets in a namespace**, not just one. You can split data across multiple datasets and it will search them all.
## Preparing Data for AutoRAG
AutoRAG works best with simple, well-described datasets. Keep your schemas narrow wherever possible. Use descriptive column names like `article_title` rather than `col1` so AutoRAG understands what each field contains. If possible, include at least one text-rich column with substantial content like paragraphs or descriptions, because semantic search needs meaningful text to match against.
When creating a dataset, provide clear data descriptions and field definitions wherever possible so AutoRAG has context about what your data represents.
## Multi-Dataset Strategy
Create multiple datasets in the same namespace for different retrieval surfaces:
* **paragraphs** -- granular text chunks for detailed retrieval
* **articles** -- full concatenated text for broad context
* **entities** -- extracted people, places, dates for entity lookup
## Connecting Datasets with Links
Links tell AutoRAG how datasets relate. Create links in **both directions** for best results. Always create links as a separate step after ingestion is complete.
During ingestion, pass `"link": {"ExactMatch": null}` to enable auto-discovery. DataLinks will analyze the ingested data and identify columns across datasets that share matching values. You can then review the discovered links in the Connections Overview and choose which ones to save.
## Using the helperPrompt
The `helperPrompt` is a system instruction that guides how the LLM reasons and formats answers. It does **not** affect retrieval, only the model's response.
**Example: Concise factual answers**
```text theme={null}
Answer with ONLY the exact name, number, or term asked for.
Maximum 1-3 words. Do not add titles, qualifiers, or extra context.
Give the shortest commonly-used form of names.
Just the bare factual answer, nothing else.
```
**Example: Multi-hop reasoning**
```text theme={null}
You answer multi-hop factual questions. Each question requires
connecting information across multiple passages.
Before answering, trace the full reasoning chain: identify every
entity, find what the context says about each, and follow the
chain to its conclusion.
Answer rules:
- 1-3 words only
- Exactly one answer, never a list
- Use the most commonly known form of names
- No titles, qualifiers, categories, or explanations
```
**Example: Detailed analytical answers**
```text theme={null}
Provide a thorough answer based on the retrieved data.
Include specific data points, dates, and names where available.
If the data contains conflicting information, note the discrepancy.
Cite which records your answer draws from.
```
**helperPrompt vs query suffix:** You can also append instructions directly to the query string (e.g., "Who founded Acme Corp? Answer in 1-3 words."). However, `helperPrompt` tends to produce better results because the query sent to semantic search remains a clean question, without format instructions mixed in that could confuse retrieval.
## Choosing a Model
AutoRAG supports multiple LLM backends. Available models may change over time; the following are currently supported:
| Model | Provider | Characteristics |
| - | - | - |
| `gemini-3-flash-preview` | `gcloud` | Best quality for QA tasks, slower |
| `gpt-5.2-2025-12-11` | `openai` | Faster, lower quality on factual QA |
If omitted, DataLinks uses its default model. Specify with:
```python theme={null}
{
"model": "gemini-3-flash-preview",
"provider": "gcloud"
}
```
## Troubleshooting
**"I am sorry" / Refusal Responses**
AutoRAG could not find relevant data to answer the question. This usually means the information is not in any dataset in the namespace, or the question uses different terminology than the data. Try rephrasing the question or adding a `helperPrompt` that discourages refusals.
**Timeouts (120s)**
Complex queries across large or linked datasets can exceed the default timeout. Reduce the number of datasets or rows in the namespace, simplify dataset schemas, or reduce the number of active links.
**Wrong Answers**
AutoRAG retrieved data but the LLM reasoned incorrectly. Try adding a `helperPrompt` with reasoning guidance, checking whether the answer actually exists in your data, or verifying that links between datasets are correctly configured.
**500 Errors**
Usually caused by overly wide schemas (10+ columns). Keep datasets to 5 columns or fewer for reliable performance.
## Best Practices
1. **Keep schemas narrow**: fewer columns per dataset works best for AutoRAG answer retrieval.
2. **Use multiple datasets**: rather than one wide one, AutoRAG searches all datasets in a namespace.
3. **Create bidirectional links**: between datasets that share common fields.
4. **Keep queries clean**: use `helperPrompt` for format control rather than appending instructions to your query.
5. **Include text-rich columns**: semantic search needs substantial text to match against.
6. **Provide dataset descriptions**: help AutoRAG understand the context of your data.
7. **Test incrementally**: start with a small dataset, verify retrieval works, then scale up.
# DataLinks Docs and API/SDK Resources
Source: https://docs.landfall.ai/index
Access the tools and guides you need to build and explore connected data using DataLinks.
# How to Connect Claude Desktop to the DataLinks MCP Server
Source: https://docs.landfall.ai/mcp/claude-desktop
Learn how to connect to the DataLinks server using Claude Desktop.
This guide provides step-by-step instructions for using the DataLinks MCP server within Claude Desktop. DataLinks provides an MCP endpoint that Claude can use once it is configured locally. The process involves:
1. Installing Claude Desktop
2. Installing required system dependencies
3. Locating the Claude config folder and file
4. Adding the DataLinks MCP configuration
5. Restarting Claude Desktop
6. Verifying the connection
## Step 1: Install Claude Desktop
1. Open your browser and go to [https://claude.com/download](https://claude.com/download)
2. Download the installer for your operating system:
* Windows: `.exe`
* macOS: `.dmg`
* Linux: `.AppImage` or `.deb` depending on your distribution
3. Run the installer and follow the on screen instructions.
4. Launch Claude Desktop and sign in with your Anthropic account.
## Step 2: Install required dependencies
Claude Desktop relies on external tools to run MCP servers. These must be installed separately.
### Windows
* Install Node.js (required)
* Download from [https://nodejs.org](https://nodejs.org)
* Choose the LTS version
* Restart your computer after installation
* Confirm installation:
```
node--version
npx--version
```
### macOS
* Install Node.js
* Option 1: Download from [https://nodejs.org](https://nodejs.org)
* Option 2: Use Homebrew (Don’t have Homebrew? For install instructions, see [brew.sh](http://brew.sh))
```
brew install node
```
* Confirm installation:
```
node -v
npx -v
```
### Linux
* Install Node.js using your package manager
Ubuntu or Debian:
```
sudo apt update
sudo apt install nodejs npm
```
Fedora:
```
sudo dnf install nodejs
```
* Confirm installation:
```
node--version
npx--version
```
## Step 3: Locate the Claude Desktop config folder and open the config file
Claude Desktop reads MCP settings from a local configuration file. This can accessed by following these steps:
1. Open Claude Desktop.
2. Click your **profile icon** in the bottom-left corner of the app.
3. Click **Settings**.
4. Click **Developer** at the bottom of the left menu.
5. Click the **Edit Config** button.
This should open your file location in your file explorer. Look for a JSON file titled `claude_desktop_config` or something similar.
### Typical config location by platform
* Windows: `%APPDATA%\\Claude\\`
* macOS: `~/Library/ApplicationSupport/Claude/`
* Linux: `~/.config/Claude/`
If the folder does not exist yet, start Claude Desktop once and close it. The folder will be created automatically.
## Step 4: Add the DataLinks MCP configuration
1. Inside the Claude folder, open the file called `claude_desktop_config.json` (or similar).
2. Paste the following configuration into the file:
```
{
"mcpServers": {
"datalinks": {
"command": "C:\\\\PROGRA~1\\\\nodejs\\\\npx.cmd",
"args": [
"mcp-remote",
"",
"--header",
"Authorization:Bearer "
]
}
}
}
```
3. Replace `` with your DataLinks API token.
### Notes for macOS and Linux
* Update the `command` path to your system’s `npx` location.
* Common values include:
```
/usr/local/bin/npx
```
or
```
/opt/homebrew/bin/npx
```
You can find the correct path by running:
```
which npx
```
## Step 5: Restart Claude Desktop
1. Fully close Claude Desktop. (This means closing it in your application tray icon as well.)
2. Open it again.
3. Claude will automatically load the MCP server during startup.
If the configuration is correct, the DataLinks MCP server becomes available for tool based interactions.
## Step 6: Verify the connection
You can verify the setup by asking Claude something like:
> Can you confirm that the DataLinks MCP server is available?
If the server fails to load, Claude usually reports an MCP startup error.
Screenshot any errors that appear at startup for troubleshooting later.
## Common troubleshooting
### Check the logs file
Claude Desktop keeps detailed logs that explain what went wrong.
### Log file locations
* Windows: `%APPDATA%\\Claude\\logs`
* macOS: `~/Library/Application Support/Claude/logs`
* Linux: `~/.config/Claude/logs`
Open the most recent log file and look for MCP related errors.
### Common issues and fixes
**Claude does not start the MCP server**
* Confirm Node.js is installed
* Confirm `npx` works from the command line
* Double check the `command` path in the config file
**Authorization errors**
* Verify your DataLinks token is correct
* Make sure there are no extra spaces in the Authorization header
**Windows path errors**
* Ensure the `npx.cmd` path matches your Node.js installation
* `C:\\Program Files\\nodejs\\npx.cmd` is common, but it can vary
**Nothing happens after restart**
* Check that the config file name is exactly `claude_desktop_config.json`
* Confirm the JSON formatting is valid
## Getting help
If you get stuck:
1. Open the Claude logs file
2. Copy the content
3. Paste them into an LLM (such as ChatGPT) along with a short description of what you tried
4. Also paste any error messages you were able to screenshot
This usually provides clear guidance on what needs to be adjusted.
Once the configuration is complete, Claude Desktop can communicate with the DataLinks MCP server and use it as part of its tool workflow.
# DataLinks Docs MCP Server
Source: https://docs.landfall.ai/mcp/docs-mcp
Access the DataLinks docs via MCP as well as the AP
The DataLinks Docs MCP server provides direct access to the DataLinks documentation from within MCP-compatible tools. Once connected, your AI assistant can search, retrieve, and reference documentation content in real time. This allows you to get immediate answers to questions about the DataLinks platform without leaving your workflow.
This guide covers the DataLinks Docs MCP server, which provides access to documentation. If you want to query your data, manage datasets, or interact with the DataLinks platform, see the [DataLinks Platform MCP Server](/mcp/general-mcp) guide instead.
## Configuration
To connect to the DataLinks Docs MCP server, add the following configuration to your MCP client. The exact location of your configuration file depends on the tool you are using.
```json theme={null}
{
"mcpServers": {
"datalinks-docs": {
"command": "npx",
"args": [
"mcp-remote",
"https://docs.datalinks.com/mcp"
]
}
}
}
```
## Using both MCP servers together
For the best experience, configure both the [DataLinks platform MCP server ](/mcp/general-mcp)and the Docs MCP server in the same environment. This allows your AI assistant to:
1. Execute queries and manage data through the platform API
2. Reference documentation to answer questions or verify correct usage
3. Troubleshoot issues by looking up relevant guides in real time
Here is an example configuration that includes both servers:
```json theme={null}
{
"mcpServers": {
"datalinks": {
"command": "C:\\\\PROGRA~1\\\\nodejs\\\\npx.cmd",
"args": [
"mcp-remote",
"https://mcp.datalinks.com/mcp/",
"--header",
"Authorization: Bearer "
]
},
"datalinks-docs": {
"command": "C:\\\\PROGRA~1\\\\nodejs\\\\npx.cmd",
"args": [
"mcp-remote",
"https://docs.datalinks.com/mcp"
]
}
}
}
```
## Verifying the connection
After adding the configuration and restarting your MCP client, you can verify the setup by asking your assistant something like:
> Can you search the DataLinks documentation for information about managing connections?
If the Docs MCP server is connected, your assistant will be able to retrieve and summarize relevant documentation content.
## Why use the Docs MCP server?
When working with DataLinks through an agentic tool like Claude Desktop, you may have questions about how to use specific features, structure queries, or troubleshoot issues. The Docs MCP server gives your AI assistant the ability to look up this information on demand.
Combined with the DataLinks platform MCP server, you get both capabilities in one environment: your assistant can query your data through the platform API while also referencing documentation to guide its actions or answer your questions. This is especially useful when you need help understanding a feature mid-task or want to verify the correct syntax for a query.
## Use cases
The Docs MCP server is helpful when you want to:
* Ask questions about DataLinks features while working in Claude Desktop or another MCP-compatible tool
* Get guidance on query syntax, API endpoints, or SDK methods without switching to a browser
* Let your AI assistant self-reference documentation to improve the accuracy of its responses
* Combine documentation lookups with live data queries in a single workflow
# How to Connect to the DataLinks MCP Server (General Guide)
Source: https://docs.landfall.ai/mcp/general-mcp
General instructions on how to connect to the DataLinks MCP server
The DataLinks MCP server allows AI tools and agent frameworks to securely access DataLinks services. Once connected, your application or desktop client can send requests to DataLinks through the MCP protocol.
This guide covers:
1. Prerequisites
2. Installing required dependencies
3. Creating a basic MCP configuration
4. Starting the MCP connection
5. Verifying the setup
6. Troubleshooting common issues
## Prerequisites
Before starting, make sure you have:
* A DataLinks API token
* Node.js installed on your system
* An AI tool or application that supports MCP
## Install Required Dependencies
The DataLinks MCP connection runs through Node.js using an MCP client.
### Windows
1. Download Node.js (LTS) from
[https://nodejs.org](https://nodejs.org)
2. Restart your computer after installation.
3. Verify installation:
```
node--version
npx--version
```
### macOS
Install Node.js using one of these options:
* Download from [https://nodejs.org](https://nodejs.org)
* Or install with Homebrew (Don’t have Homebrew? For install instructions, see [**brew.sh**](http://brew.sh)):
```
brew install node
```
Verify installation:
```
node -v
npx -v
```
### Linux
Install Node.js using your distribution’s package manager.
Ubuntu or Debian:
```
sudo apt update
sudo apt install nodejs npm
```
Fedora:
```
sudo dnf install nodejs
```
Verify installation:
```
node--version
npx--version
```
## Create a Basic MCP Configuration
Most MCP compatible tools read from a simple JSON configuration.
Create a file named something like:
```
mcp_config.json
```
Add the following configuration:
```json theme={null}
{
"mcpServers": {
"datalinks": {
"command": "npx",
"args": [
"mcp-remote",
"https://mcp.datalinks.com/mcp/",
"--header",
"Authorization: Bearer "
]
}
}
}
```
Replace `` with your DataLinks API token.
## Platform Specific Command Notes
Depending on your operating system, the `command` value may need adjustment.
* Windows commonly uses:
```
C:\\\\Program Files\\\\nodejs\\\\npx.cmd
```
* macOS commonly uses:
```
/usr/local/bin/npx
```
or
```
/opt/homebrew/bin/npx
```
* Linux commonly uses:
```
/usr/bin/npx
```
You can find the correct path by running:
```
which npx
```
## Start the MCP Connection
How you start the MCP connection depends on your tool.
* Desktop clients load the configuration at startup
* Developer tools may require starting the MCP client manually
* Agent frameworks usually detect MCP servers automatically once running
Once started, the MCP client opens a secure connection to DataLinks.
## Verify the Connection
To confirm everything is working:
* Check terminal or application logs for a successful MCP connection
* Send a simple request that uses DataLinks
* Confirm that no authentication or connection errors appear
## Common Troubleshooting
### Review Logs
Most MCP clients provide detailed logs. These logs explain:
* Startup failures
* Authentication issues
* Network errors
A helpful step is to copy the log output and paste it into an LLM-powered assistant such as ChatGPT for help understanding what went wrong.
### Common Issues
**Authorization errors**
* Confirm your DataLinks API token is correct
* Make sure there are no extra spaces in the Authorization header
**Command not found**
* Verify Node.js is installed
* Verify the `npx` path is correct for your system
**Connection failures**
* Confirm the DataLinks MCP URL is correct
* Check firewall or proxy settings
**Configuration not loading**
* Confirm the JSON file is valid
* Confirm the file name and location are correct for your tool
## Next Steps
Once connected, you can:
* Use DataLinks as a tool in MCP compatible AI clients
* Combine DataLinks with other MCP servers
* Build workflows that blend AI reasoning with real data access
# Components
Source: https://docs.landfall.ai/pythonsdk/components
Core building blocks of the DataLinks Python SDK.
### 1. **DLConfig**
`DLConfig` reads configurations (e.g., API keys) via environment variables or `.env` files. This enables dynamic adaptation across deployment environments.
### 2. **DataLinksAPI**
`DataLinksAPI` handles interactions with the API. You can:
* Ingest data directly or via multipart upload for large files.
* Track and wait for async ingestion completion.
* Query or retrieve data with complex parameters.
* Manage namespaces.
### 3. **Inference Workflow**
Use a chain of inference and validation steps defined through classes like `ProcessUnstructured`, `Normalize`, and `Validate` to automate data preparation workflows.
```python theme={null}
from datalinks.pipeline import Pipeline, ProcessUnstructured, Normalize, Validate, ValidateModes
# Define an inference pipeline
inference_steps = Pipeline(
ProcessUnstructured(derive_from="source_field", helper_prompt="This extracts tables."),
Normalize(target_cols={"email": "email_address"}, mode="all-in-one"),
Validate(mode=ValidateModes.FIELDS, columns=["email", "phone"]),
)
```
### 4. **Entity Resolution**
Supports multiple resolution strategies, configurable via `MatchTypeConfig`:
```python theme={null}
from datalinks.links import MatchTypeConfig, ExactMatch
entity_resolution = MatchTypeConfig(
# parameters are optional
exact_match=ExactMatch(minVariation=0.2, minDistinct=0.3)
)
```
### 5. **Loaders**
Abstract base loaders (e.g., `JSONLoader`) allow seamless data ingestion from custom file formats like `.json`.
### 6. **Parametrize LLMs**
You can choose the model and provider to be used in inference steps (eg.: `ProcessUnstructured`, `Normalize`, `Validate`).
```python theme={null}
from datalinks.pipeline import Pipeline, ProcessUnstructured
steps = Pipeline(
ProcessUnstructured(
derive_from="text",
helper_prompt="If you find a numeric field use only the value and omit the rest.",
model="gpt-4.1-nano-2025-04-14",
provider="openai"
)
)
```
# Reference
Source: https://docs.landfall.ai/pythonsdk/datalinks
# datalinks package
## Subpackages
* [datalinks.api package](datalinks.api)
* [`DataLinksAPI`](datalinks.api#datalinks.api.DataLinksAPI)
* [`DataLinksAPI.ingest()`](datalinks.api#datalinks.api.DataLinksAPI.ingest)
* [`DataLinksAPI.create_space()`](datalinks.api#datalinks.api.DataLinksAPI.create_space)
* [`DataLinksAPI.update_infer_definition()`](datalinks.api#datalinks.api.DataLinksAPI.update_infer_definition)
* [`DataLinksAPI.infer_dataset_description()`](datalinks.api#datalinks.api.DataLinksAPI.infer_dataset_description)
* [`DataLinksAPI.update_sort_order()`](datalinks.api#datalinks.api.DataLinksAPI.update_sort_order)
* [`DataLinksAPI.prepare_multipart_upload()`](datalinks.api#datalinks.api.DataLinksAPI.prepare_multipart_upload)
* [`DataLinksAPI.finish_multipart_upload()`](datalinks.api#datalinks.api.DataLinksAPI.finish_multipart_upload)
* [`DataLinksAPI.abort_multipart_upload()`](datalinks.api#datalinks.api.DataLinksAPI.abort_multipart_upload)
* [`DataLinksAPI.list_ingestions()`](datalinks.api#datalinks.api.DataLinksAPI.list_ingestions)
* [`DataLinksAPI.wait_for_ingestion()`](datalinks.api#datalinks.api.DataLinksAPI.wait_for_ingestion)
* [`DataLinksAPI.get_dataset_info()`](datalinks.api#datalinks.api.DataLinksAPI.get_dataset_info)
* [`DataLinksAPI.delete_dataset()`](datalinks.api#datalinks.api.DataLinksAPI.delete_dataset)
* [`DataLinksAPI.rename_dataset()`](datalinks.api#datalinks.api.DataLinksAPI.rename_dataset)
* [`DataLinksAPI.clear_dataset()`](datalinks.api#datalinks.api.DataLinksAPI.clear_dataset)
* [`DataLinksAPI.add_link()`](datalinks.api#datalinks.api.DataLinksAPI.add_link)
* [`DataLinksAPI.preview_links()`](datalinks.api#datalinks.api.DataLinksAPI.preview_links)
* [`DataLinksAPI.rebuild_links()`](datalinks.api#datalinks.api.DataLinksAPI.rebuild_links)
* [`DataLinksAPI.load_links()`](datalinks.api#datalinks.api.DataLinksAPI.load_links)
* [`DataLinksAPI.list_datasets()`](datalinks.api#datalinks.api.DataLinksAPI.list_datasets)
* [`DataLinksAPI.query_data()`](datalinks.api#datalinks.api.DataLinksAPI.query_data)
* [`DataLinksAPI.ask()`](datalinks.api#datalinks.api.DataLinksAPI.ask)
* [`DataLinksAPI.preview_ingest()`](datalinks.api#datalinks.api.DataLinksAPI.preview_ingest)
* [`DataLinksAPI.infer_schema()`](datalinks.api#datalinks.api.DataLinksAPI.infer_schema)
* [`DataLinksAPI.retry_ingestion()`](datalinks.api#datalinks.api.DataLinksAPI.retry_ingestion)
* [`DataLinksAPI.mark_ingestion_seen()`](datalinks.api#datalinks.api.DataLinksAPI.mark_ingestion_seen)
* [`DataLinksAPI.autorag()`](datalinks.api#datalinks.api.DataLinksAPI.autorag)
* [`DataLinksAPI.request_cleaning()`](datalinks.api#datalinks.api.DataLinksAPI.request_cleaning)
* [`DataLinksAPI.get_cleaning_code()`](datalinks.api#datalinks.api.DataLinksAPI.get_cleaning_code)
* [`DataLinksAPI.get_ontology()`](datalinks.api#datalinks.api.DataLinksAPI.get_ontology)
* [`DataLinksAPI.save_ontology()`](datalinks.api#datalinks.api.DataLinksAPI.save_ontology)
* [`DataLinksAPI.curate_links()`](datalinks.api#datalinks.api.DataLinksAPI.curate_links)
* [`DataLinksAPI.rename_namespace()`](datalinks.api#datalinks.api.DataLinksAPI.rename_namespace)
* [`DataLinksAPI.list_namespaces()`](datalinks.api#datalinks.api.DataLinksAPI.list_namespaces)
* [`DataLinksAPI.list_all_datasets_schema()`](datalinks.api#datalinks.api.DataLinksAPI.list_all_datasets_schema)
* [`DataLinksAPI.list_datasets_in_namespace_schema()`](datalinks.api#datalinks.api.DataLinksAPI.list_datasets_in_namespace_schema)
* [`DataLinksAPI.get_current_user()`](datalinks.api#datalinks.api.DataLinksAPI.get_current_user)
* [`DataLinksAPI.list_tokens()`](datalinks.api#datalinks.api.DataLinksAPI.list_tokens)
* [`DataLinksAPI.add_token()`](datalinks.api#datalinks.api.DataLinksAPI.add_token)
* [`DataLinksAPI.delete_token()`](datalinks.api#datalinks.api.DataLinksAPI.delete_token)
* [`DataLinksAPI.list_token_permissions()`](datalinks.api#datalinks.api.DataLinksAPI.list_token_permissions)
* [`DataLinksAPI.get_usage_history()`](datalinks.api#datalinks.api.DataLinksAPI.get_usage_history)
* [`DataLinksAPI.get_usage_by_day()`](datalinks.api#datalinks.api.DataLinksAPI.get_usage_by_day)
* [`DataLinksAPI.get_agent_run_status()`](datalinks.api#datalinks.api.DataLinksAPI.get_agent_run_status)
* [`DataLinksAPI.delete_agent_run()`](datalinks.api#datalinks.api.DataLinksAPI.delete_agent_run)
* [`DataLinksAPI.submit_agent_run_feedback()`](datalinks.api#datalinks.api.DataLinksAPI.submit_agent_run_feedback)
* [`DataLinksAPI.resume_agent_run()`](datalinks.api#datalinks.api.DataLinksAPI.resume_agent_run)
* [`DataLinksAPI.share_dataset_access()`](datalinks.api#datalinks.api.DataLinksAPI.share_dataset_access)
* [`DataLinksAPI.revoke_dataset_access()`](datalinks.api#datalinks.api.DataLinksAPI.revoke_dataset_access)
* [`DataLinksAPI.list_dataset_shares()`](datalinks.api#datalinks.api.DataLinksAPI.list_dataset_shares)
* [`DataLinksAPI.share_namespace_access()`](datalinks.api#datalinks.api.DataLinksAPI.share_namespace_access)
* [`DataLinksAPI.revoke_namespace_access()`](datalinks.api#datalinks.api.DataLinksAPI.revoke_namespace_access)
* [`DataLinksAPI.list_namespace_shares()`](datalinks.api#datalinks.api.DataLinksAPI.list_namespace_shares)
* [`DataLinksAPI.list_conversations()`](datalinks.api#datalinks.api.DataLinksAPI.list_conversations)
* [`DataLinksAPI.get_conversation()`](datalinks.api#datalinks.api.DataLinksAPI.get_conversation)
* [`AsyncDataLinksAPI`](datalinks.api#datalinks.api.AsyncDataLinksAPI)
* [`AsyncDataLinksAPI.aclose()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.aclose)
* [`AsyncDataLinksAPI.ingest()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.ingest)
* [`AsyncDataLinksAPI.create_space()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.create_space)
* [`AsyncDataLinksAPI.update_infer_definition()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.update_infer_definition)
* [`AsyncDataLinksAPI.infer_dataset_description()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.infer_dataset_description)
* [`AsyncDataLinksAPI.update_sort_order()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.update_sort_order)
* [`AsyncDataLinksAPI.prepare_multipart_upload()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.prepare_multipart_upload)
* [`AsyncDataLinksAPI.finish_multipart_upload()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.finish_multipart_upload)
* [`AsyncDataLinksAPI.abort_multipart_upload()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.abort_multipart_upload)
* [`AsyncDataLinksAPI.list_ingestions()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_ingestions)
* [`AsyncDataLinksAPI.wait_for_ingestion()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.wait_for_ingestion)
* [`AsyncDataLinksAPI.get_dataset_info()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.get_dataset_info)
* [`AsyncDataLinksAPI.delete_dataset()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.delete_dataset)
* [`AsyncDataLinksAPI.rename_dataset()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.rename_dataset)
* [`AsyncDataLinksAPI.rename_namespace()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.rename_namespace)
* [`AsyncDataLinksAPI.clear_dataset()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.clear_dataset)
* [`AsyncDataLinksAPI.add_link()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.add_link)
* [`AsyncDataLinksAPI.preview_links()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.preview_links)
* [`AsyncDataLinksAPI.rebuild_links()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.rebuild_links)
* [`AsyncDataLinksAPI.load_links()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.load_links)
* [`AsyncDataLinksAPI.list_datasets()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_datasets)
* [`AsyncDataLinksAPI.list_namespaces()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_namespaces)
* [`AsyncDataLinksAPI.list_all_datasets_schema()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_all_datasets_schema)
* [`AsyncDataLinksAPI.list_datasets_in_namespace_schema()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_datasets_in_namespace_schema)
* [`AsyncDataLinksAPI.query_data()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.query_data)
* [`AsyncDataLinksAPI.autorag()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.autorag)
* [`AsyncDataLinksAPI.ask()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.ask)
* [`AsyncDataLinksAPI.preview_ingest()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.preview_ingest)
* [`AsyncDataLinksAPI.infer_schema()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.infer_schema)
* [`AsyncDataLinksAPI.retry_ingestion()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.retry_ingestion)
* [`AsyncDataLinksAPI.mark_ingestion_seen()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.mark_ingestion_seen)
* [`AsyncDataLinksAPI.request_cleaning()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.request_cleaning)
* [`AsyncDataLinksAPI.get_cleaning_code()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.get_cleaning_code)
* [`AsyncDataLinksAPI.get_ontology()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.get_ontology)
* [`AsyncDataLinksAPI.save_ontology()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.save_ontology)
* [`AsyncDataLinksAPI.curate_links()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.curate_links)
* [`AsyncDataLinksAPI.get_current_user()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.get_current_user)
* [`AsyncDataLinksAPI.list_tokens()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_tokens)
* [`AsyncDataLinksAPI.add_token()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.add_token)
* [`AsyncDataLinksAPI.delete_token()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.delete_token)
* [`AsyncDataLinksAPI.list_token_permissions()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_token_permissions)
* [`AsyncDataLinksAPI.get_usage_history()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.get_usage_history)
* [`AsyncDataLinksAPI.get_usage_by_day()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.get_usage_by_day)
* [`AsyncDataLinksAPI.get_agent_run_status()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.get_agent_run_status)
* [`AsyncDataLinksAPI.delete_agent_run()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.delete_agent_run)
* [`AsyncDataLinksAPI.submit_agent_run_feedback()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.submit_agent_run_feedback)
* [`AsyncDataLinksAPI.resume_agent_run()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.resume_agent_run)
* [`AsyncDataLinksAPI.share_dataset_access()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.share_dataset_access)
* [`AsyncDataLinksAPI.revoke_dataset_access()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.revoke_dataset_access)
* [`AsyncDataLinksAPI.list_dataset_shares()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_dataset_shares)
* [`AsyncDataLinksAPI.share_namespace_access()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.share_namespace_access)
* [`AsyncDataLinksAPI.revoke_namespace_access()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.revoke_namespace_access)
* [`AsyncDataLinksAPI.list_namespace_shares()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_namespace_shares)
* [`AsyncDataLinksAPI.list_conversations()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.list_conversations)
* [`AsyncDataLinksAPI.get_conversation()`](datalinks.api#datalinks.api.AsyncDataLinksAPI.get_conversation)
* [`DataLinksRequestError`](datalinks.api#datalinks.api.DataLinksRequestError)
* [`DLConfig`](datalinks.api#datalinks.api.DLConfig)
* [`DLConfig.host`](datalinks.api#datalinks.api.DLConfig.host)
* [`DLConfig.apikey`](datalinks.api#datalinks.api.DLConfig.apikey)
* [`DLConfig.namespace`](datalinks.api#datalinks.api.DLConfig.namespace)
* [`DLConfig.objectname`](datalinks.api#datalinks.api.DLConfig.objectname)
* [`DLConfig.from_env()`](datalinks.api#datalinks.api.DLConfig.from_env)
* [`AskEvent`](datalinks.api#datalinks.api.AskEvent)
* [`AskEvent.type`](datalinks.api#datalinks.api.AskEvent.type)
* [`AskEvent.data`](datalinks.api#datalinks.api.AskEvent.data)
* [`AskStream`](datalinks.api#datalinks.api.AskStream)
* [`AskStream.close()`](datalinks.api#datalinks.api.AskStream.close)
* [`AskStream.headers`](datalinks.api#datalinks.api.AskStream.headers)
* [`AskStream.status_code`](datalinks.api#datalinks.api.AskStream.status_code)
* [`AsyncAskStream`](datalinks.api#datalinks.api.AsyncAskStream)
* [`AsyncAskStream.aclose()`](datalinks.api#datalinks.api.AsyncAskStream.aclose)
* [`AsyncAskStream.headers`](datalinks.api#datalinks.api.AsyncAskStream.headers)
* [`AsyncAskStream.status_code`](datalinks.api#datalinks.api.AsyncAskStream.status_code)
* [`AutoRagResult`](datalinks.api#datalinks.api.AutoRagResult)
* [`AutoRagResult.headers`](datalinks.api#datalinks.api.AutoRagResult.headers)
* [`AutoRagResult.status_code`](datalinks.api#datalinks.api.AutoRagResult.status_code)
* [`IngestionResult`](datalinks.api#datalinks.api.IngestionResult)
* [`IngestionResult.successful`](datalinks.api#datalinks.api.IngestionResult.successful)
* [`IngestionResult.failed`](datalinks.api#datalinks.api.IngestionResult.failed)
* [`IngestProxyAPI`](datalinks.api#datalinks.api.IngestProxyAPI)
* [`IngestProxyAPI.run_pipeline()`](datalinks.api#datalinks.api.IngestProxyAPI.run_pipeline)
* [`IngestProxyAPI.stream_pipeline()`](datalinks.api#datalinks.api.IngestProxyAPI.stream_pipeline)
* [`IngestProxyAPI.get_pipeline_trace()`](datalinks.api#datalinks.api.IngestProxyAPI.get_pipeline_trace)
* [`IngestProxyAPI.resume_pipeline_hook()`](datalinks.api#datalinks.api.IngestProxyAPI.resume_pipeline_hook)
* [`IngestProxyConfig`](datalinks.api#datalinks.api.IngestProxyConfig)
* [`IngestProxyConfig.host`](datalinks.api#datalinks.api.IngestProxyConfig.host)
* [`IngestProxyConfig.datalinks_token`](datalinks.api#datalinks.api.IngestProxyConfig.datalinks_token)
* [`IngestProxyConfig.datalinks_username`](datalinks.api#datalinks.api.IngestProxyConfig.datalinks_username)
* [`IngestProxyConfig.namespace`](datalinks.api#datalinks.api.IngestProxyConfig.namespace)
* [`IngestProxyConfig.from_env()`](datalinks.api#datalinks.api.IngestProxyConfig.from_env)
* [`PipelineRun`](datalinks.api#datalinks.api.PipelineRun)
* [`PipelineRun.close()`](datalinks.api#datalinks.api.PipelineRun.close)
* [Submodules](datalinks.api#submodules)
* [datalinks.api.async\_datalinks module](datalinks.api#module-datalinks.api.async_datalinks)
* [`AsyncAskStream`](datalinks.api#datalinks.api.async_datalinks.AsyncAskStream)
* [`AsyncAskStream.aclose()`](datalinks.api#datalinks.api.async_datalinks.AsyncAskStream.aclose)
* [`AsyncAskStream.headers`](datalinks.api#datalinks.api.async_datalinks.AsyncAskStream.headers)
* [`AsyncAskStream.status_code`](datalinks.api#datalinks.api.async_datalinks.AsyncAskStream.status_code)
* [`AsyncDataLinksAPI`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI)
* [`AsyncDataLinksAPI.aclose()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.aclose)
* [`AsyncDataLinksAPI.ingest()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.ingest)
* [`AsyncDataLinksAPI.create_space()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.create_space)
* [`AsyncDataLinksAPI.update_infer_definition()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.update_infer_definition)
* [`AsyncDataLinksAPI.infer_dataset_description()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.infer_dataset_description)
* [`AsyncDataLinksAPI.update_sort_order()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.update_sort_order)
* [`AsyncDataLinksAPI.prepare_multipart_upload()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.prepare_multipart_upload)
* [`AsyncDataLinksAPI.finish_multipart_upload()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.finish_multipart_upload)
* [`AsyncDataLinksAPI.abort_multipart_upload()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.abort_multipart_upload)
* [`AsyncDataLinksAPI.list_ingestions()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_ingestions)
* [`AsyncDataLinksAPI.wait_for_ingestion()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.wait_for_ingestion)
* [`AsyncDataLinksAPI.get_dataset_info()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.get_dataset_info)
* [`AsyncDataLinksAPI.delete_dataset()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.delete_dataset)
* [`AsyncDataLinksAPI.rename_dataset()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.rename_dataset)
* [`AsyncDataLinksAPI.rename_namespace()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.rename_namespace)
* [`AsyncDataLinksAPI.clear_dataset()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.clear_dataset)
* [`AsyncDataLinksAPI.add_link()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.add_link)
* [`AsyncDataLinksAPI.preview_links()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.preview_links)
* [`AsyncDataLinksAPI.rebuild_links()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.rebuild_links)
* [`AsyncDataLinksAPI.load_links()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.load_links)
* [`AsyncDataLinksAPI.list_datasets()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_datasets)
* [`AsyncDataLinksAPI.list_namespaces()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_namespaces)
* [`AsyncDataLinksAPI.list_all_datasets_schema()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_all_datasets_schema)
* [`AsyncDataLinksAPI.list_datasets_in_namespace_schema()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_datasets_in_namespace_schema)
* [`AsyncDataLinksAPI.query_data()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.query_data)
* [`AsyncDataLinksAPI.autorag()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.autorag)
* [`AsyncDataLinksAPI.ask()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.ask)
* [`AsyncDataLinksAPI.preview_ingest()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.preview_ingest)
* [`AsyncDataLinksAPI.infer_schema()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.infer_schema)
* [`AsyncDataLinksAPI.retry_ingestion()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.retry_ingestion)
* [`AsyncDataLinksAPI.mark_ingestion_seen()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.mark_ingestion_seen)
* [`AsyncDataLinksAPI.request_cleaning()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.request_cleaning)
* [`AsyncDataLinksAPI.get_cleaning_code()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.get_cleaning_code)
* [`AsyncDataLinksAPI.get_ontology()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.get_ontology)
* [`AsyncDataLinksAPI.save_ontology()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.save_ontology)
* [`AsyncDataLinksAPI.curate_links()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.curate_links)
* [`AsyncDataLinksAPI.get_current_user()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.get_current_user)
* [`AsyncDataLinksAPI.list_tokens()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_tokens)
* [`AsyncDataLinksAPI.add_token()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.add_token)
* [`AsyncDataLinksAPI.delete_token()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.delete_token)
* [`AsyncDataLinksAPI.list_token_permissions()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_token_permissions)
* [`AsyncDataLinksAPI.get_usage_history()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.get_usage_history)
* [`AsyncDataLinksAPI.get_usage_by_day()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.get_usage_by_day)
* [`AsyncDataLinksAPI.get_agent_run_status()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.get_agent_run_status)
* [`AsyncDataLinksAPI.delete_agent_run()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.delete_agent_run)
* [`AsyncDataLinksAPI.submit_agent_run_feedback()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.submit_agent_run_feedback)
* [`AsyncDataLinksAPI.resume_agent_run()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.resume_agent_run)
* [`AsyncDataLinksAPI.share_dataset_access()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.share_dataset_access)
* [`AsyncDataLinksAPI.revoke_dataset_access()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.revoke_dataset_access)
* [`AsyncDataLinksAPI.list_dataset_shares()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_dataset_shares)
* [`AsyncDataLinksAPI.share_namespace_access()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.share_namespace_access)
* [`AsyncDataLinksAPI.revoke_namespace_access()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.revoke_namespace_access)
* [`AsyncDataLinksAPI.list_namespace_shares()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_namespace_shares)
* [`AsyncDataLinksAPI.list_conversations()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.list_conversations)
* [`AsyncDataLinksAPI.get_conversation()`](datalinks.api#datalinks.api.async_datalinks.AsyncDataLinksAPI.get_conversation)
* [datalinks.api.datalinks module](datalinks.api#module-datalinks.api.datalinks)
* [`DLConfig`](datalinks.api#datalinks.api.datalinks.DLConfig)
* [`DLConfig.host`](datalinks.api#datalinks.api.datalinks.DLConfig.host)
* [`DLConfig.apikey`](datalinks.api#datalinks.api.datalinks.DLConfig.apikey)
* [`DLConfig.namespace`](datalinks.api#datalinks.api.datalinks.DLConfig.namespace)
* [`DLConfig.objectname`](datalinks.api#datalinks.api.datalinks.DLConfig.objectname)
* [`DLConfig.from_env()`](datalinks.api#datalinks.api.datalinks.DLConfig.from_env)
* [`AskEvent`](datalinks.api#datalinks.api.datalinks.AskEvent)
* [`AskEvent.type`](datalinks.api#datalinks.api.datalinks.AskEvent.type)
* [`AskEvent.data`](datalinks.api#datalinks.api.datalinks.AskEvent.data)
* [`AskStream`](datalinks.api#datalinks.api.datalinks.AskStream)
* [`AskStream.close()`](datalinks.api#datalinks.api.datalinks.AskStream.close)
* [`AskStream.headers`](datalinks.api#datalinks.api.datalinks.AskStream.headers)
* [`AskStream.status_code`](datalinks.api#datalinks.api.datalinks.AskStream.status_code)
* [`AutoRagResult`](datalinks.api#datalinks.api.datalinks.AutoRagResult)
* [`AutoRagResult.headers`](datalinks.api#datalinks.api.datalinks.AutoRagResult.headers)
* [`AutoRagResult.status_code`](datalinks.api#datalinks.api.datalinks.AutoRagResult.status_code)
* [`IngestionResult`](datalinks.api#datalinks.api.datalinks.IngestionResult)
* [`IngestionResult.successful`](datalinks.api#datalinks.api.datalinks.IngestionResult.successful)
* [`IngestionResult.failed`](datalinks.api#datalinks.api.datalinks.IngestionResult.failed)
* [`DataLinksAPI`](datalinks.api#datalinks.api.datalinks.DataLinksAPI)
* [`DataLinksAPI.config`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.config)
* [`DataLinksAPI.ingest()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.ingest)
* [`DataLinksAPI.create_space()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.create_space)
* [`DataLinksAPI.update_infer_definition()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.update_infer_definition)
* [`DataLinksAPI.infer_dataset_description()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.infer_dataset_description)
* [`DataLinksAPI.update_sort_order()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.update_sort_order)
* [`DataLinksAPI.prepare_multipart_upload()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.prepare_multipart_upload)
* [`DataLinksAPI.finish_multipart_upload()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.finish_multipart_upload)
* [`DataLinksAPI.abort_multipart_upload()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.abort_multipart_upload)
* [`DataLinksAPI.list_ingestions()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_ingestions)
* [`DataLinksAPI.wait_for_ingestion()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.wait_for_ingestion)
* [`DataLinksAPI.get_dataset_info()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.get_dataset_info)
* [`DataLinksAPI.delete_dataset()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.delete_dataset)
* [`DataLinksAPI.rename_dataset()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.rename_dataset)
* [`DataLinksAPI.clear_dataset()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.clear_dataset)
* [`DataLinksAPI.add_link()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.add_link)
* [`DataLinksAPI.preview_links()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.preview_links)
* [`DataLinksAPI.rebuild_links()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.rebuild_links)
* [`DataLinksAPI.load_links()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.load_links)
* [`DataLinksAPI.list_datasets()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_datasets)
* [`DataLinksAPI.query_data()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.query_data)
* [`DataLinksAPI.ask()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.ask)
* [`DataLinksAPI.preview_ingest()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.preview_ingest)
* [`DataLinksAPI.infer_schema()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.infer_schema)
* [`DataLinksAPI.retry_ingestion()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.retry_ingestion)
* [`DataLinksAPI.mark_ingestion_seen()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.mark_ingestion_seen)
* [`DataLinksAPI.autorag()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.autorag)
* [`DataLinksAPI.request_cleaning()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.request_cleaning)
* [`DataLinksAPI.get_cleaning_code()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.get_cleaning_code)
* [`DataLinksAPI.get_ontology()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.get_ontology)
* [`DataLinksAPI.save_ontology()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.save_ontology)
* [`DataLinksAPI.curate_links()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.curate_links)
* [`DataLinksAPI.rename_namespace()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.rename_namespace)
* [`DataLinksAPI.list_namespaces()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_namespaces)
* [`DataLinksAPI.list_all_datasets_schema()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_all_datasets_schema)
* [`DataLinksAPI.list_datasets_in_namespace_schema()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_datasets_in_namespace_schema)
* [`DataLinksAPI.get_current_user()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.get_current_user)
* [`DataLinksAPI.list_tokens()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_tokens)
* [`DataLinksAPI.add_token()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.add_token)
* [`DataLinksAPI.delete_token()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.delete_token)
* [`DataLinksAPI.list_token_permissions()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_token_permissions)
* [`DataLinksAPI.get_usage_history()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.get_usage_history)
* [`DataLinksAPI.get_usage_by_day()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.get_usage_by_day)
* [`DataLinksAPI.get_agent_run_status()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.get_agent_run_status)
* [`DataLinksAPI.delete_agent_run()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.delete_agent_run)
* [`DataLinksAPI.submit_agent_run_feedback()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.submit_agent_run_feedback)
* [`DataLinksAPI.resume_agent_run()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.resume_agent_run)
* [`DataLinksAPI.share_dataset_access()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.share_dataset_access)
* [`DataLinksAPI.revoke_dataset_access()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.revoke_dataset_access)
* [`DataLinksAPI.list_dataset_shares()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_dataset_shares)
* [`DataLinksAPI.share_namespace_access()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.share_namespace_access)
* [`DataLinksAPI.revoke_namespace_access()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.revoke_namespace_access)
* [`DataLinksAPI.list_namespace_shares()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_namespace_shares)
* [`DataLinksAPI.list_conversations()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.list_conversations)
* [`DataLinksAPI.get_conversation()`](datalinks.api#datalinks.api.datalinks.DataLinksAPI.get_conversation)
* [datalinks.api.ingest\_proxy module](datalinks.api#module-datalinks.api.ingest_proxy)
* [`IngestProxyConfig`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyConfig)
* [`IngestProxyConfig.host`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyConfig.host)
* [`IngestProxyConfig.datalinks_token`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyConfig.datalinks_token)
* [`IngestProxyConfig.datalinks_username`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyConfig.datalinks_username)
* [`IngestProxyConfig.namespace`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyConfig.namespace)
* [`IngestProxyConfig.from_env()`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyConfig.from_env)
* [`PipelineRun`](datalinks.api#datalinks.api.ingest_proxy.PipelineRun)
* [`PipelineRun.close()`](datalinks.api#datalinks.api.ingest_proxy.PipelineRun.close)
* [`IngestProxyAPI`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyAPI)
* [`IngestProxyAPI.config`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyAPI.config)
* [`IngestProxyAPI.run_pipeline()`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyAPI.run_pipeline)
* [`IngestProxyAPI.stream_pipeline()`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyAPI.stream_pipeline)
* [`IngestProxyAPI.get_pipeline_trace()`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyAPI.get_pipeline_trace)
* [`IngestProxyAPI.resume_pipeline_hook()`](datalinks.api#datalinks.api.ingest_proxy.IngestProxyAPI.resume_pipeline_hook)
## Submodules
## datalinks.cli module
### *class* datalinks.cli.StandardCLI(name='datalinks-client', description='Infer and link your data!')
Bases: `object`
Command-Line Interface (CLI) wrapper for customizable argument parsing.
Simplifies the creation and usage of the DataLinks CLI by allowing to
pass a custom callback function for additional arguments specific to an
application. It provides a standard set of CLI arguments while enabling
customization through user-defined groups.
* **Variables:**
* **name** – The name of the CLI program.
* **description** – The description of the CLI program.
### datalinks.cli.get\_default\_args()
* **Return type:**
`Namespace`
## datalinks.links module
### *class* datalinks.links.MatchType(targetColumns=None)
Bases: `object`
The base type for entity resolution operators.
* **Variables:**
**targetColumns** – A list of column names that are the target for matching. If
None, all columns are used for entity resolution.
#### targetColumns *: List\[str] | None* *= None*
### *class* datalinks.links.ExactMatch(targetColumns=None, minVariation=None, minDistinct=None)
Bases: [`MatchType`](#datalinks.links.MatchType)
Use this match type to evaluate and configure specific exact matching criteria
for the data values.
* **Variables:**
* **minVariation** – Minimum allowable variation in the field to check for matches
(defaults to 0.0).
* **minDistinct** – Minimum percentage of distinct values in the field to check for matches
(defaults to 0.0).
#### minVariation *: float | None* *= None*
#### minDistinct *: float | None* *= None*
### *class* datalinks.links.GeoMatch(targetColumns=None, distance=None, distanceUnit=None)
Bases: [`MatchType`](#datalinks.links.MatchType)
use this match type to check for matches in fields that represent geographical
attributes.
* **Variables:**
* **distance** – The maximum distance value for the geographical match (defaults to 2.0)
* **distanceUnit** – The unit of measurement for the distance, such as
kilometers or miles (defaults to ‘kilometers’).
#### distance *: float | None* *= None*
#### distanceUnit *: str | None* *= None*
### *class* datalinks.links.EntityResolutionTypes(value)
Bases: `StrEnum`
Enumerates the various resolution strategies for handling
matching or reconciliation of entity data. Each enumeration value
specifies a particular method or approach used for determining
entity equivalence or correspondence.
* **Variables:**
* **ExactMatch** – Used when entities are determined to be equivalent
based on exact value matches without any approximation.
* **GeoMatch** – Used when entities are matched based on their
geographical location or proximity.
#### ExactMatch *= 'ExactMatch'*
#### GeoMatch *= 'GeoMatch'*
### *class* datalinks.links.MatchTypeConfig(exact\_match=None, geo\_match=None)
Bases: `object`
Encapsulates configuration related to different types of entity resolution matches.
This class is designed to store, manage, and provide access to various entity resolution
match type configurations, such as ExactMatch and GeoMatch. It maintains internal
state for these match types and also provides access to a consolidated configuration
in dictionary format.
* **Variables:**
**matchTypes** – A dictionary mapping entity resolution types to their respective match
configurations (e.g., ExactMatch, GeoMatch).
#### matchTypes *: dict\[[EntityResolutionTypes](#datalinks.links.EntityResolutionTypes), [MatchType](#datalinks.links.MatchType) | None]*
#### *property* config *: Dict\[str, Dict] | None*
## datalinks.loaders module
### *class* datalinks.loaders.Loader(folder)
Bases: `ABC`
Abstract base class for loading resources from a specified folder.
It serves as a template for loading files or other resources
while maintaining consistency across different implementations.
* **Variables:**
**folder** – Path to the folder from which resources will be loaded.
#### *abstractmethod* load\_from\_folder()
### *class* datalinks.loaders.JSONLoader(folder)
Bases: [`Loader`](#datalinks.loaders.Loader)
A loader for processing JSON files in a specified folder.
Iterates through all .json files within a given folder,
parses their content, and processes each JSON object into
a standardized format using the load\_item method.
* **Variables:**
**folder** – Path to the folder containing JSON files. All .json files
in this folder will be processed.
#### load\_from\_folder()
* **Return type:**
`List`\[`Dict`\[`str`, `str`]]
#### *abstractmethod* load\_item(row)
* **Return type:**
`Dict`\[`str`, `str`]
## datalinks.models module
Request and response models, re-exported for type annotations.
Generated by make codegen from spec/openapi.yaml. Do not edit.
### *class* datalinks.models.AbortFileUploadResponse(\*\*data)
Bases: `ResponseModel`
#### session\_id *: Annotated\[[UploadSessionId](#datalinks.models.UploadSessionId), FieldInfo(annotation=NoneType, required=True, alias='sessionId', alias\_priority=2)]*
#### session\_status *: Annotated\[SessionStatus, FieldInfo(annotation=NoneType, required=True, alias='sessionStatus', alias\_priority=2, description='Status of the upload session.', examples=\['preparing'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AbortMultipartRequest(\*\*data)
Bases: `RequestModel`
#### upload\_id *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='uploadId', alias\_priority=2, description='Upload ID to abort.', examples=\['2\~VmxqYXRlRDZWfjJrRWRiZ2FEQXk3dHI4DGhGLygtTmQNXEZPQXFzMVZOZTUgdEdrXCEyNStkQUp3U3Q2RjJqSkV4MFNBOWxJCk0yIHEkfcQ'])]*
#### key *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='S3 object key to abort.', examples=\['uploads/data/20240301-143022-uuid.json'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AccessGrant(\*\*data)
Bases: `ResponseModel`
#### username *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Username of the user that has access.', examples=\['alice'])]*
#### role *: Annotated\[Role, FieldInfo(annotation=NoneType, required=True, description='Access level granted to the user.', examples=\['editor'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AddLinkRequest(\*\*data)
Bases: `RequestModel`
#### from\_ *: ApiStringColumnIdentifier, FieldInfo(annotation=NoneType, required=True, alias='from', alias\_priority=2, examples=\[\{'username': 'johndoe', 'namespace': 'cinematography', 'dataset': 'movies', 'columnName': 'director'}])]*
#### to *: ApiStringColumnIdentifier, FieldInfo(annotation=NoneType, required=True, examples=\[\{'username': 'janedoe', 'namespace': 'entertainment', 'dataset': 'films', 'columnName': 'director\_name'}])]*
#### options *: Annotated\[[ExactMatchConfig](#datalinks.models.ExactMatchConfig) | [GeoMatchConfig](#datalinks.models.GeoMatchConfig) | None, FieldInfo(annotation=NoneType, required=True, description='Flexible options map for match configuration.')]*
#### match\_type *: Annotated\[MatchType, FieldInfo(annotation=NoneType, required=True, alias='matchType', alias\_priority=2, description='Type of match to create between the columns.', examples=\['ExactMatch'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AgentRunFeedbackRequest(\*\*data)
Bases: `RequestModel`
#### thumbs\_up *: Annotated\[bool, FieldInfo(annotation=NoneType, required=True, alias='thumbsUp', alias\_priority=2, description='True for a thumbs up, false for a thumbs down.')]*
#### comment *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='Optional free-text comment accompanying the rating.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AgentRunStatusResponse(\*\*data)
Bases: `ResponseModel`
#### run\_id *: Annotated\[UUID, FieldInfo(annotation=NoneType, required=True, alias='runId', alias\_priority=2)]*
#### agent\_kind *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='agentKind', alias\_priority=2, examples=\['auto\_rag'])]*
#### question *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The original prompt that started the conversation.')]*
#### status *: Status*
#### can\_resume *: Annotated\[bool, FieldInfo(annotation=NoneType, required=True, alias='canResume', alias\_priority=2, description='True only when the status is `running` AND the checkpoint blob loads cleanly.')]*
#### last\_node\_id *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='lastNodeId', alias\_priority=2)]*
#### error\_message *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='errorMessage', alias\_priority=2)]*
#### result *: Annotated\[dict\[str, Any] | None, FieldInfo(annotation=NoneType, required=True, description="Rendered AutoRag result for completed runs, in the same shape as `POST /query/autorag`'s response body.")]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ApiDatasetDescription(\*\*data)
Bases: `ResponseModel`
#### name *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Name of the dataset.')]*
#### namespace *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Name of the namespace the dataset belongs to.')]*
#### name\_with\_namespace *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='nameWithNamespace', alias\_priority=2, description='The name concatenated with the namespace.', deprecated=True)]*
Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
#### msg
The deprecation message to be emitted.
#### wrapped\_property
The property instance if the deprecated field is a computed field, or None.
#### field\_name
The name of the field being deprecated.
#### owner *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The username associated with the data.')]*
#### user *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='The username associated with the data.', deprecated=True)]*
Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
#### msg
The deprecation message to be emitted.
#### wrapped\_property
The property instance if the deprecated field is a computed field, or None.
#### field\_name
The name of the field being deprecated.
#### visibility *: Annotated\[Visibility, FieldInfo(annotation=NoneType, required=True, description='Indicates whether the data is public or private.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ApiDatasetMetadata(\*\*data)
Bases: `ResponseModel`
#### column\_names *: Annotated\[list\[str], FieldInfo(annotation=NoneType, required=True, alias='columnNames', alias\_priority=2, description='List of dataset column names.', examples=\[\['id', 'name', 'age', 'department', 'salary']])]*
#### data\_description *: Annotated\[[DataDescription](#datalinks.models.DataDescription) | None, FieldInfo(annotation=NoneType, required=True, alias='dataDescription', alias\_priority=2)]*
#### schema\_definition *: Annotated\[[SchemaDefinition](#datalinks.models.SchemaDefinition) | None, FieldInfo(annotation=NoneType, required=True, alias='schemaDefinition', alias\_priority=2)]*
#### row\_count *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='rowCount', alias\_priority=2, description='Number of rows in dataset.', examples=\[1250])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ApiLink(\*\*data)
Bases: `ResponseModel`
#### destination *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, examples=\['ri.user.examples.companies.other\_addresses'])]*
#### hits *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, examples=\[5])]*
#### match\_type *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='matchType', alias\_priority=2, examples=\['ExactMatch'])]*
#### origin *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, examples=\['ri.user.examples.companies.nationalities'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ApiNamespace(\*\*data)
Bases: `ResponseModel`
#### name *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The name of the namespace.', examples=\['cinematography'])]*
#### owner *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Username of the namespace owner.', examples=\['datalinks'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ApiStringColumnIdentifier(\*\*data)
Bases: `RequestModel`
#### username *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Username of the dataset owner.', examples=\['johndoe'])]*
#### namespace *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Namespace of the dataset.', examples=\['cinematography'])]*
#### dataset *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Name of the dataset.', examples=\['movies'])]*
#### column\_name *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='columnName', alias\_priority=2, description='Column name in the dataset.', examples=\['director'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagAnswerEvent(\*\*data)
Bases: `ResponseModel`
#### response *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description="The final synthesized answer to the user's question.")]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagAnswerStartedEvent(\*\*data)
Bases: `ResponseModel`
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagErrorEvent(\*\*data)
Bases: `ResponseModel`
#### message *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Error message describing what went wrong.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagPlanEvent(\*\*data)
Bases: `ResponseModel`
#### steps *: Annotated\[list\[str], FieldInfo(annotation=NoneType, required=True, description='List of step descriptions that will be executed.')]*
#### reasoning *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The reasoning produced by the LLM when generating the plan.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagPlanningStartedEvent(\*\*data)
Bases: `ResponseModel`
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagQueryExecutingEvent(\*\*data)
Bases: `ResponseModel`
#### index *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, description='Zero-based index of the step whose query is executing.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagRequest(\*\*data)
Bases: `RequestModel`
#### username *: str*
#### namespace *: str*
#### query *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, examples=\['What do llamas drink to cool themselves down?'])]*
#### helper\_prompt *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='helperPrompt', alias\_priority=2, examples=\["You're a prophet of the alpacalypse. Your task is to spit the word!"])]*
#### model *: str | None*
#### provider *: str | None*
#### models *: [AutoRagStageModels](#datalinks.models.AutoRagStageModels) | None*
#### conversation\_id *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='conversationId', alias\_priority=2, description='Existing conversation to continue. Omit to start a new conversation; the id is returned on the run-started SSE event.')]*
#### web\_search *: Annotated\[bool | None, FieldInfo(annotation=NoneType, required=True, alias='webSearch', alias\_priority=2, description='Override web search for this request: false disables it; omit (or true) to use the deployment default.')]*
#### caller\_context *: Annotated\[[CallerContext](#datalinks.models.CallerContext) | None, FieldInfo(annotation=NoneType, required=True, alias='callerContext', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagResponseData(\*\*data)
Bases: `ResponseModel`
#### response *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description="Final response generated for the user's question.")]*
#### steps *: Annotated\[list\[Step], FieldInfo(annotation=NoneType, required=True, description='List of steps executed during the AutoRAG process.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagStageModel(\*\*data)
Bases: `RequestModel`
#### model *: str | None*
#### provider *: str | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagStageModels(\*\*data)
Bases: `RequestModel`
#### plan *: [AutoRagStageModel](#datalinks.models.AutoRagStageModel) | None*
#### query *: [AutoRagStageModel](#datalinks.models.AutoRagStageModel) | None*
#### answer *: [AutoRagStageModel](#datalinks.models.AutoRagStageModel) | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagStepEvent(\*\*data)
Bases: `ResponseModel`
#### index *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, description='Zero-based index of this step in the plan.')]*
#### instruction *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The instruction generated and executed for this step.')]*
#### query *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The query derived from the instruction.')]*
#### reasoning *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The reasoning produced by the LLM when generating the query for this step.')]*
#### data *: [DataArray](#datalinks.models.DataArray)*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagStepStartedEvent(\*\*data)
Bases: `ResponseModel`
#### index *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, description='Zero-based index of the step that just started.')]*
#### instruction *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The plan instruction for this step.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.AutoRagStreamEvent(root=PydanticUndefined, \*\*data)
Bases: `RootModel[Union[AutoRagPlanEvent, AutoRagStepEvent, AutoRagAnswerEvent, AutoRagErrorEvent, AutoRagPlanningStartedEvent, AutoRagStepStartedEvent, AutoRagQueryExecutingEvent, AutoRagAnswerStartedEvent]]`
#### root *: [AutoRagPlanEvent](#datalinks.models.AutoRagPlanEvent) | [AutoRagStepEvent](#datalinks.models.AutoRagStepEvent) | [AutoRagAnswerEvent](#datalinks.models.AutoRagAnswerEvent) | [AutoRagErrorEvent](#datalinks.models.AutoRagErrorEvent) | [AutoRagPlanningStartedEvent](#datalinks.models.AutoRagPlanningStartedEvent) | [AutoRagStepStartedEvent](#datalinks.models.AutoRagStepStartedEvent) | [AutoRagQueryExecutingEvent](#datalinks.models.AutoRagQueryExecutingEvent) | [AutoRagAnswerStartedEvent](#datalinks.models.AutoRagAnswerStartedEvent)*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CallerContext(\*\*data)
Bases: `RequestModel`
#### turns *: Annotated\[list\[[CallerContextTurn](#datalinks.models.CallerContextTurn)] | None, FieldInfo(annotation=NoneType, required=True, description='Prior exchange in the calling system, oldest first.')]*
#### facts *: Annotated\[list\[[CallerContextFact](#datalinks.models.CallerContextFact)] | None, FieldInfo(annotation=NoneType, required=True, description='Values the calling system asserts. Where a label repeats, the later entry supersedes the earlier one.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CallerContextFact(\*\*data)
Bases: `RequestModel`
#### label *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, examples=\['tenant'])]*
#### value *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, examples=\['acme'])]*
#### source *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='Which upstream agent asserted this fact.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CallerContextTurn(\*\*data)
Bases: `RequestModel`
#### role *: Role2*
#### text *: str*
#### agent\_name *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='agentName', alias\_priority=2, description='Which upstream agent produced this turn. Shown to the model in place of the bare role.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CleanerAgentCodeFile(\*\*data)
Bases: `ResponseModel`
#### name *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Filename of the code file.', examples=\['clean.py'])]*
#### content *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Code file content aka the code.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CleanerAgentCodeFilesResponse(\*\*data)
Bases: `ResponseModel`
#### files *: list\[[CleanerAgentCodeFile](#datalinks.models.CleanerAgentCodeFile)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ConversationDetailResponse(\*\*data)
Bases: `ResponseModel`
#### id *: UUID*
#### title *: str*
#### turns *: list\[[ConversationTurnResponse](#datalinks.models.ConversationTurnResponse)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ConversationListResponse(\*\*data)
Bases: `ResponseModel`
#### conversations *: list\[[ConversationSummaryResponse](#datalinks.models.ConversationSummaryResponse)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ConversationSummaryResponse(\*\*data)
Bases: `ResponseModel`
#### id *: UUID*
#### title *: str*
#### created\_at *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='createdAt', alias\_priority=2, description='Creation time as epoch milliseconds.')]*
#### updated\_at *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='updatedAt', alias\_priority=2, description='Last-updated time as epoch milliseconds.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ConversationTurnResponse(\*\*data)
Bases: `ResponseModel`
#### run\_id *: Annotated\[UUID, FieldInfo(annotation=NoneType, required=True, alias='runId', alias\_priority=2)]*
#### question *: str*
#### status *: Status*
#### result *: Annotated\[dict\[str, Any] | None, FieldInfo(annotation=NoneType, required=True, description="Rendered AutoRag result for completed turns, in the same shape as `POST /query/autorag`'s response body.")]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CreateDatasetRequest(\*\*data)
Bases: `RequestModel`
#### data\_description *: Annotated\[[DataDescription](#datalinks.models.DataDescription) | None, FieldInfo(annotation=NoneType, required=True, alias='dataDescription', alias\_priority=2)]*
#### schema\_definition *: Annotated\[[SchemaDefinition](#datalinks.models.SchemaDefinition) | None, FieldInfo(annotation=NoneType, required=True, alias='schemaDefinition', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CreateTokenRequest(\*\*data)
Bases: `RequestModel`
#### expires\_at *: str | None*
#### name *: str*
#### access\_restricted\_to *: Annotated\[list\[[TokenPermissionEntry](#datalinks.models.TokenPermissionEntry)] | None, FieldInfo(annotation=NoneType, required=True, alias='accessRestrictedTo', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CreatedIngestionTrackingResponse(\*\*data)
Bases: `ResponseModel`
#### id *: Annotated\[UUID, FieldInfo(annotation=NoneType, required=True, description='Ingestion id created for background ingestion.', examples=\['550e8400-e29b-41d4-a716-446655440000'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CurateLinkRequest(\*\*data)
Bases: `RequestModel`
#### namespace *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The namespace containing the dataset(s) to curate.', examples=\['my\_namespace'])]*
#### dataset *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='Optional. If specified, curates links only for this dataset. If omitted, curates all datasets in the namespace.', examples=\['my\_dataset'])]*
#### model *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='Optional. The LLM model to use for curation (e.g., "gpt-4", "claude-3-5-sonnet-20241022").', examples=\['gpt-4'])]*
#### provider *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='Optional. The LLM provider to use (e.g., "openai", "anthropic").', examples=\['openai'])]*
#### activate *: Annotated\[bool, FieldInfo(annotation=NoneType, required=True, description='Optional. If true, the curated links will be activated (added to the ontology). If false, only returns the curated links without activating them.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CurateLinkResponse(\*\*data)
Bases: `ResponseModel`
#### datasets\_processed *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='datasetsProcessed', alias\_priority=2, description='Number of datasets that were processed.', examples=\[5])]*
#### total\_selected *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='totalSelected', alias\_priority=2, description='Total number of links selected by the curator agent.', examples=\[45])]*
#### curated\_links *: Annotated\[list\[[ApiLink](#datalinks.models.ApiLink)] | None, FieldInfo(annotation=NoneType, required=True, alias='curatedLinks', alias\_priority=2, description='The curated links. Only included in the response when activate is false.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.CurrentUserResponse(\*\*data)
Bases: `ResponseModel`
#### id *: str*
#### username *: str*
#### name *: str | None*
#### email *: str | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.DataArray(root=PydanticUndefined, \*\*data)
Bases: `RootCollectionModel[list[dict[str, Any]]]`
#### root *: Annotated\[list\[dict\[str, Any]], FieldInfo(annotation=NoneType, required=True, description='Array of ontology data objects retrieved from queries.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.DataDescription(root=PydanticUndefined, \*\*data)
Bases: `RootModel[str]`
#### root *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='A description of the dataset to help the AI system understand the context.', examples=\['Employee dataset containing demographics and compensation.'], metadata=\[MaxLen(max\_length=10000)])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.DatasetResponse(\*\*data)
Bases: `ResponseModel`
#### dataset *: [ApiDatasetDescription](#datalinks.models.ApiDatasetDescription)*
#### metadata *: [ApiDatasetMetadata](#datalinks.models.ApiDatasetMetadata)*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.DatasetsResponse(root=PydanticUndefined, \*\*data)
Bases: `RootCollectionModel[list[ApiDatasetDescription]]`
#### root *: list\[[ApiDatasetDescription](#datalinks.models.ApiDatasetDescription)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ErrorResponse(\*\*data)
Bases: `ResponseModel`
#### error\_code *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, examples=\['UNAUTHORIZED'])]*
#### error\_message *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, examples=\[''])]*
#### message *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, examples=\['Missing authentication token'])]*
#### sub\_errors *: list\[SubError] | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ExactMatchConfig(\*\*data)
Bases: `RequestModel`
#### min\_distinct *: Annotated\[float | None, FieldInfo(annotation=NoneType, required=True, alias='minDistinct', alias\_priority=2)]*
#### min\_variation *: Annotated\[float | None, FieldInfo(annotation=NoneType, required=True, alias='minVariation', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FileUploadPrepareSessionRequest(\*\*data)
Bases: `RequestModel`
#### files\_to\_upload *: Annotated\[list\[[FileUploadPrepareSessionRequestFileToUpload](#datalinks.models.FileUploadPrepareSessionRequestFileToUpload)], FieldInfo(annotation=NoneType, required=True, alias='filesToUpload', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FileUploadPrepareSessionRequestFileToUpload(\*\*data)
Bases: `RequestModel`
#### filename *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Name of the file to be uploaded.')]*
#### size *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, description='Size of the file in bytes.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FileUploadPrepareSessionResponse(\*\*data)
Bases: `ResponseModel`
#### session\_id *: Annotated\[[UploadSessionId](#datalinks.models.UploadSessionId), FieldInfo(annotation=NoneType, required=True, alias='sessionId', alias\_priority=2)]*
#### files *: list\[[FileUploadPrepareSessionResponseFileInfo](#datalinks.models.FileUploadPrepareSessionResponseFileInfo)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FileUploadPrepareSessionResponseFileInfo(\*\*data)
Bases: `ResponseModel`
#### file\_id *: Annotated\[[UploadFileId](#datalinks.models.UploadFileId), FieldInfo(annotation=NoneType, required=True, alias='fileId', alias\_priority=2)]*
#### upload\_id *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='uploadId', alias\_priority=2, description='Upload identifier')]*
#### key *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Name of the file to be uploaded.')]*
#### filename *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='File name')]*
#### part\_size *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='partSize', alias\_priority=2, description='Size of the part to be uploaded')]*
#### presigned\_urls *: Annotated\[list\[[FileUploadPrepareSessionResponsePresignedInfo](#datalinks.models.FileUploadPrepareSessionResponsePresignedInfo)], FieldInfo(annotation=NoneType, required=True, alias='presignedUrls', alias\_priority=2, description='List of presiggned ULR where file has to be uploaded')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FileUploadPrepareSessionResponsePresignedInfo(\*\*data)
Bases: `ResponseModel`
#### url *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='URL for the presigned upload.')]*
#### part\_size *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='partSize', alias\_priority=2, description='Size of the part to be uploaded')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FinishFileUploadRequest(\*\*data)
Bases: `RequestModel`
#### upload\_id *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='uploadId', alias\_priority=2)]*
#### key *: str*
#### parts *: list\[[FinishFileUploadRequestParts](#datalinks.models.FinishFileUploadRequestParts)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FinishFileUploadRequestParts(\*\*data)
Bases: `RequestModel`
#### part\_number *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='partNumber', alias\_priority=2)]*
#### etag *: str*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FinishFileUploadResponse(\*\*data)
Bases: `ResponseModel`
#### session\_id *: Annotated\[[UploadSessionId](#datalinks.models.UploadSessionId), FieldInfo(annotation=NoneType, required=True, alias='sessionId', alias\_priority=2)]*
#### session\_status *: Annotated\[SessionStatus, FieldInfo(annotation=NoneType, required=True, alias='sessionStatus', alias\_priority=2, description='Status of the upload session.', examples=\['preparing'])]*
#### file\_id *: Annotated\[[UploadFileId](#datalinks.models.UploadFileId), FieldInfo(annotation=NoneType, required=True, alias='fileId', alias\_priority=2)]*
#### file\_status *: Annotated\[FileStatus, FieldInfo(annotation=NoneType, required=True, alias='fileStatus', alias\_priority=2, description='Status of the file upload.', examples=\['completed'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FinishedMultipartItem(\*\*data)
Bases: `RequestModel`
#### part\_number *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='partNumber', alias\_priority=2, description='Part number (1-based)', examples=\[1])]*
#### etag *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='ETag returned by S3 after uploading the part.', examples=\['d41d8cd98f00b204e9800998ecf8427e'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.FinishedMultipartRequest(\*\*data)
Bases: `RequestModel`
#### upload\_id *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='uploadId', alias\_priority=2, description='Upload ID from the prepare response.', examples=\['2\~VmxqYXRlRDZWfjJrRWRiZ2FEQXk3dHI4DGhGLygtTmQNXEZPQXFzMVZOZTUgdEdrXCEyNStkQUp3U3Q2RjJqSkV4MFNBOWxJCk0yIHEkfcQ'])]*
#### key *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='S3 object key from the prepare response.', examples=\['uploads/data/20240301-143022-uuid.json'])]*
#### parts *: Annotated\[list\[[FinishedMultipartItem](#datalinks.models.FinishedMultipartItem)], FieldInfo(annotation=NoneType, required=True, description='Array of completed parts with their ETags.', metadata=\[MinLen(min\_length=1)])]*
#### name *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='Optional name/label for the ingestion (e.g., original filename).', examples=\['sales-data-2024.csv'])]*
#### infer *: Annotated\[[IngestRequestInfer](#datalinks.models.IngestRequestInfer) | None, FieldInfo(annotation=NoneType, required=True, description='Optional inference pipeline steps to run on each batch of data after reading from S3.')]*
#### link *: Annotated\[[IngestLink](#datalinks.models.IngestLink) | None, FieldInfo(annotation=NoneType, required=True, description='Optional entity resolution configuration.')]*
#### infer\_batch\_size *: Annotated\[int | None, FieldInfo(annotation=NoneType, required=True, alias='inferBatchSize', alias\_priority=2, description='Number of rows per LLM inference batch. Defaults to 1000. Reduce for large files to avoid S3 read timeouts caused by slow LLM processing.', examples=\[100])]*
#### data\_description *: Annotated\[[DataDescription](#datalinks.models.DataDescription) | None, FieldInfo(annotation=NoneType, required=True, alias='dataDescription', alias\_priority=2)]*
#### schema\_definition *: Annotated\[[SchemaDefinition](#datalinks.models.SchemaDefinition) | None, FieldInfo(annotation=NoneType, required=True, alias='schemaDefinition', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.GeoMatchConfig(\*\*data)
Bases: `RequestModel`
#### distance *: float | None*
#### distance\_unit *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='distanceUnit', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.InferSchemaDescriptionRequest(\*\*data)
Bases: `RequestModel`
#### sample *: [DataArray](#datalinks.models.DataArray)*
#### model *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='The inference model to use.')]*
#### provider *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='Provider offering the model.')]*
#### current\_value *: Annotated\[CurrentValue | None, FieldInfo(annotation=NoneType, required=True, alias='currentValue', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.InferredDescriptionResponse(\*\*data)
Bases: `ResponseModel`
#### description *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='The inferred description text.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.InferredSchemaResponse(\*\*data)
Bases: `ResponseModel`
#### schema\_ *: Annotated\[dict\[str, str], FieldInfo(annotation=NoneType, required=True, alias='schema', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.IngestDataResponse(\*\*data)
Bases: `ResponseModel`
#### indexed *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, description='Number of records indexed by this request.', examples=\[42])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.IngestLink(\*\*data)
Bases: `RequestModel`
#### exact\_match *: Annotated\[[ExactMatchConfig](#datalinks.models.ExactMatchConfig) | [ResolutionParameters](#datalinks.models.ResolutionParameters) | None, FieldInfo(annotation=NoneType, required=True, alias='ExactMatch', alias\_priority=2)]*
#### geo\_match *: Annotated\[[GeoMatchConfig](#datalinks.models.GeoMatchConfig) | [ResolutionParameters](#datalinks.models.ResolutionParameters) | None, FieldInfo(annotation=NoneType, required=True, alias='GeoMatch', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.IngestLinksRequest(\*\*data)
Bases: `RequestModel`
#### data *: [DataArray](#datalinks.models.DataArray)*
#### link *: [IngestLink](#datalinks.models.IngestLink) | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.IngestLinksResponse(root=PydanticUndefined, \*\*data)
Bases: `RootCollectionModel[list[ApiLink]]`
#### root *: list\[[ApiLink](#datalinks.models.ApiLink)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.IngestPreviewResponse(root=PydanticUndefined, \*\*data)
Bases: `RootCollectionModel[list[dict[str, Any]]]`
#### root *: Any]], FieldInfo(annotation=NoneType, required=True, examples=\[\[\{'id': '001', '\_\_metadata\_textToTable': 'name -> John Smith, age -> 28, department -> Engineering', 'name': 'John Smith', 'age': '28', 'gender': 'Male', 'department': 'Engineering', 'location': 'New York'}, \{'id': '002', '\_\_metadata\_textToTable': 'name -> Sarah Johnson, age -> 32, department -> Marketing', 'name': 'Sarah Johnson', 'age': '32', 'gender': 'Female', 'department': 'Marketing', 'location': 'California'}, \{'id': '003', 'name': 'Michael Brown', 'age': '45', 'gender': 'Male', 'department': 'Finance', 'location': 'Texas', '\_\_metadata\_normalize': "Normalized department field from 'Fin Dept' to 'Finance'"}]])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.IngestRequest(\*\*data)
Bases: `RequestModel`
#### data *: list\[dict\[str, Any]]*
#### infer *: [IngestRequestInfer](#datalinks.models.IngestRequestInfer) | None*
#### link *: [IngestLink](#datalinks.models.IngestLink) | None*
#### curate *: bool | None*
#### data\_description *: Annotated\[[DataDescription](#datalinks.models.DataDescription) | None, FieldInfo(annotation=NoneType, required=True, alias='dataDescription', alias\_priority=2)]*
#### schema\_definition *: Annotated\[[SchemaDefinition](#datalinks.models.SchemaDefinition) | None, FieldInfo(annotation=NoneType, required=True, alias='schemaDefinition', alias\_priority=2)]*
#### additional\_instructions *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='additionalInstructions', alias\_priority=2, description='Additional instructions to guide the AI system during ingestion.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.IngestRequestInfer(\*\*data)
Bases: `RequestModel`
#### steps *: list\[[IngestRequestInferStep](#datalinks.models.IngestRequestInferStep)] | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.IngestRequestInferStep(root=PydanticUndefined, \*\*data)
Bases: `RootModel[Union[TableStep, RowsStep, NormaliseStep, ValidateStep, ReverseGeoStep, NamedEntitiesStep]]`
#### root *: Annotated\[[TableStep](#datalinks.models.TableStep) | [RowsStep](#datalinks.models.RowsStep) | [NormaliseStep](#datalinks.models.NormaliseStep) | [ValidateStep](#datalinks.models.ValidateStep) | [ReverseGeoStep](#datalinks.models.ReverseGeoStep) | [NamedEntitiesStep](#datalinks.models.NamedEntitiesStep), FieldInfo(annotation=NoneType, required=True, discriminator='type')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.IngestionItemAttributes(\*\*data)
Bases: `ResponseModel`
#### name *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='Filename or identifier for the ingestion.')]*
#### seen\_at *: Annotated\[AwareDatetime | None, FieldInfo(annotation=NoneType, required=True, alias='seenAt', alias\_priority=2, description='Timestamp when the ingestion was first seen.')]*
#### original\_ingestion\_id *: Annotated\[UUID | None, FieldInfo(annotation=NoneType, required=True, alias='originalIngestionId', alias\_priority=2, description="In case of retry, it's the original ingestion id.")]*
#### last\_retry\_at *: Annotated\[AwareDatetime | None, FieldInfo(annotation=NoneType, required=True, alias='lastRetryAt', alias\_priority=2, description='Timestamp of the last retry attempt.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ListIngestionsResponseData(\*\*data)
Bases: `ResponseModel`
#### data *: list\[[ListIngestionsResponseDataItem](#datalinks.models.ListIngestionsResponseDataItem)]*
#### metadata *: [ListIngestionsResponseMetadata](#datalinks.models.ListIngestionsResponseMetadata)*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ListIngestionsResponseDataItem(\*\*data)
Bases: `ResponseModel`
#### id *: UUID*
#### status *: str*
#### status\_message *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='statusMessage', alias\_priority=2)]*
#### processed\_bytes *: Annotated\[int | None, FieldInfo(annotation=NoneType, required=True, alias='processedBytes', alias\_priority=2)]*
#### expected\_total\_bytes *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='expectedTotalBytes', alias\_priority=2)]*
#### processed\_rows *: Annotated\[int | None, FieldInfo(annotation=NoneType, required=True, alias='processedRows', alias\_priority=2)]*
#### attributes *: [IngestionItemAttributes](#datalinks.models.IngestionItemAttributes) | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ListIngestionsResponseMetadata(\*\*data)
Bases: `ResponseModel`
#### before\_created\_at *: Annotated\[AwareDatetime | None, FieldInfo(annotation=NoneType, required=True, alias='beforeCreatedAt', alias\_priority=2, description='Value of the `created_at` field before or equal to which return ingestions records.', metadata=\[Ge(ge=1)])]*
#### page\_size *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='pageSize', alias\_priority=2, metadata=\[Ge(ge=10), Le(le=100)])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ListSessionFilesResponse(\*\*data)
Bases: `ResponseModel`
#### session\_id *: Annotated\[[UploadSessionId](#datalinks.models.UploadSessionId), FieldInfo(annotation=NoneType, required=True, alias='sessionId', alias\_priority=2)]*
#### session\_status *: Annotated\[SessionStatus, FieldInfo(annotation=NoneType, required=True, alias='sessionStatus', alias\_priority=2, description='Status of the upload session.', examples=\['completed'])]*
#### files *: list\[[ListSessionFilesResponseFileInfo](#datalinks.models.ListSessionFilesResponseFileInfo)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ListSessionFilesResponseFileInfo(\*\*data)
Bases: `ResponseModel`
#### file\_id *: Annotated\[[UploadFileId](#datalinks.models.UploadFileId), FieldInfo(annotation=NoneType, required=True, alias='fileId', alias\_priority=2)]*
#### filename *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Original filename provided during prepare.')]*
#### s3\_bucket *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='s3Bucket', alias\_priority=2, description='S3 bucket containing the uploaded object.')]*
#### s3\_key *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='s3Key', alias\_priority=2, description='S3 key of the uploaded object.')]*
#### file\_size *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='fileSize', alias\_priority=2, description='Size of the uploaded file in bytes.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ModifyOntologyRequest(\*\*data)
Bases: `RequestModel`
#### add *: list\[[ApiLink](#datalinks.models.ApiLink)] | None*
#### remove *: list\[[ApiLink](#datalinks.models.ApiLink)] | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.NamedEntitiesStep(\*\*data)
Bases: `RequestModel`
#### type *: Literal\['namedEntities']*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.NormaliseStep(\*\*data)
Bases: `RequestModel`
#### type *: Literal\['normalise']*
#### mode *: Annotated\[Mode, FieldInfo(annotation=NoneType, required=True, description='Normalisation mode.')]*
#### target\_cols *: Annotated\[dict\[str, str] | None, FieldInfo(annotation=NoneType, required=True, alias='targetCols', alias\_priority=2, description='Target columns with their descriptions.')]*
#### data\_description *: Annotated\[[DataDescription](#datalinks.models.DataDescription) | None, FieldInfo(annotation=NoneType, required=True, alias='dataDescription', alias\_priority=2)]*
#### model *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='The LLM model to use for this step.')]*
#### provider *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='The LLM provider to use for this step.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.OntologyResponse(root=PydanticUndefined, \*\*data)
Bases: `RootCollectionModel[list[ApiLink]]`
#### root *: list\[[ApiLink](#datalinks.models.ApiLink)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.OperationResult(root=PydanticUndefined, \*\*data)
Bases: `RootModel[str]`
#### root *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Type for the result of an operation.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.PrepareMultipartRequest(\*\*data)
Bases: `RequestModel`
#### filename *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Name of the file being uploaded.', examples=\['large-data.csv'])]*
#### size *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, description='Size of the file in bytes.', examples=\[73473457])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.PrepareMultipartResponse(\*\*data)
Bases: `ResponseModel`
#### upload\_id *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='uploadId', alias\_priority=2, description='Unique identifier for the multipart upload session.', examples=\['2\~VmxqYXRlRDZWfjJrRWRiZ2FEQXk3dHI4DGhGLygtTmQNXEZPQXFzMVZOZTUgdEdrXCEyNStkQUp3U3Q2RjJqSkV4MFNBOWxJCk0yIHEkfcQ'])]*
#### key *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Generated S3 object key.', examples=\['uploads/data/20240301-143022-uuid.json'])]*
#### part\_size *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='partSize', alias\_priority=2, description='Size of each part in bytes (8MB).', examples=\[8388608])]*
#### presigned\_urls *: Annotated\[list\[[PresignedUrlItem](#datalinks.models.PresignedUrlItem)], FieldInfo(annotation=NoneType, required=True, alias='presignedUrls', alias\_priority=2, description='Array of presigned URLs for multipart upload.', metadata=\[MinLen(min\_length=1), MaxLen(max\_length=10000)])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.PresignedUrlItem(\*\*data)
Bases: `ResponseModel`
#### url *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Presigned URL for uploading this part.', examples=\['[https://my-data-bucket.s3.amazonaws.com/uploads/data/20240301-143022-uuid.json?partNumber=1\&uploadId=](https://my-data-bucket.s3.amazonaws.com/uploads/data/20240301-143022-uuid.json?partNumber=1\&uploadId=)...'])]*
#### size *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, description='Exact number of bytes to upload to this URL. Use this value to slice the file — the last part will be smaller than partSize when the file size is not a multiple of partSize.', examples=\[8388608])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.QueryDataRequest(\*\*data)
Bases: `RequestModel`
#### username *: str*
#### namespace *: str*
#### dataset *: str | None*
#### query *: str*
#### natural\_language\_query *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='naturalLanguageQuery', alias\_priority=2)]*
#### model *: str | None*
#### provider *: str | None*
#### explain\_requested *: Annotated\[bool | None, FieldInfo(annotation=NoneType, required=True, alias='explainRequested', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.QueryExplainResponse(\*\*data)
Bases: `ResponseModel`
#### id *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Id of the step.', examples=\['930f9d0d-eb0e-4e2f-9b1f-91855efe6b94'])]*
#### dataset\_queries\_stat *: Annotated\[[QueryExplainResponseDatasetQueriesStat](#datalinks.models.QueryExplainResponseDatasetQueriesStat), FieldInfo(annotation=NoneType, required=True, alias='datasetQueriesStat', alias\_priority=2)]*
#### start *: Annotated\[AwareDatetime, FieldInfo(annotation=NoneType, required=True, description='Timestamp of the step start.', examples=\['2024-03-01T14:30:22.000Z'])]*
#### duration *: Annotated\[timedelta, FieldInfo(annotation=NoneType, required=True, description='Duration of the step in ISO 8601 format.', examples=\['PT1M30S'])]*
#### children *: list\[[QueryExplainResponse](#datalinks.models.QueryExplainResponse)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.QueryExplainResponseDatasetQueriesStat(\*\*data)
Bases: `ResponseModel`
#### username *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Username that dataset belong to')]*
#### namespace *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Namespace dataset belongs to.')]*
#### dataset *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Name of dataset.')]*
#### result\_count *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='resultCount', alias\_priority=2, description='Number of results returned from the dataset.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.QueryResponseData(\*\*data)
Bases: `ResponseModel`
#### data *: [DataArray](#datalinks.models.DataArray)*
#### explain\_results *: Annotated\[[QueryExplainResponse](#datalinks.models.QueryExplainResponse) | None, FieldInfo(annotation=NoneType, required=True, alias='explainResults', alias\_priority=2)]*
#### metadata *: [QueryResponseMetadata](#datalinks.models.QueryResponseMetadata)*
#### result\_type *: Annotated\[[OperationResult](#datalinks.models.OperationResult) | None, FieldInfo(annotation=NoneType, required=True, alias='resultType', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.QueryResponseMetadata(\*\*data)
Bases: `ResponseModel`
#### count *: Annotated\[int | None, FieldInfo(annotation=NoneType, required=True, description='Number of results returned.')]*
#### executed\_query *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='executedQuery', alias\_priority=2, description='The actual query that was executed.')]*
#### original\_query *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='originalQuery', alias\_priority=2, description='The original query provided by the user.')]*
#### starting\_dataset *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='startingDataset', alias\_priority=2, description='The initial dataset used when evaluating the query. For linked queries, this is the dataset where the query begins.')]*
#### final\_dataset *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='finalDataset', alias\_priority=2, description='The dataset that the query ultimately returns results from. For linked queries, this is the final dataset after following links.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.RequestCleaningRequest(\*\*data)
Bases: `RequestModel`
#### prompts *: Annotated\[list\[str], FieldInfo(annotation=NoneType, required=True, description='Prompts that describe each cleaning step in the intended order.', examples=\[\['Remove extra spaces from firstName and lastName fields.', 'Remove empty values from the age field.']], metadata=\[MinLen(min\_length=1), MaxLen(max\_length=10)])]*
#### output\_namespace *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='outputNamespace', alias\_priority=2, description='Target namespace where the cleaned dataset will be created.', examples=\['all\_about\_movies'])]*
#### output\_dataset\_name *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='outputDatasetName', alias\_priority=2, description='Specifies the name of the cleaned dataset. The service creates this dataset when the cleaning job starts, and the name must be unused in the target namespace.', examples=\['movies\_cleaned'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.RequestCleaningResponse(\*\*data)
Bases: `ResponseModel`
#### cleaning\_task\_id *: Annotated\[UUID, FieldInfo(annotation=NoneType, required=True, alias='cleaningTaskId', alias\_priority=2, description='Id for cleaning task.', examples=\['550e8400-e29b-41d4-a716-446655440000'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ResolutionParameters(\*\*data)
Bases: `RequestModel`
#### bidirectional *: bool | None*
#### target\_columns *: Annotated\[list\[str] | None, FieldInfo(annotation=NoneType, required=True, alias='targetColumns', alias\_priority=2)]*
#### ignore\_private\_columns *: Annotated\[bool | None, FieldInfo(annotation=NoneType, required=True, alias='ignorePrivateColumns', alias\_priority=2)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ReverseGeoStep(\*\*data)
Bases: `RequestModel`
#### type *: Literal\['reverseGeo']*
#### derive\_from *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='deriveFrom', alias\_priority=2, description='Name of the location column.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.RowsStep(\*\*data)
Bases: `RequestModel`
#### type *: Literal\['rows']*
#### derive\_from *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='deriveFrom', alias\_priority=2, description='Name of the column that contains the JSON content.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.SchemaDefinition(root=PydanticUndefined, \*\*data)
Bases: `RootCollectionModel[dict[str, str]]`
#### root *: dict\[str, str]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ShareAccessRequest(\*\*data)
Bases: `RequestModel`
#### username *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, description='Username of the user to grant or revoke access for.', examples=\['alice'])]*
#### role *: Annotated\[Role, FieldInfo(annotation=NoneType, required=True, description="Access level. 'viewer' is read-only; 'editor' is read and write.", examples=\['viewer'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.TableStep(\*\*data)
Bases: `RequestModel`
#### type *: Literal\['table']*
#### derive\_from *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='deriveFrom', alias\_priority=2, description='Name of the source column to extract from.')]*
#### additional\_instructions *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, alias='additionalInstructions', alias\_priority=2, description='Additional instructions to guide the AI system for higher accuracy during extraction.', examples=\["This text contains a table comma separated, but instead of line breaks we are using a ';'."])]*
#### model *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='The LLM model to use for this step.')]*
#### provider *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='The LLM provider to use for this step.')]*
#### replace\_original\_column *: Annotated\[bool, FieldInfo(annotation=NoneType, required=True, alias='replaceOriginalColumn', alias\_priority=2, description='When true, the source column specified in deriveFrom is removed from the output after extraction.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.TokenPermissionEntry(\*\*data)
Bases: `ResponseModel`
#### username *: str*
#### namespace *: str*
#### dataset *: str | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.TokenPermissionsResponse(\*\*data)
Bases: `ResponseModel`
#### restricted *: bool*
#### permissions *: list\[[TokenPermissionEntry](#datalinks.models.TokenPermissionEntry)] | None*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UpdateSortOrderRequest(\*\*data)
Bases: `RequestModel`
#### order *: Annotated\[list\[str], FieldInfo(annotation=NoneType, required=True, description='Ordered list of all column names in the desired sequence.', examples=\[\['id', 'name', 'age', 'department', 'location']])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UploadFileId(root=PydanticUndefined, \*\*data)
Bases: `RootModel[UUID]`
#### root *: Annotated\[UUID, FieldInfo(annotation=NoneType, required=True, description='Unique identifier for a file', examples=\['311fe2cb-1c85-46a4-a8d9-f63a88da44fd'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UploadSessionId(root=PydanticUndefined, \*\*data)
Bases: `RootModel[UUID]`
#### root *: Annotated\[UUID, FieldInfo(annotation=NoneType, required=True, description='Unique identifier for the upload session.', examples=\['24b49ba0-6b5c-4950-94c1-1622939cc481'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UserToken(\*\*data)
Bases: `ResponseModel`
#### created\_at *: str*
#### expires\_at *: str*
#### id *: str*
#### last\_used\_at *: str | None*
#### name *: str*
#### token *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UserTokenResponse(root=PydanticUndefined, \*\*data)
Bases: `RootCollectionModel[list[UserToken]]`
#### root *: list\[[UserToken](#datalinks.models.UserToken)]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UserUsageByDayItem(\*\*data)
Bases: `ResponseModel`
#### date *: Annotated\[date, FieldInfo(annotation=NoneType, required=True, description='The day of the aggregated usage.', examples=\['2021-01-21'])]*
#### tokens\_used *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='tokensUsed', alias\_priority=2, description='Total number of tokens used in this day.', examples=\[125000])]*
#### cost\_usd *: Annotated\[float, FieldInfo(annotation=NoneType, required=True, alias='costUSD', alias\_priority=2, description='Total cost in USD for this day.', examples=\[12.5])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UserUsageByDayMeta(\*\*data)
Bases: `ResponseModel`
#### time\_zone\_for\_aggregation *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='timeZoneForAggregation', alias\_priority=2, description='The timezone used for date aggregation.', examples=\['America/New\_York'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UserUsageByDayResponse(\*\*data)
Bases: `ResponseModel`
#### data *: list\[[UserUsageByDayItem](#datalinks.models.UserUsageByDayItem)]*
#### meta *: [UserUsageByDayMeta](#datalinks.models.UserUsageByDayMeta)*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UserUsageHistoryCursor(\*\*data)
Bases: `ResponseModel`
#### last\_start\_time *: Annotated\[AwareDatetime, FieldInfo(annotation=NoneType, required=True, alias='lastStartTime', alias\_priority=2, description='The start time of the last observation in the current page.', examples=\['2021-01-01T12:12:12.000Z'])]*
#### last\_observation\_id *: Annotated\[str, FieldInfo(annotation=NoneType, required=True, alias='lastObservationId', alias\_priority=2, description='The observation ID of the last observation in the current page.', examples=\['obs\_12345'])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UserUsageHistoryItem(\*\*data)
Bases: `ResponseModel`
#### timestamp *: Annotated\[AwareDatetime, FieldInfo(annotation=NoneType, required=True, description='The timestamp of the usage record.', examples=\['2021-01-01T12:12:12.000Z'])]*
#### tokens\_used *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='tokensUsed', alias\_priority=2, description='Number of tokens used.', examples=\[1241])]*
#### cost\_usd *: Annotated\[float, FieldInfo(annotation=NoneType, required=True, alias='costUSD', alias\_priority=2, description='Cost in USD for this usage.', examples=\[0.0001])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UserUsageHistoryMeta(\*\*data)
Bases: `ResponseModel`
#### page\_cursor *: Annotated\[[UserUsageHistoryCursor](#datalinks.models.UserUsageHistoryCursor) | None, FieldInfo(annotation=NoneType, required=True, alias='pageCursor', alias\_priority=2, description='Cursor for fetching the next page of results.')]*
#### page\_size *: Annotated\[int, FieldInfo(annotation=NoneType, required=True, alias='pageSize', alias\_priority=2, description='Number of items per page.', examples=\[100])]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.UserUsageHistoryResponse(\*\*data)
Bases: `ResponseModel`
#### data *: list\[[UserUsageHistoryItem](#datalinks.models.UserUsageHistoryItem)]*
#### meta *: [UserUsageHistoryMeta](#datalinks.models.UserUsageHistoryMeta)*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'allow', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
### *class* datalinks.models.ValidateStep(\*\*data)
Bases: `RequestModel`
#### type *: Literal\['validate']*
#### mode *: Annotated\[Mode1, FieldInfo(annotation=NoneType, required=True, description='Validation mode.')]*
#### columns *: Annotated\[list\[str], FieldInfo(annotation=NoneType, required=True, description='Columns to validate.')]*
#### model *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='The LLM model to use for this step.')]*
#### provider *: Annotated\[str | None, FieldInfo(annotation=NoneType, required=True, description='The LLM provider to use for this step.')]*
#### model\_config *: ClassVar\[ConfigDict]* *= \{'extra': 'forbid', 'populate\_by\_name': True, 'validate\_by\_alias': True, 'validate\_by\_name': True}*
Configuration for the model, should be a dictionary conforming to \[ConfigDict]\[pydantic.config.ConfigDict].
## datalinks.pipeline module
### *class* datalinks.pipeline.StepTypes(value)
Bases: `Enum`
Represents different types of processing steps for data manipulation.
This class enumerates various distinct processing types that can be
used in DataLinks workflows. Each enumeration value signifies a specific
stage in the broader data-processing pipeline.
#### TABLE *= 'table'*
#### ROWS *= 'rows'*
#### NORMALIZE *= 'normalise'*
#### VALIDATE *= 'validate'*
#### REVERSE\_GEO *= 'reverseGeo'*
### *class* datalinks.pipeline.NormalizeModes(value)
Bases: `Enum`
Enumeration for normalization modes.
This class represents different modes of data normalization
used in the ‘normalize’ step. It provides three options
for normalization: ‘embeddings’ for embedding-level normalization,
‘all-in-one’ for holistic normalization, and ‘field-by-field’
for column-wise normalization.
* **Variables:**
* **EMBEDDINGS** – Mode for normalizing data on an embedding level.
* **ALL\_IN\_ONE** – Mode for normalizing data holistically, treating
the entire dataset as a single entity.
* **FIELD\_BY\_FIELD** – Mode for normalizing data column-by-column,
focusing on individual fields independently.
#### EMBEDDINGS *= 'embeddings'*
#### ALL\_IN\_ONE *= 'all-in-one'*
#### FIELD\_BY\_FIELD *= 'field-by-field'*
### *class* datalinks.pipeline.ValidateModes(value)
Bases: `Enum`
Enumeration class that defines various validation modes.
This class is designed to specify the modes of operation for the ‘validate’
step. The predefined modes include validation by rows, regular
expressions, and fields.
* **Variables:**
* **ROWS** – Validation mode that focuses on rows.
* **REGEX** – Validation mode that utilizes regular expressions.
* **FIELDS** – Validation mode that focuses on columns.
#### ROWS *= 'rows'*
#### REGEX *= 'regex'*
#### FIELDS *= 'fields'*
### *class* datalinks.pipeline.BaseStep
Bases: `object`
Represents the base step within DataLinks.
This class serves as the foundational step structure for various
implementations. It includes methods to transform its data
representation into a dictionary format, custom-processed with specific
rules for attributes of Enum type. It is primarily designed as a metaclass.
* **Variables:**
**step\_type** – The type of the step, categorized using StepTypes.
#### step\_type *: ClassVar\[[StepTypes](#datalinks.pipeline.StepTypes)]*
#### to\_dict()
* **Return type:**
`dict`
### *class* datalinks.pipeline.LlmStep(model, provider)
Bases: [`BaseStep`](#datalinks.pipeline.BaseStep)
Common class for pipeline steps that rely on LLM inference.
* **Variables:**
* **model** – The name of the model to use in the step.
* **provider** – The identifier of the provider to be used (ollama, openai, etc)
#### model *: str | None*
#### provider *: str | None*
### *class* datalinks.pipeline.InferenceStep(derive\_from)
Bases: [`BaseStep`](#datalinks.pipeline.BaseStep)
Represents the ‘infer’ step in the DataLinks workflow.
* **Variables:**
**derive\_from** – The identifier of the source field used in the inference step.
#### derive\_from *: str*
### *class* datalinks.pipeline.ProcessUnstructured(derive\_from, model, provider, helper\_prompt='')
Bases: [`LlmStep`](#datalinks.pipeline.LlmStep), [`InferenceStep`](#datalinks.pipeline.InferenceStep)
Use this step to infer a table from unstructured data.
* **Variables:**
**helper\_prompt** – A string that stores an optional helper
prompt or additional guiding context specific to the table
inference step.
#### step\_type *: ClassVar\[[StepTypes](#datalinks.pipeline.StepTypes)]* *= 'table'*
#### helper\_prompt *: str* *= ''*
### *class* datalinks.pipeline.ProcessStructured(derive\_from)
Bases: [`InferenceStep`](#datalinks.pipeline.InferenceStep)
Use this step to extract data that is already in tabular format (eg.: CSV).
#### step\_type *: ClassVar\[[StepTypes](#datalinks.pipeline.StepTypes)]* *= 'rows'*
### *class* datalinks.pipeline.ReverseGeo(derive\_from)
Bases: [`InferenceStep`](#datalinks.pipeline.InferenceStep)
Use this step to perform reverse geolocation based on the source field.
#### step\_type *: ClassVar\[[StepTypes](#datalinks.pipeline.StepTypes)]* *= 'reverseGeo'*
### *class* datalinks.pipeline.Normalize(model, provider, target\_cols, mode, helper\_prompt='')
Bases: [`LlmStep`](#datalinks.pipeline.LlmStep)
Use this step to attempt normalisation of the extracted column names. Table
inference across different unstructured data blocks may result in different field names
for the same information, hence the need to normalize the column names.
Encapsulates the configuration necessary to perform the ‘normalize’ step.
It specifies the desired target columns, the mode of normalisation, and includes optional
helper prompts to provide further instructions or context.
* **Variables:**
* **target\_cols** – A mapping of the desired column names to an optional
description used as context.
* **mode** – Specifies the normalisation mode to be applied.
* **helper\_prompt** – Optional helper text or prompt information.
#### step\_type *: ClassVar\[[StepTypes](#datalinks.pipeline.StepTypes)]* *= 'normalise'*
#### target\_cols *: Mapping\[str, str | None]*
#### mode *: [NormalizeModes](#datalinks.pipeline.NormalizeModes)*
#### helper\_prompt *: str* *= ''*
### *class* datalinks.pipeline.Validate(model, provider, mode, columns)
Bases: [`LlmStep`](#datalinks.pipeline.LlmStep)
Use this step to add data validation to the inference pipeline.
* **Variables:**
* **mode** – Indicates the mode of validation to be applied.
* **columns** – List containing the column names which are used for validation.
#### step\_type *: ClassVar\[[StepTypes](#datalinks.pipeline.StepTypes)]* *= 'validate'*
#### mode *: [ValidateModes](#datalinks.pipeline.ValidateModes)*
#### columns *: List\[str]*
### *class* datalinks.pipeline.Pipeline(\*steps)
Bases: `object`
Represents a collection of sequential steps. Holds and manages the
sequence of steps used for ingesting and/or enhancing data.
* **Variables:**
**steps** – A collection of steps to be executed in sequence.
#### to\_list()
* **Return type:**
`list`\[`dict`\[`str`, `Any`]]
# Examples
Source: https://docs.landfall.ai/pythonsdk/examples
End-to-end examples demonstrating common DataLinks SDK usage patterns.
The following examples demonstrate end-to-end usage of the DataLinks Python SDK across a range of common scenarios. Each example is self-contained and can be run directly after configuring your environment variables.
***
# Direct Ingestion
Demonstrates the simplest possible ingestion flow: load pre-structured rows from a JSON file and push them to DataLinks **without** any inference pipeline or entity resolution. Use this pattern when your data is already in a clean, tabular format and requires no AI-assisted transformation.
**Components covered:** `DLConfig`, `DataLinksAPI`, `create_space`, `ingest`, `query_data`
```python theme={null}
"""
Direct ingestion example using data/pgproducts.json.
Ingests pre-structured rows directly without any inference pipeline or
entity resolution — the data is stored as-is.
"""
import json
import logging
from pprint import pformat
import datalinks
from datalinks.api import DLConfig
def main():
logging.basicConfig(level=logging.INFO)
dl_config = DLConfig.from_env()
dl_config.namespace = "pg"
dl_config.objectname = "products_direct"
dlapi = datalinks.api.DataLinksAPI(dl_config)
dlapi.create_space()
jsonfile = "data/pgproducts.json"
logging.info(f"Loading {jsonfile}")
with open(jsonfile) as f:
rows = json.load(f)["rows"]
logging.info(f"Ingesting {len(rows)} rows")
result = dlapi.ingest(data=rows)
logging.info(
f"Ingestion result: {len(result.successful)} succeeded, "
f"{len(result.failed)} failed"
)
data = dlapi.query_data()
logging.info(f"Ingested data:\n{pformat(data)}")
if __name__ == "__main__":
main()
```
***
# JSON Ingestion with Pipeline and Entity Resolution
Demonstrates how to ingest structured JSON data using a `ProcessStructured` pipeline step and `ExactMatch` entity resolution. The pipeline instructs DataLinks to derive tabular rows from the JSON `"rows"` key, and entity resolution deduplicates records by exact field matching.
**Components covered:** `DLConfig`, `DataLinksAPI`, `Pipeline`, `ProcessStructured`, `MatchTypeConfig`, `ExactMatch`, `ingest`, `query_data`
```python theme={null}
import json
import logging
from pprint import pformat
import datalinks
from datalinks.api import DLConfig
from datalinks.links import EntityResolutionTypes, MatchTypeConfig, ExactMatch
from datalinks.pipeline import Pipeline, ProcessStructured
def main():
logging.basicConfig(level=logging.INFO)
dl_config = DLConfig.from_env()
# we did not set namespace and object because it varies with each example
dl_config.namespace = "pg"
dl_config.objectname = "products"
# OR
#dl_config = DLConfig(
# host="http://localhost:9001",
# apikey="", # your DataLinks API key
# index="tests",
# namespace="pg",
# objectname="products"
#)
dlapi = datalinks.api.DataLinksAPI(dl_config)
dlapi.create_space()
jsonfile = "data/pgproducts.json"
logging.info(f"Loading json data in {jsonfile}")
with open(jsonfile) as f:
data = json.load(f)
steps = Pipeline(
ProcessStructured(derive_from="rows") # Data is already tabular
)
entity_resolution = MatchTypeConfig(ExactMatch())
result = dlapi.ingest(
data = [data], # supports multiple files
inference_steps=steps,
entity_resolution=entity_resolution,
batch_size=0 # default (no file batching)
)
logging.info(f"Ingestion result:"
f"\nSuccessfully ingested {len(result.successful)} dataset(s)."
f"\nFailed {len(result.failed)} dataset(s).")
data = dlapi.query_data(
model="gpt-4.1-nano-2025-04-14",
provider="openai"
)
logging.info(f"Ingested data:"
f"{pformat(data)}")
if __name__ == '__main__':
main()
```
***
# Tabular Inference from Unstructured Text
Demonstrates a full AI-powered inference pipeline that transforms raw unstructured text into a structured table. The three-step pipeline uses `ProcessUnstructured` to extract an initial table from free-form text, `Normalize` to map columns to a target schema, and `Validate` to verify row integrity — all powered by an LLM.
**Components covered:** `DLConfig`, `DataLinksAPI`, `Pipeline`, `ProcessUnstructured`, `Normalize`, `NormalizeModes`, `Validate`, `ValidateModes`, `MatchTypeConfig`, `ExactMatch`, `ingest`, `query_data`
```python theme={null}
import logging
from pprint import pformat
import datalinks
from datalinks.api import DLConfig
from datalinks.links import MatchTypeConfig, ExactMatch
from datalinks.pipeline import Pipeline, ProcessUnstructured, Normalize, NormalizeModes, Validate, ValidateModes
def main():
logging.basicConfig(level=logging.DEBUG)
dl_config = DLConfig.from_env()
# we did not set namespace and object because it varies with each example
dl_config.namespace = "cinema"
dl_config.objectname = "awards"
# OR
# dl_config = DLConfig(
# host="http://localhost:9001",
# apikey="", # your DataLinks API key
# index="tests",
# namespace="pg",
# objectname="products"
# )
dlapi = datalinks.api.DataLinksAPI(dl_config)
dlapi.create_space()
textfile = "data/movies.txt"
logging.info(f"Loading text in {textfile}")
with open(textfile) as f:
data = {"text": f.read()}
steps = Pipeline(
ProcessUnstructured(
derive_from="text",
helper_prompt="If you find a numeric field use only the value and omit the rest.",
model="gpt-4.1-mini-2025-04-14",
provider="openai"
), # Infer table from unstructured text
Normalize(
target_cols={
"Name": "the actor/actress name",
"Titles": "the list of notable films where the actor was in",
"Oscars": "the number of oscars won"
},
mode=NormalizeModes.ALL_IN_ONE,
model="gpt-4.1-mini-2025-04-14",
provider="openai"
),
Validate(
mode=ValidateModes.ROWS,
columns=["Name", "Titles", "Oscars"],
model="gpt-4.1-mini-2025-04-14",
provider="openai"
)
)
entity_resolution = MatchTypeConfig(ExactMatch())
result = dlapi.ingest(
data = [data], # supports multiple files
inference_steps=steps,
entity_resolution=entity_resolution,
max_attempts=1,
batch_size=0 # default (no file batching)
)
logging.info(f"Ingestion result:"
f"\nSuccessfully ingested {len(result.successful)} dataset(s)."
f"\nFailed {len(result.failed)} dataset(s).")
data = dlapi.query_data(
model="gpt-4.1-mini-2025-04-14",
provider="openai",
include_metadata=True
)
logging.info(f"Ingested data:\n"
f"{pformat(data)}")
if __name__ == '__main__':
main()
```
***
# Multipart Upload
Demonstrates how to upload large files to DataLinks using the multipart upload API. The three-phase flow — prepare, upload, finish — streams the file in chunks directly to presigned S3 URLs, avoiding memory constraints for large datasets. If any part fails, the upload session is aborted to free server-side resources.
**Components covered:** `DLConfig`, `DataLinksAPI`, `prepare_multipart_upload`, `finish_multipart_upload`, `wait_for_ingestion`, `abort_multipart_upload`
```python theme={null}
"""
Multipart upload example using data/pgproducts_mp.json (~10 MB, 2 parts).
Multipart upload is the recommended approach for large files. The flow is:
1. Prepare — DataLinks allocates an upload session and returns presigned S3 URLs
and the server-side partSize to use when splitting the file.
2. Upload — Each file chunk (sized to partSize) is PUT directly to its presigned
URL; S3 returns an ETag per part.
3. Finish — DataLinks assembles the parts and triggers ingestion.
If anything goes wrong during upload, abort is called to clean up the partial upload.
"""
import logging
import os
import requests
import datalinks
from datalinks.api import DLConfig
from datalinks.links import ExactMatch, MatchTypeConfig
def upload_multipart(filepath: str):
dl_config = DLConfig.from_env()
dl_config.namespace = "pg_multipart"
dl_config.objectname = "products"
dlapi = datalinks.api.DataLinksAPI(dl_config)
dlapi.create_space()
filename = os.path.basename(filepath)
size = os.path.getsize(filepath)
logging.info(f"Preparing multipart upload for '{filename}' ({size:,} bytes)")
prepare = dlapi.prepare_multipart_upload(filename, size)
upload_id = prepare["uploadId"]
key = prepare["key"]
part_size = prepare["partSize"]
presigned_urls = [entry["url"] for entry in prepare["presignedUrls"]]
logging.info(
f"Upload session ready: {len(presigned_urls)} part(s) of {part_size:,} bytes, "
f"uploadId={upload_id}"
)
parts = []
part_num = 1
try:
with open(filepath, "rb") as f:
for part_num, url in enumerate(presigned_urls, start=1):
chunk = f.read(part_size)
if not chunk:
break
logging.info(
f"Uploading part {part_num}/{len(presigned_urls)} ({len(chunk):,} bytes)"
)
response = requests.put(url, data=chunk)
response.raise_for_status()
etag = response.headers["ETag"].strip('"')
parts.append({"partNumber": part_num, "etag": etag})
logging.info(f"Part {part_num} uploaded, ETag={etag}")
except Exception as e:
logging.error(f"Upload failed on part {part_num}: {e} — aborting")
dlapi.abort_multipart_upload(upload_id, key)
raise
logging.info("All parts uploaded, finishing ingestion")
entity_resolution = MatchTypeConfig(ExactMatch())
result = dlapi.finish_multipart_upload(
upload_id, key, parts,
name=filename,
entity_resolution=entity_resolution,
)
ingestion_id = result["id"]
logging.info(f"Ingestion queued: id={ingestion_id}")
final = dlapi.wait_for_ingestion(ingestion_id)
logging.info(
f"Ingestion finished: status={final.status!r}, "
f"rows={final.processed_rows}, message={final.status_message!r}"
)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
upload_multipart("data/pgproducts_mp.json")
```
***
# Interactive Assistant
Demonstrates how to build an interactive command-line assistant that answers natural language questions about your data using the `ask` streaming API. The example handles each `AskEvent` type — `plan`, `step`, `answer`, and `error` — and renders responses using the `rich` library for a polished terminal experience.
**Components covered:** `DLConfig`, `DataLinksAPI`, `AskEvent`, `ask` (streaming)
```python theme={null}
# NOTE:
# This example creates an interactive CLI to ask natural language questions about your data.
import datalinks
from datalinks.api import DLConfig, AskEvent
from rich.console import Console
from rich.markdown import Markdown
from rich.text import Text
console = Console()
def handle_event(event: AskEvent) -> None:
if event.type == "plan":
steps = event.data.get("steps", [])
console.print()
for step in steps:
console.print(f" {step}", style="dim")
elif event.type == "step":
idx = event.data.get("index", 0)
reasoning = event.data.get("reasoning", "")
query = event.data.get("query", "")
data = event.data.get("data", [])
console.print(f" [{idx + 1}] {reasoning}", style="dim")
if query:
console.print(f" query: {query}", style="dim italic")
if data:
console.print(f" → {len(data)} record(s) retrieved", style="dim")
elif event.type == "answer":
response = event.data.get("response", "")
console.print()
console.print(Markdown(response))
console.print()
elif event.type == "error":
message = event.data.get("message", "Unknown error")
console.print(f"\nError: {message}\n", style="bold red")
def ask_loop(dlapi: datalinks.api.DataLinksAPI, namespace: str) -> None:
console.print(Text.assemble(("DataLinks Assistant", "bold cyan"), " ", (f"({namespace})", "dim")))
console.print("Type your question and press Enter. Type 'exit' to quit.\n", style="dim")
while True:
try:
question = console.input("[bold yellow]You:[/bold yellow] ").strip()
except (EOFError, KeyboardInterrupt):
console.print()
break
if not question:
continue
if question.lower() == "exit":
break
console.print("[bold cyan]DataLinks:[/bold cyan]", end=" ")
for event in dlapi.ask(question):
handle_event(event)
def main() -> None:
dl_config = DLConfig.from_env()
if not dl_config.namespace:
try:
dl_config.namespace = console.input("Namespace: ").strip()
except (EOFError, KeyboardInterrupt):
console.print()
return
dl_config.objectname = ""
dlapi = datalinks.api.DataLinksAPI(dl_config)
ask_loop(dlapi, dl_config.namespace)
if __name__ == "__main__":
main()
```
***
# GitHub Tickets Ingestion with Auto-Modelled Ontology (Vercel)
Demonstrates how to use the `IngestProxyAPI` to ingest raw GitHub tickets and pull requests and let DataLinks automatically model an ontology from them. A natural-language prompt describes the desired schema — tables for tickets, contributors, triage insights, and applied technologies — and the pipeline surfaces that structure without any manual mapping. Events from the streaming pipeline run are logged as they arrive. The client reconnects automatically if the stream drops, resuming from the last received event.
**Components covered:** `IngestProxyConfig`, `IngestProxyAPI`, `run_pipeline`
**Required env vars:** `DL_INGEST_PROXY`, `DL_API_KEY`, `DL_USERNAME`, `DL_NAMESPACE`
```python theme={null}
import json
import logging
from datalinks.api import IngestProxyAPI, IngestProxyConfig
USER_PROMPT = """
## What we are doing
We are building an assistant to help with the coordinations tasks of a development team. It is effectively a combination of multiple agents that collaborate on a shared memory pool to perform their tasks, these agents include:
- An agent that helps assign tickets and pull requests to the correct developer for implementation or investigation
- An agent that helps to estimate effort of specific tasks
- An agent that does duplicate detection
- An agent that helps generating prompts for Claude Code to implement whenever the ticket is estimated to be of acceptable complexity and size
- A workflow that ingests new tickets and and updates existing tickets
## What are we working with
We're feeding all the data to DataLinks automodeler (note: that's you, and thank you for the help!), and we're expecting an ontology to surface automatically. We're submitting raw GitHub tickets and pull requests.
## What we want
We want to design and populate an ontology for agents to write and read from, it should have at least the following tables:
1. **Tickets**: Core ticket or PR identity — ticket_id, title, body, labels, state, lock status, reporter, ticket_url.
2. **Contributors**: People/orgs involved (reporter, assignees, closers). Track roles and contributor ids and usernames.
3. **Triage**: Triage insights — inferred priority (Low/Med/High), effort size (Low/Med/High), technology stack, risk level, labels, and any other useful signals.
4. **Applied Technologies** - What are the dependencies and technologies a specific ticket has. A single ticket may have multiple technologies or frameworks, we want them to be represented here.
5. **Additional tables** as needed, we expect the ontology to evolve as new data comes in.
Rules:
- Use identifiers always as string, never as numbers.
- Always include the available timestamps
"""
def main():
logging.basicConfig(level=logging.INFO)
config = IngestProxyConfig.from_env()
config.namespace = "vercel"
proxy = IngestProxyAPI(config)
with open("data/vercel100.json") as f:
data = json.load(f)
logging.info(f"Starting pipeline run for {len(data)} records")
with proxy.run_pipeline(
data=data,
user_prompt=USER_PROMPT,
) as run:
logging.info(f"Run ID: {run.run_id}")
for event in run:
logging.info(f"Event: {event}")
logging.info("Pipeline run complete")
if __name__ == "__main__":
main()
```
***
# A2A Client (Agent2Agent Protocol)
```python theme={null}
# This example talks to a DataLinks agent over the Agent2Agent (A2A) protocol
# using the official A2A Python SDK (`a2a-sdk`)
#
# DataLinks exposes every agent as a standard A2A 1.0 server:
#
# GET {host}/a2a -> list agents (public)
# GET {host}/a2a/{agent}/.well-known/agent-card.json -> AgentCard (public)
# POST {host}/a2a/{agent} -> JSON-RPC (bearer auth)
#
# So any A2A-compliant client can drive it. Here we:
# 1. discover the available agents,
# 2. resolve the chosen agent's AgentCard,
# 3. wire the DataLinks API key as the bearer credential for the card's
# declared security scheme(s),
# 4. stream a natural-language question, passing the target namespace in the
# message metadata (the server reads `message.metadata.namespace`).
#
# Install the extra dependency first:
# uv pip install a2a-sdk httpx
#
# Run it (reads DL_HOST / DL_API_KEY / DL_NAMESPACE from the environment / .env):
# uv run python examples/a2a_client.py "How many orders were placed today?"
# # optionally pick an agent: DL_A2A_AGENT=nl2ql uv run python examples/a2a_client.py "..."
import asyncio
import os
import sys
from pprint import pformat
from uuid import uuid4
import httpx
from google.protobuf.json_format import MessageToDict
from google.protobuf.struct_pb2 import Struct
from a2a.client import A2ACardResolver, AuthInterceptor, ClientConfig, ClientFactory
from a2a.client import ClientCallContext, InMemoryContextCredentialStore
from a2a.types import Message, Part, Role, SendMessageConfiguration, SendMessageRequest, TaskState
from datalinks.api import DLConfig
SESSION_ID = "datalinks-a2a-example"
async def list_agents(http: httpx.AsyncClient, host: str) -> list[dict]:
"""Return the agents DataLinks exposes over A2A (the public `/a2a` listing)."""
resp = await http.get(f"{host}/a2a")
resp.raise_for_status()
return resp.json()
def render(response) -> None:
"""Print a single streamed A2A event.
A2A streaming yields a `StreamResponse` whose `payload` oneof is one of
task / message / status_update / artifact_update. We pull text out of
whichever variant arrived.
"""
kind = response.WhichOneof("payload")
if kind == "message":
_print_parts(response.message.parts)
elif kind == "artifact_update":
artifact = response.artifact_update.artifact
print(f"\n[{artifact.name or artifact.artifact_id}]")
_print_parts(artifact.parts)
elif kind == "status_update":
status = response.status_update.status
print(f"\n[status: {TaskState.Name(status.state)}]", flush=True)
if status.HasField("message"):
_print_parts(status.message.parts)
elif kind == "task":
task = response.task
print(f"\n[task {task.id}: {TaskState.Name(task.status.state)}]", flush=True)
for artifact in task.artifacts:
print(f"[{artifact.name or artifact.artifact_id}]")
_print_parts(artifact.parts)
def _pprint(value, indent: str = "") -> None:
"""Pretty-print a nested dict/list, prefixing every line with `indent`."""
text = pformat(value, width=100, sort_dicts=False)
for line in text.splitlines():
print(f"{indent}{line}")
def _print_parts(parts) -> None:
# The nl2ql agent returns the generated query as a TextPart and the result
# rows as a DataPart (`part.data`). Handling only text drops the rows — which
# is why the query was the only thing showing up.
for part in parts:
content = part.WhichOneof("content")
if content == "text":
print(part.text)
elif content == "data":
payload = MessageToDict(part.data) # protobuf Value -> native dict/list
rows = payload.get("rows") if isinstance(payload, dict) else None
if rows is not None:
print(f" {len(rows)} row(s):")
_pprint(rows, indent=" ")
else:
_pprint(payload, indent=" ")
async def ask(host: str, api_key: str, namespace: str, agent_name: str, question: str) -> None:
async with httpx.AsyncClient(timeout=httpx.Timeout(120.0)) as http:
# 1. Resolve the agent's AgentCard from its discovery document.
agent_base = f"{host}/a2a/{agent_name}"
card = await A2ACardResolver(http, base_url=agent_base).get_agent_card()
# 2. Register the bearer credential for whatever scheme(s) the card declares
# (DataLinks uses bearerAuth / openId — both are sent as `Bearer `).
store = InMemoryContextCredentialStore()
for requirement in card.security_requirements:
for scheme_name in requirement.schemes:
await store.set_credentials(SESSION_ID, scheme_name, api_key)
# 3. Build a streaming client that injects the credential on every call.
factory = ClientFactory(ClientConfig(httpx_client=http, streaming=True))
client = factory.create(card, interceptors=[AuthInterceptor(store)])
# 4. Build the message; the target namespace travels in the metadata.
metadata = Struct()
metadata.update({"namespace": namespace})
request = SendMessageRequest(
message=Message(
message_id=str(uuid4()),
role=Role.ROLE_USER,
parts=[Part(text=question)],
metadata=metadata,
),
configuration=SendMessageConfiguration(accepted_output_modes=["text"]),
)
# 5. Stream the answer. The ClientCallContext.sessionId matches the
# credential store key set above.
context = ClientCallContext(state={"sessionId": SESSION_ID})
print(f"Q: {question}\nA: ", end="", flush=True)
async for response in client.send_message(request, context=context):
render(response)
print()
async def main() -> None:
config = DLConfig.from_env()
question = " ".join(sys.argv[1:]) or "What can you tell me about this dataset?"
async with httpx.AsyncClient(timeout=httpx.Timeout(30.0)) as http:
agents = await list_agents(http, config.host)
if not agents:
print("No A2A agents are exposed by this DataLinks server.", file=sys.stderr)
return
agent_name = os.getenv("DL_A2A_AGENT") or agents[0]["name"]
print(f"Available A2A agents: {', '.join(a['name'] for a in agents)}")
print(f"Using agent: {agent_name} (namespace: {config.namespace})\n")
await ask(config.host, config.apikey, config.namespace, agent_name, question)
if __name__ == "__main__":
asyncio.run(main())
```
# DataLinks Python SDK
Source: https://docs.landfall.ai/pythonsdk/index
Simplify data ingestion, normalization, linking, and querying with the DataLinks Python SDK.
The Python SDK is an internal DataLinks tool that is made available for public use. It is intended for experimentation and lightweight integrations. Feature coverage and update timing may vary. For fully supported functionality and production use, please refer to the DataLinks API documentation.
The **DataLinks Python SDK** is designed to simplify data ingestion, normalization, linking, and querying processes with DataLinks.
It integrates with the DataLinks API to provide a seamless development experience for managing data workflows, including entity resolution and inference steps, with robust configuration options.
This SDK is designed to accelerate the development of applications with DataLinks by wrapping the API integrations with a Pythonic interface, supporting flexible chaining of inference and validation steps.
Get started by [installing the SDK](sdk-install) and viewing the [Quick Start guide](sdk-quickstart).
## Features
* **Ingestion API**: Easily ingest data into namespaces with built-in batching and retry mechanisms.
* **Multipart Upload**: Reliable large file ingestion via chunked S3 uploads with automatic abort on failure.
* **Ingestion Tracking**: Poll for async ingestion completion and progress after multipart uploads.
* **Inference Workflow Management**: Define custom chains of inference and validation steps.
* **Entity Resolution**: Match entities using configurable exact or geo-based matching methods.
* **Namespace Management**: Create and manage namespaces with privacy options.
* **Data Querying**: Query data with options to include/exclude metadata.
* **Custom Loaders**: Load custom data formats like JSON into defined workflows.
* **CLI Tool**: Standardized command-line interface for managing ingestion pipelines quickly.
## Run Unit Tests
Run the tests against your own interpreter:
```shell script theme={null}
tox -e py
```
A bare `tox` runs the full matrix — Python 3.11 through 3.14, plus type checking
and linting — and fails on any interpreter you do not have installed.
## License
**DataLinks Python SDK** is licensed under the MIT License. See the [LICENSE](https://opensource.org/license/mit) file for more details.
## Support
For questions or support, please [contact us](https://datalinks.com/newsletter).
# Modules
Source: https://docs.landfall.ai/pythonsdk/modules
# datalinks
* [datalinks package](datalinks)
* [Subpackages](datalinks#subpackages)
* [datalinks.api package](datalinks.api)
* [`DataLinksAPI`](datalinks.api#datalinks.api.DataLinksAPI)
* [`AsyncDataLinksAPI`](datalinks.api#datalinks.api.AsyncDataLinksAPI)
* [`DataLinksRequestError`](datalinks.api#datalinks.api.DataLinksRequestError)
* [`DLConfig`](datalinks.api#datalinks.api.DLConfig)
* [`AskEvent`](datalinks.api#datalinks.api.AskEvent)
* [`AskStream`](datalinks.api#datalinks.api.AskStream)
* [`AsyncAskStream`](datalinks.api#datalinks.api.AsyncAskStream)
* [`AutoRagResult`](datalinks.api#datalinks.api.AutoRagResult)
* [`IngestionResult`](datalinks.api#datalinks.api.IngestionResult)
* [`IngestProxyAPI`](datalinks.api#datalinks.api.IngestProxyAPI)
* [`IngestProxyConfig`](datalinks.api#datalinks.api.IngestProxyConfig)
* [`PipelineRun`](datalinks.api#datalinks.api.PipelineRun)
* [Submodules](datalinks.api#submodules)
* [datalinks.api.async\_datalinks module](datalinks.api#module-datalinks.api.async_datalinks)
* [datalinks.api.datalinks module](datalinks.api#module-datalinks.api.datalinks)
* [datalinks.api.ingest\_proxy module](datalinks.api#module-datalinks.api.ingest_proxy)
* [Submodules](datalinks#submodules)
* [datalinks.cli module](datalinks#module-datalinks.cli)
* [`StandardCLI`](datalinks#datalinks.cli.StandardCLI)
* [`get_default_args()`](datalinks#datalinks.cli.get_default_args)
* [datalinks.links module](datalinks#module-datalinks.links)
* [`MatchType`](datalinks#datalinks.links.MatchType)
* [`MatchType.targetColumns`](datalinks#datalinks.links.MatchType.targetColumns)
* [`ExactMatch`](datalinks#datalinks.links.ExactMatch)
* [`ExactMatch.minVariation`](datalinks#datalinks.links.ExactMatch.minVariation)
* [`ExactMatch.minDistinct`](datalinks#datalinks.links.ExactMatch.minDistinct)
* [`GeoMatch`](datalinks#datalinks.links.GeoMatch)
* [`GeoMatch.distance`](datalinks#datalinks.links.GeoMatch.distance)
* [`GeoMatch.distanceUnit`](datalinks#datalinks.links.GeoMatch.distanceUnit)
* [`EntityResolutionTypes`](datalinks#datalinks.links.EntityResolutionTypes)
* [`EntityResolutionTypes.ExactMatch`](datalinks#datalinks.links.EntityResolutionTypes.ExactMatch)
* [`EntityResolutionTypes.GeoMatch`](datalinks#datalinks.links.EntityResolutionTypes.GeoMatch)
* [`MatchTypeConfig`](datalinks#datalinks.links.MatchTypeConfig)
* [`MatchTypeConfig.matchTypes`](datalinks#datalinks.links.MatchTypeConfig.matchTypes)
* [`MatchTypeConfig.config`](datalinks#datalinks.links.MatchTypeConfig.config)
* [datalinks.loaders module](datalinks#module-datalinks.loaders)
* [`Loader`](datalinks#datalinks.loaders.Loader)
* [`Loader.load_from_folder()`](datalinks#datalinks.loaders.Loader.load_from_folder)
* [`JSONLoader`](datalinks#datalinks.loaders.JSONLoader)
* [`JSONLoader.load_from_folder()`](datalinks#datalinks.loaders.JSONLoader.load_from_folder)
* [`JSONLoader.load_item()`](datalinks#datalinks.loaders.JSONLoader.load_item)
* [datalinks.models module](datalinks#module-datalinks.models)
* [`AbortFileUploadResponse`](datalinks#datalinks.models.AbortFileUploadResponse)
* [`AbortFileUploadResponse.session_id`](datalinks#datalinks.models.AbortFileUploadResponse.session_id)
* [`AbortFileUploadResponse.session_status`](datalinks#datalinks.models.AbortFileUploadResponse.session_status)
* [`AbortFileUploadResponse.model_config`](datalinks#datalinks.models.AbortFileUploadResponse.model_config)
* [`AbortMultipartRequest`](datalinks#datalinks.models.AbortMultipartRequest)
* [`AbortMultipartRequest.upload_id`](datalinks#datalinks.models.AbortMultipartRequest.upload_id)
* [`AbortMultipartRequest.key`](datalinks#datalinks.models.AbortMultipartRequest.key)
* [`AbortMultipartRequest.model_config`](datalinks#datalinks.models.AbortMultipartRequest.model_config)
* [`AccessGrant`](datalinks#datalinks.models.AccessGrant)
* [`AccessGrant.username`](datalinks#datalinks.models.AccessGrant.username)
* [`AccessGrant.role`](datalinks#datalinks.models.AccessGrant.role)
* [`AccessGrant.model_config`](datalinks#datalinks.models.AccessGrant.model_config)
* [`AddLinkRequest`](datalinks#datalinks.models.AddLinkRequest)
* [`AddLinkRequest.from_`](datalinks#datalinks.models.AddLinkRequest.from_)
* [`AddLinkRequest.to`](datalinks#datalinks.models.AddLinkRequest.to)
* [`AddLinkRequest.options`](datalinks#datalinks.models.AddLinkRequest.options)
* [`AddLinkRequest.match_type`](datalinks#datalinks.models.AddLinkRequest.match_type)
* [`AddLinkRequest.model_config`](datalinks#datalinks.models.AddLinkRequest.model_config)
* [`AgentRunFeedbackRequest`](datalinks#datalinks.models.AgentRunFeedbackRequest)
* [`AgentRunFeedbackRequest.thumbs_up`](datalinks#datalinks.models.AgentRunFeedbackRequest.thumbs_up)
* [`AgentRunFeedbackRequest.comment`](datalinks#datalinks.models.AgentRunFeedbackRequest.comment)
* [`AgentRunFeedbackRequest.model_config`](datalinks#datalinks.models.AgentRunFeedbackRequest.model_config)
* [`AgentRunStatusResponse`](datalinks#datalinks.models.AgentRunStatusResponse)
* [`AgentRunStatusResponse.run_id`](datalinks#datalinks.models.AgentRunStatusResponse.run_id)
* [`AgentRunStatusResponse.agent_kind`](datalinks#datalinks.models.AgentRunStatusResponse.agent_kind)
* [`AgentRunStatusResponse.question`](datalinks#datalinks.models.AgentRunStatusResponse.question)
* [`AgentRunStatusResponse.status`](datalinks#datalinks.models.AgentRunStatusResponse.status)
* [`AgentRunStatusResponse.can_resume`](datalinks#datalinks.models.AgentRunStatusResponse.can_resume)
* [`AgentRunStatusResponse.last_node_id`](datalinks#datalinks.models.AgentRunStatusResponse.last_node_id)
* [`AgentRunStatusResponse.error_message`](datalinks#datalinks.models.AgentRunStatusResponse.error_message)
* [`AgentRunStatusResponse.result`](datalinks#datalinks.models.AgentRunStatusResponse.result)
* [`AgentRunStatusResponse.model_config`](datalinks#datalinks.models.AgentRunStatusResponse.model_config)
* [`ApiDatasetDescription`](datalinks#datalinks.models.ApiDatasetDescription)
* [`ApiDatasetDescription.name`](datalinks#datalinks.models.ApiDatasetDescription.name)
* [`ApiDatasetDescription.namespace`](datalinks#datalinks.models.ApiDatasetDescription.namespace)
* [`ApiDatasetDescription.name_with_namespace`](datalinks#datalinks.models.ApiDatasetDescription.name_with_namespace)
* [`ApiDatasetDescription.owner`](datalinks#datalinks.models.ApiDatasetDescription.owner)
* [`ApiDatasetDescription.user`](datalinks#datalinks.models.ApiDatasetDescription.user)
* [`ApiDatasetDescription.visibility`](datalinks#datalinks.models.ApiDatasetDescription.visibility)
* [`ApiDatasetDescription.model_config`](datalinks#datalinks.models.ApiDatasetDescription.model_config)
* [`ApiDatasetMetadata`](datalinks#datalinks.models.ApiDatasetMetadata)
* [`ApiDatasetMetadata.column_names`](datalinks#datalinks.models.ApiDatasetMetadata.column_names)
* [`ApiDatasetMetadata.data_description`](datalinks#datalinks.models.ApiDatasetMetadata.data_description)
* [`ApiDatasetMetadata.schema_definition`](datalinks#datalinks.models.ApiDatasetMetadata.schema_definition)
* [`ApiDatasetMetadata.row_count`](datalinks#datalinks.models.ApiDatasetMetadata.row_count)
* [`ApiDatasetMetadata.model_config`](datalinks#datalinks.models.ApiDatasetMetadata.model_config)
* [`ApiLink`](datalinks#datalinks.models.ApiLink)
* [`ApiLink.destination`](datalinks#datalinks.models.ApiLink.destination)
* [`ApiLink.hits`](datalinks#datalinks.models.ApiLink.hits)
* [`ApiLink.match_type`](datalinks#datalinks.models.ApiLink.match_type)
* [`ApiLink.origin`](datalinks#datalinks.models.ApiLink.origin)
* [`ApiLink.model_config`](datalinks#datalinks.models.ApiLink.model_config)
* [`ApiNamespace`](datalinks#datalinks.models.ApiNamespace)
* [`ApiNamespace.name`](datalinks#datalinks.models.ApiNamespace.name)
* [`ApiNamespace.owner`](datalinks#datalinks.models.ApiNamespace.owner)
* [`ApiNamespace.model_config`](datalinks#datalinks.models.ApiNamespace.model_config)
* [`ApiStringColumnIdentifier`](datalinks#datalinks.models.ApiStringColumnIdentifier)
* [`ApiStringColumnIdentifier.username`](datalinks#datalinks.models.ApiStringColumnIdentifier.username)
* [`ApiStringColumnIdentifier.namespace`](datalinks#datalinks.models.ApiStringColumnIdentifier.namespace)
* [`ApiStringColumnIdentifier.dataset`](datalinks#datalinks.models.ApiStringColumnIdentifier.dataset)
* [`ApiStringColumnIdentifier.column_name`](datalinks#datalinks.models.ApiStringColumnIdentifier.column_name)
* [`ApiStringColumnIdentifier.model_config`](datalinks#datalinks.models.ApiStringColumnIdentifier.model_config)
* [`AutoRagAnswerEvent`](datalinks#datalinks.models.AutoRagAnswerEvent)
* [`AutoRagAnswerEvent.response`](datalinks#datalinks.models.AutoRagAnswerEvent.response)
* [`AutoRagAnswerEvent.model_config`](datalinks#datalinks.models.AutoRagAnswerEvent.model_config)
* [`AutoRagAnswerStartedEvent`](datalinks#datalinks.models.AutoRagAnswerStartedEvent)
* [`AutoRagAnswerStartedEvent.model_config`](datalinks#datalinks.models.AutoRagAnswerStartedEvent.model_config)
* [`AutoRagErrorEvent`](datalinks#datalinks.models.AutoRagErrorEvent)
* [`AutoRagErrorEvent.message`](datalinks#datalinks.models.AutoRagErrorEvent.message)
* [`AutoRagErrorEvent.model_config`](datalinks#datalinks.models.AutoRagErrorEvent.model_config)
* [`AutoRagPlanEvent`](datalinks#datalinks.models.AutoRagPlanEvent)
* [`AutoRagPlanEvent.steps`](datalinks#datalinks.models.AutoRagPlanEvent.steps)
* [`AutoRagPlanEvent.reasoning`](datalinks#datalinks.models.AutoRagPlanEvent.reasoning)
* [`AutoRagPlanEvent.model_config`](datalinks#datalinks.models.AutoRagPlanEvent.model_config)
* [`AutoRagPlanningStartedEvent`](datalinks#datalinks.models.AutoRagPlanningStartedEvent)
* [`AutoRagPlanningStartedEvent.model_config`](datalinks#datalinks.models.AutoRagPlanningStartedEvent.model_config)
* [`AutoRagQueryExecutingEvent`](datalinks#datalinks.models.AutoRagQueryExecutingEvent)
* [`AutoRagQueryExecutingEvent.index`](datalinks#datalinks.models.AutoRagQueryExecutingEvent.index)
* [`AutoRagQueryExecutingEvent.model_config`](datalinks#datalinks.models.AutoRagQueryExecutingEvent.model_config)
* [`AutoRagRequest`](datalinks#datalinks.models.AutoRagRequest)
* [`AutoRagRequest.username`](datalinks#datalinks.models.AutoRagRequest.username)
* [`AutoRagRequest.namespace`](datalinks#datalinks.models.AutoRagRequest.namespace)
* [`AutoRagRequest.query`](datalinks#datalinks.models.AutoRagRequest.query)
* [`AutoRagRequest.helper_prompt`](datalinks#datalinks.models.AutoRagRequest.helper_prompt)
* [`AutoRagRequest.model`](datalinks#datalinks.models.AutoRagRequest.model)
* [`AutoRagRequest.provider`](datalinks#datalinks.models.AutoRagRequest.provider)
* [`AutoRagRequest.models`](datalinks#datalinks.models.AutoRagRequest.models)
* [`AutoRagRequest.conversation_id`](datalinks#datalinks.models.AutoRagRequest.conversation_id)
* [`AutoRagRequest.web_search`](datalinks#datalinks.models.AutoRagRequest.web_search)
* [`AutoRagRequest.caller_context`](datalinks#datalinks.models.AutoRagRequest.caller_context)
* [`AutoRagRequest.model_config`](datalinks#datalinks.models.AutoRagRequest.model_config)
* [`AutoRagResponseData`](datalinks#datalinks.models.AutoRagResponseData)
* [`AutoRagResponseData.response`](datalinks#datalinks.models.AutoRagResponseData.response)
* [`AutoRagResponseData.steps`](datalinks#datalinks.models.AutoRagResponseData.steps)
* [`AutoRagResponseData.model_config`](datalinks#datalinks.models.AutoRagResponseData.model_config)
* [`AutoRagStageModel`](datalinks#datalinks.models.AutoRagStageModel)
* [`AutoRagStageModel.model`](datalinks#datalinks.models.AutoRagStageModel.model)
* [`AutoRagStageModel.provider`](datalinks#datalinks.models.AutoRagStageModel.provider)
* [`AutoRagStageModel.model_config`](datalinks#datalinks.models.AutoRagStageModel.model_config)
* [`AutoRagStageModels`](datalinks#datalinks.models.AutoRagStageModels)
* [`AutoRagStageModels.plan`](datalinks#datalinks.models.AutoRagStageModels.plan)
* [`AutoRagStageModels.query`](datalinks#datalinks.models.AutoRagStageModels.query)
* [`AutoRagStageModels.answer`](datalinks#datalinks.models.AutoRagStageModels.answer)
* [`AutoRagStageModels.model_config`](datalinks#datalinks.models.AutoRagStageModels.model_config)
* [`AutoRagStepEvent`](datalinks#datalinks.models.AutoRagStepEvent)
* [`AutoRagStepEvent.index`](datalinks#datalinks.models.AutoRagStepEvent.index)
* [`AutoRagStepEvent.instruction`](datalinks#datalinks.models.AutoRagStepEvent.instruction)
* [`AutoRagStepEvent.query`](datalinks#datalinks.models.AutoRagStepEvent.query)
* [`AutoRagStepEvent.reasoning`](datalinks#datalinks.models.AutoRagStepEvent.reasoning)
* [`AutoRagStepEvent.data`](datalinks#datalinks.models.AutoRagStepEvent.data)
* [`AutoRagStepEvent.model_config`](datalinks#datalinks.models.AutoRagStepEvent.model_config)
* [`AutoRagStepStartedEvent`](datalinks#datalinks.models.AutoRagStepStartedEvent)
* [`AutoRagStepStartedEvent.index`](datalinks#datalinks.models.AutoRagStepStartedEvent.index)
* [`AutoRagStepStartedEvent.instruction`](datalinks#datalinks.models.AutoRagStepStartedEvent.instruction)
* [`AutoRagStepStartedEvent.model_config`](datalinks#datalinks.models.AutoRagStepStartedEvent.model_config)
* [`AutoRagStreamEvent`](datalinks#datalinks.models.AutoRagStreamEvent)
* [`AutoRagStreamEvent.root`](datalinks#datalinks.models.AutoRagStreamEvent.root)
* [`AutoRagStreamEvent.model_config`](datalinks#datalinks.models.AutoRagStreamEvent.model_config)
* [`CallerContext`](datalinks#datalinks.models.CallerContext)
* [`CallerContext.turns`](datalinks#datalinks.models.CallerContext.turns)
* [`CallerContext.facts`](datalinks#datalinks.models.CallerContext.facts)
* [`CallerContext.model_config`](datalinks#datalinks.models.CallerContext.model_config)
* [`CallerContextFact`](datalinks#datalinks.models.CallerContextFact)
* [`CallerContextFact.label`](datalinks#datalinks.models.CallerContextFact.label)
* [`CallerContextFact.value`](datalinks#datalinks.models.CallerContextFact.value)
* [`CallerContextFact.source`](datalinks#datalinks.models.CallerContextFact.source)
* [`CallerContextFact.model_config`](datalinks#datalinks.models.CallerContextFact.model_config)
* [`CallerContextTurn`](datalinks#datalinks.models.CallerContextTurn)
* [`CallerContextTurn.role`](datalinks#datalinks.models.CallerContextTurn.role)
* [`CallerContextTurn.text`](datalinks#datalinks.models.CallerContextTurn.text)
* [`CallerContextTurn.agent_name`](datalinks#datalinks.models.CallerContextTurn.agent_name)
* [`CallerContextTurn.model_config`](datalinks#datalinks.models.CallerContextTurn.model_config)
* [`CleanerAgentCodeFile`](datalinks#datalinks.models.CleanerAgentCodeFile)
* [`CleanerAgentCodeFile.name`](datalinks#datalinks.models.CleanerAgentCodeFile.name)
* [`CleanerAgentCodeFile.content`](datalinks#datalinks.models.CleanerAgentCodeFile.content)
* [`CleanerAgentCodeFile.model_config`](datalinks#datalinks.models.CleanerAgentCodeFile.model_config)
* [`CleanerAgentCodeFilesResponse`](datalinks#datalinks.models.CleanerAgentCodeFilesResponse)
* [`CleanerAgentCodeFilesResponse.files`](datalinks#datalinks.models.CleanerAgentCodeFilesResponse.files)
* [`CleanerAgentCodeFilesResponse.model_config`](datalinks#datalinks.models.CleanerAgentCodeFilesResponse.model_config)
* [`ConversationDetailResponse`](datalinks#datalinks.models.ConversationDetailResponse)
* [`ConversationDetailResponse.id`](datalinks#datalinks.models.ConversationDetailResponse.id)
* [`ConversationDetailResponse.title`](datalinks#datalinks.models.ConversationDetailResponse.title)
* [`ConversationDetailResponse.turns`](datalinks#datalinks.models.ConversationDetailResponse.turns)
* [`ConversationDetailResponse.model_config`](datalinks#datalinks.models.ConversationDetailResponse.model_config)
* [`ConversationListResponse`](datalinks#datalinks.models.ConversationListResponse)
* [`ConversationListResponse.conversations`](datalinks#datalinks.models.ConversationListResponse.conversations)
* [`ConversationListResponse.model_config`](datalinks#datalinks.models.ConversationListResponse.model_config)
* [`ConversationSummaryResponse`](datalinks#datalinks.models.ConversationSummaryResponse)
* [`ConversationSummaryResponse.id`](datalinks#datalinks.models.ConversationSummaryResponse.id)
* [`ConversationSummaryResponse.title`](datalinks#datalinks.models.ConversationSummaryResponse.title)
* [`ConversationSummaryResponse.created_at`](datalinks#datalinks.models.ConversationSummaryResponse.created_at)
* [`ConversationSummaryResponse.updated_at`](datalinks#datalinks.models.ConversationSummaryResponse.updated_at)
* [`ConversationSummaryResponse.model_config`](datalinks#datalinks.models.ConversationSummaryResponse.model_config)
* [`ConversationTurnResponse`](datalinks#datalinks.models.ConversationTurnResponse)
* [`ConversationTurnResponse.run_id`](datalinks#datalinks.models.ConversationTurnResponse.run_id)
* [`ConversationTurnResponse.question`](datalinks#datalinks.models.ConversationTurnResponse.question)
* [`ConversationTurnResponse.status`](datalinks#datalinks.models.ConversationTurnResponse.status)
* [`ConversationTurnResponse.result`](datalinks#datalinks.models.ConversationTurnResponse.result)
* [`ConversationTurnResponse.model_config`](datalinks#datalinks.models.ConversationTurnResponse.model_config)
* [`CreateDatasetRequest`](datalinks#datalinks.models.CreateDatasetRequest)
* [`CreateDatasetRequest.data_description`](datalinks#datalinks.models.CreateDatasetRequest.data_description)
* [`CreateDatasetRequest.schema_definition`](datalinks#datalinks.models.CreateDatasetRequest.schema_definition)
* [`CreateDatasetRequest.model_config`](datalinks#datalinks.models.CreateDatasetRequest.model_config)
* [`CreateTokenRequest`](datalinks#datalinks.models.CreateTokenRequest)
* [`CreateTokenRequest.expires_at`](datalinks#datalinks.models.CreateTokenRequest.expires_at)
* [`CreateTokenRequest.name`](datalinks#datalinks.models.CreateTokenRequest.name)
* [`CreateTokenRequest.access_restricted_to`](datalinks#datalinks.models.CreateTokenRequest.access_restricted_to)
* [`CreateTokenRequest.model_config`](datalinks#datalinks.models.CreateTokenRequest.model_config)
* [`CreatedIngestionTrackingResponse`](datalinks#datalinks.models.CreatedIngestionTrackingResponse)
* [`CreatedIngestionTrackingResponse.id`](datalinks#datalinks.models.CreatedIngestionTrackingResponse.id)
* [`CreatedIngestionTrackingResponse.model_config`](datalinks#datalinks.models.CreatedIngestionTrackingResponse.model_config)
* [`CurateLinkRequest`](datalinks#datalinks.models.CurateLinkRequest)
* [`CurateLinkRequest.namespace`](datalinks#datalinks.models.CurateLinkRequest.namespace)
* [`CurateLinkRequest.dataset`](datalinks#datalinks.models.CurateLinkRequest.dataset)
* [`CurateLinkRequest.model`](datalinks#datalinks.models.CurateLinkRequest.model)
* [`CurateLinkRequest.provider`](datalinks#datalinks.models.CurateLinkRequest.provider)
* [`CurateLinkRequest.activate`](datalinks#datalinks.models.CurateLinkRequest.activate)
* [`CurateLinkRequest.model_config`](datalinks#datalinks.models.CurateLinkRequest.model_config)
* [`CurateLinkResponse`](datalinks#datalinks.models.CurateLinkResponse)
* [`CurateLinkResponse.datasets_processed`](datalinks#datalinks.models.CurateLinkResponse.datasets_processed)
* [`CurateLinkResponse.total_selected`](datalinks#datalinks.models.CurateLinkResponse.total_selected)
* [`CurateLinkResponse.curated_links`](datalinks#datalinks.models.CurateLinkResponse.curated_links)
* [`CurateLinkResponse.model_config`](datalinks#datalinks.models.CurateLinkResponse.model_config)
* [`CurrentUserResponse`](datalinks#datalinks.models.CurrentUserResponse)
* [`CurrentUserResponse.id`](datalinks#datalinks.models.CurrentUserResponse.id)
* [`CurrentUserResponse.username`](datalinks#datalinks.models.CurrentUserResponse.username)
* [`CurrentUserResponse.name`](datalinks#datalinks.models.CurrentUserResponse.name)
* [`CurrentUserResponse.email`](datalinks#datalinks.models.CurrentUserResponse.email)
* [`CurrentUserResponse.model_config`](datalinks#datalinks.models.CurrentUserResponse.model_config)
* [`DataArray`](datalinks#datalinks.models.DataArray)
* [`DataArray.root`](datalinks#datalinks.models.DataArray.root)
* [`DataArray.model_config`](datalinks#datalinks.models.DataArray.model_config)
* [`DataDescription`](datalinks#datalinks.models.DataDescription)
* [`DataDescription.root`](datalinks#datalinks.models.DataDescription.root)
* [`DataDescription.model_config`](datalinks#datalinks.models.DataDescription.model_config)
* [`DatasetResponse`](datalinks#datalinks.models.DatasetResponse)
* [`DatasetResponse.dataset`](datalinks#datalinks.models.DatasetResponse.dataset)
* [`DatasetResponse.metadata`](datalinks#datalinks.models.DatasetResponse.metadata)
* [`DatasetResponse.model_config`](datalinks#datalinks.models.DatasetResponse.model_config)
* [`DatasetsResponse`](datalinks#datalinks.models.DatasetsResponse)
* [`DatasetsResponse.root`](datalinks#datalinks.models.DatasetsResponse.root)
* [`DatasetsResponse.model_config`](datalinks#datalinks.models.DatasetsResponse.model_config)
* [`ErrorResponse`](datalinks#datalinks.models.ErrorResponse)
* [`ErrorResponse.error_code`](datalinks#datalinks.models.ErrorResponse.error_code)
* [`ErrorResponse.error_message`](datalinks#datalinks.models.ErrorResponse.error_message)
* [`ErrorResponse.message`](datalinks#datalinks.models.ErrorResponse.message)
* [`ErrorResponse.sub_errors`](datalinks#datalinks.models.ErrorResponse.sub_errors)
* [`ErrorResponse.model_config`](datalinks#datalinks.models.ErrorResponse.model_config)
* [`ExactMatchConfig`](datalinks#datalinks.models.ExactMatchConfig)
* [`ExactMatchConfig.min_distinct`](datalinks#datalinks.models.ExactMatchConfig.min_distinct)
* [`ExactMatchConfig.min_variation`](datalinks#datalinks.models.ExactMatchConfig.min_variation)
* [`ExactMatchConfig.model_config`](datalinks#datalinks.models.ExactMatchConfig.model_config)
* [`FileUploadPrepareSessionRequest`](datalinks#datalinks.models.FileUploadPrepareSessionRequest)
* [`FileUploadPrepareSessionRequest.files_to_upload`](datalinks#datalinks.models.FileUploadPrepareSessionRequest.files_to_upload)
* [`FileUploadPrepareSessionRequest.model_config`](datalinks#datalinks.models.FileUploadPrepareSessionRequest.model_config)
* [`FileUploadPrepareSessionRequestFileToUpload`](datalinks#datalinks.models.FileUploadPrepareSessionRequestFileToUpload)
* [`FileUploadPrepareSessionRequestFileToUpload.filename`](datalinks#datalinks.models.FileUploadPrepareSessionRequestFileToUpload.filename)
* [`FileUploadPrepareSessionRequestFileToUpload.size`](datalinks#datalinks.models.FileUploadPrepareSessionRequestFileToUpload.size)
* [`FileUploadPrepareSessionRequestFileToUpload.model_config`](datalinks#datalinks.models.FileUploadPrepareSessionRequestFileToUpload.model_config)
* [`FileUploadPrepareSessionResponse`](datalinks#datalinks.models.FileUploadPrepareSessionResponse)
* [`FileUploadPrepareSessionResponse.session_id`](datalinks#datalinks.models.FileUploadPrepareSessionResponse.session_id)
* [`FileUploadPrepareSessionResponse.files`](datalinks#datalinks.models.FileUploadPrepareSessionResponse.files)
* [`FileUploadPrepareSessionResponse.model_config`](datalinks#datalinks.models.FileUploadPrepareSessionResponse.model_config)
* [`FileUploadPrepareSessionResponseFileInfo`](datalinks#datalinks.models.FileUploadPrepareSessionResponseFileInfo)
* [`FileUploadPrepareSessionResponseFileInfo.file_id`](datalinks#datalinks.models.FileUploadPrepareSessionResponseFileInfo.file_id)
* [`FileUploadPrepareSessionResponseFileInfo.upload_id`](datalinks#datalinks.models.FileUploadPrepareSessionResponseFileInfo.upload_id)
* [`FileUploadPrepareSessionResponseFileInfo.key`](datalinks#datalinks.models.FileUploadPrepareSessionResponseFileInfo.key)
* [`FileUploadPrepareSessionResponseFileInfo.filename`](datalinks#datalinks.models.FileUploadPrepareSessionResponseFileInfo.filename)
* [`FileUploadPrepareSessionResponseFileInfo.part_size`](datalinks#datalinks.models.FileUploadPrepareSessionResponseFileInfo.part_size)
* [`FileUploadPrepareSessionResponseFileInfo.presigned_urls`](datalinks#datalinks.models.FileUploadPrepareSessionResponseFileInfo.presigned_urls)
* [`FileUploadPrepareSessionResponseFileInfo.model_config`](datalinks#datalinks.models.FileUploadPrepareSessionResponseFileInfo.model_config)
* [`FileUploadPrepareSessionResponsePresignedInfo`](datalinks#datalinks.models.FileUploadPrepareSessionResponsePresignedInfo)
* [`FileUploadPrepareSessionResponsePresignedInfo.url`](datalinks#datalinks.models.FileUploadPrepareSessionResponsePresignedInfo.url)
* [`FileUploadPrepareSessionResponsePresignedInfo.part_size`](datalinks#datalinks.models.FileUploadPrepareSessionResponsePresignedInfo.part_size)
* [`FileUploadPrepareSessionResponsePresignedInfo.model_config`](datalinks#datalinks.models.FileUploadPrepareSessionResponsePresignedInfo.model_config)
* [`FinishFileUploadRequest`](datalinks#datalinks.models.FinishFileUploadRequest)
* [`FinishFileUploadRequest.upload_id`](datalinks#datalinks.models.FinishFileUploadRequest.upload_id)
* [`FinishFileUploadRequest.key`](datalinks#datalinks.models.FinishFileUploadRequest.key)
* [`FinishFileUploadRequest.parts`](datalinks#datalinks.models.FinishFileUploadRequest.parts)
* [`FinishFileUploadRequest.model_config`](datalinks#datalinks.models.FinishFileUploadRequest.model_config)
* [`FinishFileUploadRequestParts`](datalinks#datalinks.models.FinishFileUploadRequestParts)
* [`FinishFileUploadRequestParts.part_number`](datalinks#datalinks.models.FinishFileUploadRequestParts.part_number)
* [`FinishFileUploadRequestParts.etag`](datalinks#datalinks.models.FinishFileUploadRequestParts.etag)
* [`FinishFileUploadRequestParts.model_config`](datalinks#datalinks.models.FinishFileUploadRequestParts.model_config)
* [`FinishFileUploadResponse`](datalinks#datalinks.models.FinishFileUploadResponse)
* [`FinishFileUploadResponse.session_id`](datalinks#datalinks.models.FinishFileUploadResponse.session_id)
* [`FinishFileUploadResponse.session_status`](datalinks#datalinks.models.FinishFileUploadResponse.session_status)
* [`FinishFileUploadResponse.file_id`](datalinks#datalinks.models.FinishFileUploadResponse.file_id)
* [`FinishFileUploadResponse.file_status`](datalinks#datalinks.models.FinishFileUploadResponse.file_status)
* [`FinishFileUploadResponse.model_config`](datalinks#datalinks.models.FinishFileUploadResponse.model_config)
* [`FinishedMultipartItem`](datalinks#datalinks.models.FinishedMultipartItem)
* [`FinishedMultipartItem.part_number`](datalinks#datalinks.models.FinishedMultipartItem.part_number)
* [`FinishedMultipartItem.etag`](datalinks#datalinks.models.FinishedMultipartItem.etag)
* [`FinishedMultipartItem.model_config`](datalinks#datalinks.models.FinishedMultipartItem.model_config)
* [`FinishedMultipartRequest`](datalinks#datalinks.models.FinishedMultipartRequest)
* [`FinishedMultipartRequest.upload_id`](datalinks#datalinks.models.FinishedMultipartRequest.upload_id)
* [`FinishedMultipartRequest.key`](datalinks#datalinks.models.FinishedMultipartRequest.key)
* [`FinishedMultipartRequest.parts`](datalinks#datalinks.models.FinishedMultipartRequest.parts)
* [`FinishedMultipartRequest.name`](datalinks#datalinks.models.FinishedMultipartRequest.name)
* [`FinishedMultipartRequest.infer`](datalinks#datalinks.models.FinishedMultipartRequest.infer)
* [`FinishedMultipartRequest.link`](datalinks#datalinks.models.FinishedMultipartRequest.link)
* [`FinishedMultipartRequest.infer_batch_size`](datalinks#datalinks.models.FinishedMultipartRequest.infer_batch_size)
* [`FinishedMultipartRequest.data_description`](datalinks#datalinks.models.FinishedMultipartRequest.data_description)
* [`FinishedMultipartRequest.schema_definition`](datalinks#datalinks.models.FinishedMultipartRequest.schema_definition)
* [`FinishedMultipartRequest.model_config`](datalinks#datalinks.models.FinishedMultipartRequest.model_config)
* [`GeoMatchConfig`](datalinks#datalinks.models.GeoMatchConfig)
* [`GeoMatchConfig.distance`](datalinks#datalinks.models.GeoMatchConfig.distance)
* [`GeoMatchConfig.distance_unit`](datalinks#datalinks.models.GeoMatchConfig.distance_unit)
* [`GeoMatchConfig.model_config`](datalinks#datalinks.models.GeoMatchConfig.model_config)
* [`InferSchemaDescriptionRequest`](datalinks#datalinks.models.InferSchemaDescriptionRequest)
* [`InferSchemaDescriptionRequest.sample`](datalinks#datalinks.models.InferSchemaDescriptionRequest.sample)
* [`InferSchemaDescriptionRequest.model`](datalinks#datalinks.models.InferSchemaDescriptionRequest.model)
* [`InferSchemaDescriptionRequest.provider`](datalinks#datalinks.models.InferSchemaDescriptionRequest.provider)
* [`InferSchemaDescriptionRequest.current_value`](datalinks#datalinks.models.InferSchemaDescriptionRequest.current_value)
* [`InferSchemaDescriptionRequest.model_config`](datalinks#datalinks.models.InferSchemaDescriptionRequest.model_config)
* [`InferredDescriptionResponse`](datalinks#datalinks.models.InferredDescriptionResponse)
* [`InferredDescriptionResponse.description`](datalinks#datalinks.models.InferredDescriptionResponse.description)
* [`InferredDescriptionResponse.model_config`](datalinks#datalinks.models.InferredDescriptionResponse.model_config)
* [`InferredSchemaResponse`](datalinks#datalinks.models.InferredSchemaResponse)
* [`InferredSchemaResponse.schema_`](datalinks#datalinks.models.InferredSchemaResponse.schema_)
* [`InferredSchemaResponse.model_config`](datalinks#datalinks.models.InferredSchemaResponse.model_config)
* [`IngestDataResponse`](datalinks#datalinks.models.IngestDataResponse)
* [`IngestDataResponse.indexed`](datalinks#datalinks.models.IngestDataResponse.indexed)
* [`IngestDataResponse.model_config`](datalinks#datalinks.models.IngestDataResponse.model_config)
* [`IngestLink`](datalinks#datalinks.models.IngestLink)
* [`IngestLink.exact_match`](datalinks#datalinks.models.IngestLink.exact_match)
* [`IngestLink.geo_match`](datalinks#datalinks.models.IngestLink.geo_match)
* [`IngestLink.model_config`](datalinks#datalinks.models.IngestLink.model_config)
* [`IngestLinksRequest`](datalinks#datalinks.models.IngestLinksRequest)
* [`IngestLinksRequest.data`](datalinks#datalinks.models.IngestLinksRequest.data)
* [`IngestLinksRequest.link`](datalinks#datalinks.models.IngestLinksRequest.link)
* [`IngestLinksRequest.model_config`](datalinks#datalinks.models.IngestLinksRequest.model_config)
* [`IngestLinksResponse`](datalinks#datalinks.models.IngestLinksResponse)
* [`IngestLinksResponse.root`](datalinks#datalinks.models.IngestLinksResponse.root)
* [`IngestLinksResponse.model_config`](datalinks#datalinks.models.IngestLinksResponse.model_config)
* [`IngestPreviewResponse`](datalinks#datalinks.models.IngestPreviewResponse)
* [`IngestPreviewResponse.root`](datalinks#datalinks.models.IngestPreviewResponse.root)
* [`IngestPreviewResponse.model_config`](datalinks#datalinks.models.IngestPreviewResponse.model_config)
* [`IngestRequest`](datalinks#datalinks.models.IngestRequest)
* [`IngestRequest.data`](datalinks#datalinks.models.IngestRequest.data)
* [`IngestRequest.infer`](datalinks#datalinks.models.IngestRequest.infer)
* [`IngestRequest.link`](datalinks#datalinks.models.IngestRequest.link)
* [`IngestRequest.curate`](datalinks#datalinks.models.IngestRequest.curate)
* [`IngestRequest.data_description`](datalinks#datalinks.models.IngestRequest.data_description)
* [`IngestRequest.schema_definition`](datalinks#datalinks.models.IngestRequest.schema_definition)
* [`IngestRequest.additional_instructions`](datalinks#datalinks.models.IngestRequest.additional_instructions)
* [`IngestRequest.model_config`](datalinks#datalinks.models.IngestRequest.model_config)
* [`IngestRequestInfer`](datalinks#datalinks.models.IngestRequestInfer)
* [`IngestRequestInfer.steps`](datalinks#datalinks.models.IngestRequestInfer.steps)
* [`IngestRequestInfer.model_config`](datalinks#datalinks.models.IngestRequestInfer.model_config)
* [`IngestRequestInferStep`](datalinks#datalinks.models.IngestRequestInferStep)
* [`IngestRequestInferStep.root`](datalinks#datalinks.models.IngestRequestInferStep.root)
* [`IngestRequestInferStep.model_config`](datalinks#datalinks.models.IngestRequestInferStep.model_config)
* [`IngestionItemAttributes`](datalinks#datalinks.models.IngestionItemAttributes)
* [`IngestionItemAttributes.name`](datalinks#datalinks.models.IngestionItemAttributes.name)
* [`IngestionItemAttributes.seen_at`](datalinks#datalinks.models.IngestionItemAttributes.seen_at)
* [`IngestionItemAttributes.original_ingestion_id`](datalinks#datalinks.models.IngestionItemAttributes.original_ingestion_id)
* [`IngestionItemAttributes.last_retry_at`](datalinks#datalinks.models.IngestionItemAttributes.last_retry_at)
* [`IngestionItemAttributes.model_config`](datalinks#datalinks.models.IngestionItemAttributes.model_config)
* [`ListIngestionsResponseData`](datalinks#datalinks.models.ListIngestionsResponseData)
* [`ListIngestionsResponseData.data`](datalinks#datalinks.models.ListIngestionsResponseData.data)
* [`ListIngestionsResponseData.metadata`](datalinks#datalinks.models.ListIngestionsResponseData.metadata)
* [`ListIngestionsResponseData.model_config`](datalinks#datalinks.models.ListIngestionsResponseData.model_config)
* [`ListIngestionsResponseDataItem`](datalinks#datalinks.models.ListIngestionsResponseDataItem)
* [`ListIngestionsResponseDataItem.id`](datalinks#datalinks.models.ListIngestionsResponseDataItem.id)
* [`ListIngestionsResponseDataItem.status`](datalinks#datalinks.models.ListIngestionsResponseDataItem.status)
* [`ListIngestionsResponseDataItem.status_message`](datalinks#datalinks.models.ListIngestionsResponseDataItem.status_message)
* [`ListIngestionsResponseDataItem.processed_bytes`](datalinks#datalinks.models.ListIngestionsResponseDataItem.processed_bytes)
* [`ListIngestionsResponseDataItem.expected_total_bytes`](datalinks#datalinks.models.ListIngestionsResponseDataItem.expected_total_bytes)
* [`ListIngestionsResponseDataItem.processed_rows`](datalinks#datalinks.models.ListIngestionsResponseDataItem.processed_rows)
* [`ListIngestionsResponseDataItem.attributes`](datalinks#datalinks.models.ListIngestionsResponseDataItem.attributes)
* [`ListIngestionsResponseDataItem.model_config`](datalinks#datalinks.models.ListIngestionsResponseDataItem.model_config)
* [`ListIngestionsResponseMetadata`](datalinks#datalinks.models.ListIngestionsResponseMetadata)
* [`ListIngestionsResponseMetadata.before_created_at`](datalinks#datalinks.models.ListIngestionsResponseMetadata.before_created_at)
* [`ListIngestionsResponseMetadata.page_size`](datalinks#datalinks.models.ListIngestionsResponseMetadata.page_size)
* [`ListIngestionsResponseMetadata.model_config`](datalinks#datalinks.models.ListIngestionsResponseMetadata.model_config)
* [`ListSessionFilesResponse`](datalinks#datalinks.models.ListSessionFilesResponse)
* [`ListSessionFilesResponse.session_id`](datalinks#datalinks.models.ListSessionFilesResponse.session_id)
* [`ListSessionFilesResponse.session_status`](datalinks#datalinks.models.ListSessionFilesResponse.session_status)
* [`ListSessionFilesResponse.files`](datalinks#datalinks.models.ListSessionFilesResponse.files)
* [`ListSessionFilesResponse.model_config`](datalinks#datalinks.models.ListSessionFilesResponse.model_config)
* [`ListSessionFilesResponseFileInfo`](datalinks#datalinks.models.ListSessionFilesResponseFileInfo)
* [`ListSessionFilesResponseFileInfo.file_id`](datalinks#datalinks.models.ListSessionFilesResponseFileInfo.file_id)
* [`ListSessionFilesResponseFileInfo.filename`](datalinks#datalinks.models.ListSessionFilesResponseFileInfo.filename)
* [`ListSessionFilesResponseFileInfo.s3_bucket`](datalinks#datalinks.models.ListSessionFilesResponseFileInfo.s3_bucket)
* [`ListSessionFilesResponseFileInfo.s3_key`](datalinks#datalinks.models.ListSessionFilesResponseFileInfo.s3_key)
* [`ListSessionFilesResponseFileInfo.file_size`](datalinks#datalinks.models.ListSessionFilesResponseFileInfo.file_size)
* [`ListSessionFilesResponseFileInfo.model_config`](datalinks#datalinks.models.ListSessionFilesResponseFileInfo.model_config)
* [`ModifyOntologyRequest`](datalinks#datalinks.models.ModifyOntologyRequest)
* [`ModifyOntologyRequest.add`](datalinks#datalinks.models.ModifyOntologyRequest.add)
* [`ModifyOntologyRequest.remove`](datalinks#datalinks.models.ModifyOntologyRequest.remove)
* [`ModifyOntologyRequest.model_config`](datalinks#datalinks.models.ModifyOntologyRequest.model_config)
* [`NamedEntitiesStep`](datalinks#datalinks.models.NamedEntitiesStep)
* [`NamedEntitiesStep.type`](datalinks#datalinks.models.NamedEntitiesStep.type)
* [`NamedEntitiesStep.model_config`](datalinks#datalinks.models.NamedEntitiesStep.model_config)
* [`NormaliseStep`](datalinks#datalinks.models.NormaliseStep)
* [`NormaliseStep.type`](datalinks#datalinks.models.NormaliseStep.type)
* [`NormaliseStep.mode`](datalinks#datalinks.models.NormaliseStep.mode)
* [`NormaliseStep.target_cols`](datalinks#datalinks.models.NormaliseStep.target_cols)
* [`NormaliseStep.data_description`](datalinks#datalinks.models.NormaliseStep.data_description)
* [`NormaliseStep.model`](datalinks#datalinks.models.NormaliseStep.model)
* [`NormaliseStep.provider`](datalinks#datalinks.models.NormaliseStep.provider)
* [`NormaliseStep.model_config`](datalinks#datalinks.models.NormaliseStep.model_config)
* [`OntologyResponse`](datalinks#datalinks.models.OntologyResponse)
* [`OntologyResponse.root`](datalinks#datalinks.models.OntologyResponse.root)
* [`OntologyResponse.model_config`](datalinks#datalinks.models.OntologyResponse.model_config)
* [`OperationResult`](datalinks#datalinks.models.OperationResult)
* [`OperationResult.root`](datalinks#datalinks.models.OperationResult.root)
* [`OperationResult.model_config`](datalinks#datalinks.models.OperationResult.model_config)
* [`PrepareMultipartRequest`](datalinks#datalinks.models.PrepareMultipartRequest)
* [`PrepareMultipartRequest.filename`](datalinks#datalinks.models.PrepareMultipartRequest.filename)
* [`PrepareMultipartRequest.size`](datalinks#datalinks.models.PrepareMultipartRequest.size)
* [`PrepareMultipartRequest.model_config`](datalinks#datalinks.models.PrepareMultipartRequest.model_config)
* [`PrepareMultipartResponse`](datalinks#datalinks.models.PrepareMultipartResponse)
* [`PrepareMultipartResponse.upload_id`](datalinks#datalinks.models.PrepareMultipartResponse.upload_id)
* [`PrepareMultipartResponse.key`](datalinks#datalinks.models.PrepareMultipartResponse.key)
* [`PrepareMultipartResponse.part_size`](datalinks#datalinks.models.PrepareMultipartResponse.part_size)
* [`PrepareMultipartResponse.presigned_urls`](datalinks#datalinks.models.PrepareMultipartResponse.presigned_urls)
* [`PrepareMultipartResponse.model_config`](datalinks#datalinks.models.PrepareMultipartResponse.model_config)
* [`PresignedUrlItem`](datalinks#datalinks.models.PresignedUrlItem)
* [`PresignedUrlItem.url`](datalinks#datalinks.models.PresignedUrlItem.url)
* [`PresignedUrlItem.size`](datalinks#datalinks.models.PresignedUrlItem.size)
* [`PresignedUrlItem.model_config`](datalinks#datalinks.models.PresignedUrlItem.model_config)
* [`QueryDataRequest`](datalinks#datalinks.models.QueryDataRequest)
* [`QueryDataRequest.username`](datalinks#datalinks.models.QueryDataRequest.username)
* [`QueryDataRequest.namespace`](datalinks#datalinks.models.QueryDataRequest.namespace)
* [`QueryDataRequest.dataset`](datalinks#datalinks.models.QueryDataRequest.dataset)
* [`QueryDataRequest.query`](datalinks#datalinks.models.QueryDataRequest.query)
* [`QueryDataRequest.natural_language_query`](datalinks#datalinks.models.QueryDataRequest.natural_language_query)
* [`QueryDataRequest.model`](datalinks#datalinks.models.QueryDataRequest.model)
* [`QueryDataRequest.provider`](datalinks#datalinks.models.QueryDataRequest.provider)
* [`QueryDataRequest.explain_requested`](datalinks#datalinks.models.QueryDataRequest.explain_requested)
* [`QueryDataRequest.model_config`](datalinks#datalinks.models.QueryDataRequest.model_config)
* [`QueryExplainResponse`](datalinks#datalinks.models.QueryExplainResponse)
* [`QueryExplainResponse.id`](datalinks#datalinks.models.QueryExplainResponse.id)
* [`QueryExplainResponse.dataset_queries_stat`](datalinks#datalinks.models.QueryExplainResponse.dataset_queries_stat)
* [`QueryExplainResponse.start`](datalinks#datalinks.models.QueryExplainResponse.start)
* [`QueryExplainResponse.duration`](datalinks#datalinks.models.QueryExplainResponse.duration)
* [`QueryExplainResponse.children`](datalinks#datalinks.models.QueryExplainResponse.children)
* [`QueryExplainResponse.model_config`](datalinks#datalinks.models.QueryExplainResponse.model_config)
* [`QueryExplainResponseDatasetQueriesStat`](datalinks#datalinks.models.QueryExplainResponseDatasetQueriesStat)
* [`QueryExplainResponseDatasetQueriesStat.username`](datalinks#datalinks.models.QueryExplainResponseDatasetQueriesStat.username)
* [`QueryExplainResponseDatasetQueriesStat.namespace`](datalinks#datalinks.models.QueryExplainResponseDatasetQueriesStat.namespace)
* [`QueryExplainResponseDatasetQueriesStat.dataset`](datalinks#datalinks.models.QueryExplainResponseDatasetQueriesStat.dataset)
* [`QueryExplainResponseDatasetQueriesStat.result_count`](datalinks#datalinks.models.QueryExplainResponseDatasetQueriesStat.result_count)
* [`QueryExplainResponseDatasetQueriesStat.model_config`](datalinks#datalinks.models.QueryExplainResponseDatasetQueriesStat.model_config)
* [`QueryResponseData`](datalinks#datalinks.models.QueryResponseData)
* [`QueryResponseData.data`](datalinks#datalinks.models.QueryResponseData.data)
* [`QueryResponseData.explain_results`](datalinks#datalinks.models.QueryResponseData.explain_results)
* [`QueryResponseData.metadata`](datalinks#datalinks.models.QueryResponseData.metadata)
* [`QueryResponseData.result_type`](datalinks#datalinks.models.QueryResponseData.result_type)
* [`QueryResponseData.model_config`](datalinks#datalinks.models.QueryResponseData.model_config)
* [`QueryResponseMetadata`](datalinks#datalinks.models.QueryResponseMetadata)
* [`QueryResponseMetadata.count`](datalinks#datalinks.models.QueryResponseMetadata.count)
* [`QueryResponseMetadata.executed_query`](datalinks#datalinks.models.QueryResponseMetadata.executed_query)
* [`QueryResponseMetadata.original_query`](datalinks#datalinks.models.QueryResponseMetadata.original_query)
* [`QueryResponseMetadata.starting_dataset`](datalinks#datalinks.models.QueryResponseMetadata.starting_dataset)
* [`QueryResponseMetadata.final_dataset`](datalinks#datalinks.models.QueryResponseMetadata.final_dataset)
* [`QueryResponseMetadata.model_config`](datalinks#datalinks.models.QueryResponseMetadata.model_config)
* [`RequestCleaningRequest`](datalinks#datalinks.models.RequestCleaningRequest)
* [`RequestCleaningRequest.prompts`](datalinks#datalinks.models.RequestCleaningRequest.prompts)
* [`RequestCleaningRequest.output_namespace`](datalinks#datalinks.models.RequestCleaningRequest.output_namespace)
* [`RequestCleaningRequest.output_dataset_name`](datalinks#datalinks.models.RequestCleaningRequest.output_dataset_name)
* [`RequestCleaningRequest.model_config`](datalinks#datalinks.models.RequestCleaningRequest.model_config)
* [`RequestCleaningResponse`](datalinks#datalinks.models.RequestCleaningResponse)
* [`RequestCleaningResponse.cleaning_task_id`](datalinks#datalinks.models.RequestCleaningResponse.cleaning_task_id)
* [`RequestCleaningResponse.model_config`](datalinks#datalinks.models.RequestCleaningResponse.model_config)
* [`ResolutionParameters`](datalinks#datalinks.models.ResolutionParameters)
* [`ResolutionParameters.bidirectional`](datalinks#datalinks.models.ResolutionParameters.bidirectional)
* [`ResolutionParameters.target_columns`](datalinks#datalinks.models.ResolutionParameters.target_columns)
* [`ResolutionParameters.ignore_private_columns`](datalinks#datalinks.models.ResolutionParameters.ignore_private_columns)
* [`ResolutionParameters.model_config`](datalinks#datalinks.models.ResolutionParameters.model_config)
* [`ReverseGeoStep`](datalinks#datalinks.models.ReverseGeoStep)
* [`ReverseGeoStep.type`](datalinks#datalinks.models.ReverseGeoStep.type)
* [`ReverseGeoStep.derive_from`](datalinks#datalinks.models.ReverseGeoStep.derive_from)
* [`ReverseGeoStep.model_config`](datalinks#datalinks.models.ReverseGeoStep.model_config)
* [`RowsStep`](datalinks#datalinks.models.RowsStep)
* [`RowsStep.type`](datalinks#datalinks.models.RowsStep.type)
* [`RowsStep.derive_from`](datalinks#datalinks.models.RowsStep.derive_from)
* [`RowsStep.model_config`](datalinks#datalinks.models.RowsStep.model_config)
* [`SchemaDefinition`](datalinks#datalinks.models.SchemaDefinition)
* [`SchemaDefinition.root`](datalinks#datalinks.models.SchemaDefinition.root)
* [`SchemaDefinition.model_config`](datalinks#datalinks.models.SchemaDefinition.model_config)
* [`ShareAccessRequest`](datalinks#datalinks.models.ShareAccessRequest)
* [`ShareAccessRequest.username`](datalinks#datalinks.models.ShareAccessRequest.username)
* [`ShareAccessRequest.role`](datalinks#datalinks.models.ShareAccessRequest.role)
* [`ShareAccessRequest.model_config`](datalinks#datalinks.models.ShareAccessRequest.model_config)
* [`TableStep`](datalinks#datalinks.models.TableStep)
* [`TableStep.type`](datalinks#datalinks.models.TableStep.type)
* [`TableStep.derive_from`](datalinks#datalinks.models.TableStep.derive_from)
* [`TableStep.additional_instructions`](datalinks#datalinks.models.TableStep.additional_instructions)
* [`TableStep.model`](datalinks#datalinks.models.TableStep.model)
* [`TableStep.provider`](datalinks#datalinks.models.TableStep.provider)
* [`TableStep.replace_original_column`](datalinks#datalinks.models.TableStep.replace_original_column)
* [`TableStep.model_config`](datalinks#datalinks.models.TableStep.model_config)
* [`TokenPermissionEntry`](datalinks#datalinks.models.TokenPermissionEntry)
* [`TokenPermissionEntry.username`](datalinks#datalinks.models.TokenPermissionEntry.username)
* [`TokenPermissionEntry.namespace`](datalinks#datalinks.models.TokenPermissionEntry.namespace)
* [`TokenPermissionEntry.dataset`](datalinks#datalinks.models.TokenPermissionEntry.dataset)
* [`TokenPermissionEntry.model_config`](datalinks#datalinks.models.TokenPermissionEntry.model_config)
* [`TokenPermissionsResponse`](datalinks#datalinks.models.TokenPermissionsResponse)
* [`TokenPermissionsResponse.restricted`](datalinks#datalinks.models.TokenPermissionsResponse.restricted)
* [`TokenPermissionsResponse.permissions`](datalinks#datalinks.models.TokenPermissionsResponse.permissions)
* [`TokenPermissionsResponse.model_config`](datalinks#datalinks.models.TokenPermissionsResponse.model_config)
* [`UpdateSortOrderRequest`](datalinks#datalinks.models.UpdateSortOrderRequest)
* [`UpdateSortOrderRequest.order`](datalinks#datalinks.models.UpdateSortOrderRequest.order)
* [`UpdateSortOrderRequest.model_config`](datalinks#datalinks.models.UpdateSortOrderRequest.model_config)
* [`UploadFileId`](datalinks#datalinks.models.UploadFileId)
* [`UploadFileId.root`](datalinks#datalinks.models.UploadFileId.root)
* [`UploadFileId.model_config`](datalinks#datalinks.models.UploadFileId.model_config)
* [`UploadSessionId`](datalinks#datalinks.models.UploadSessionId)
* [`UploadSessionId.root`](datalinks#datalinks.models.UploadSessionId.root)
* [`UploadSessionId.model_config`](datalinks#datalinks.models.UploadSessionId.model_config)
* [`UserToken`](datalinks#datalinks.models.UserToken)
* [`UserToken.created_at`](datalinks#datalinks.models.UserToken.created_at)
* [`UserToken.expires_at`](datalinks#datalinks.models.UserToken.expires_at)
* [`UserToken.id`](datalinks#datalinks.models.UserToken.id)
* [`UserToken.last_used_at`](datalinks#datalinks.models.UserToken.last_used_at)
* [`UserToken.name`](datalinks#datalinks.models.UserToken.name)
* [`UserToken.token`](datalinks#datalinks.models.UserToken.token)
* [`UserToken.model_config`](datalinks#datalinks.models.UserToken.model_config)
* [`UserTokenResponse`](datalinks#datalinks.models.UserTokenResponse)
* [`UserTokenResponse.root`](datalinks#datalinks.models.UserTokenResponse.root)
* [`UserTokenResponse.model_config`](datalinks#datalinks.models.UserTokenResponse.model_config)
* [`UserUsageByDayItem`](datalinks#datalinks.models.UserUsageByDayItem)
* [`UserUsageByDayItem.date`](datalinks#datalinks.models.UserUsageByDayItem.date)
* [`UserUsageByDayItem.tokens_used`](datalinks#datalinks.models.UserUsageByDayItem.tokens_used)
* [`UserUsageByDayItem.cost_usd`](datalinks#datalinks.models.UserUsageByDayItem.cost_usd)
* [`UserUsageByDayItem.model_config`](datalinks#datalinks.models.UserUsageByDayItem.model_config)
* [`UserUsageByDayMeta`](datalinks#datalinks.models.UserUsageByDayMeta)
* [`UserUsageByDayMeta.time_zone_for_aggregation`](datalinks#datalinks.models.UserUsageByDayMeta.time_zone_for_aggregation)
* [`UserUsageByDayMeta.model_config`](datalinks#datalinks.models.UserUsageByDayMeta.model_config)
* [`UserUsageByDayResponse`](datalinks#datalinks.models.UserUsageByDayResponse)
* [`UserUsageByDayResponse.data`](datalinks#datalinks.models.UserUsageByDayResponse.data)
* [`UserUsageByDayResponse.meta`](datalinks#datalinks.models.UserUsageByDayResponse.meta)
* [`UserUsageByDayResponse.model_config`](datalinks#datalinks.models.UserUsageByDayResponse.model_config)
* [`UserUsageHistoryCursor`](datalinks#datalinks.models.UserUsageHistoryCursor)
* [`UserUsageHistoryCursor.last_start_time`](datalinks#datalinks.models.UserUsageHistoryCursor.last_start_time)
* [`UserUsageHistoryCursor.last_observation_id`](datalinks#datalinks.models.UserUsageHistoryCursor.last_observation_id)
* [`UserUsageHistoryCursor.model_config`](datalinks#datalinks.models.UserUsageHistoryCursor.model_config)
* [`UserUsageHistoryItem`](datalinks#datalinks.models.UserUsageHistoryItem)
* [`UserUsageHistoryItem.timestamp`](datalinks#datalinks.models.UserUsageHistoryItem.timestamp)
* [`UserUsageHistoryItem.tokens_used`](datalinks#datalinks.models.UserUsageHistoryItem.tokens_used)
* [`UserUsageHistoryItem.cost_usd`](datalinks#datalinks.models.UserUsageHistoryItem.cost_usd)
* [`UserUsageHistoryItem.model_config`](datalinks#datalinks.models.UserUsageHistoryItem.model_config)
* [`UserUsageHistoryMeta`](datalinks#datalinks.models.UserUsageHistoryMeta)
* [`UserUsageHistoryMeta.page_cursor`](datalinks#datalinks.models.UserUsageHistoryMeta.page_cursor)
* [`UserUsageHistoryMeta.page_size`](datalinks#datalinks.models.UserUsageHistoryMeta.page_size)
* [`UserUsageHistoryMeta.model_config`](datalinks#datalinks.models.UserUsageHistoryMeta.model_config)
* [`UserUsageHistoryResponse`](datalinks#datalinks.models.UserUsageHistoryResponse)
* [`UserUsageHistoryResponse.data`](datalinks#datalinks.models.UserUsageHistoryResponse.data)
* [`UserUsageHistoryResponse.meta`](datalinks#datalinks.models.UserUsageHistoryResponse.meta)
* [`UserUsageHistoryResponse.model_config`](datalinks#datalinks.models.UserUsageHistoryResponse.model_config)
* [`ValidateStep`](datalinks#datalinks.models.ValidateStep)
* [`ValidateStep.type`](datalinks#datalinks.models.ValidateStep.type)
* [`ValidateStep.mode`](datalinks#datalinks.models.ValidateStep.mode)
* [`ValidateStep.columns`](datalinks#datalinks.models.ValidateStep.columns)
* [`ValidateStep.model`](datalinks#datalinks.models.ValidateStep.model)
* [`ValidateStep.provider`](datalinks#datalinks.models.ValidateStep.provider)
* [`ValidateStep.model_config`](datalinks#datalinks.models.ValidateStep.model_config)
* [datalinks.pipeline module](datalinks#module-datalinks.pipeline)
* [`StepTypes`](datalinks#datalinks.pipeline.StepTypes)
* [`StepTypes.TABLE`](datalinks#datalinks.pipeline.StepTypes.TABLE)
* [`StepTypes.ROWS`](datalinks#datalinks.pipeline.StepTypes.ROWS)
* [`StepTypes.NORMALIZE`](datalinks#datalinks.pipeline.StepTypes.NORMALIZE)
* [`StepTypes.VALIDATE`](datalinks#datalinks.pipeline.StepTypes.VALIDATE)
* [`StepTypes.REVERSE_GEO`](datalinks#datalinks.pipeline.StepTypes.REVERSE_GEO)
* [`NormalizeModes`](datalinks#datalinks.pipeline.NormalizeModes)
* [`NormalizeModes.EMBEDDINGS`](datalinks#datalinks.pipeline.NormalizeModes.EMBEDDINGS)
* [`NormalizeModes.ALL_IN_ONE`](datalinks#datalinks.pipeline.NormalizeModes.ALL_IN_ONE)
* [`NormalizeModes.FIELD_BY_FIELD`](datalinks#datalinks.pipeline.NormalizeModes.FIELD_BY_FIELD)
* [`ValidateModes`](datalinks#datalinks.pipeline.ValidateModes)
* [`ValidateModes.ROWS`](datalinks#datalinks.pipeline.ValidateModes.ROWS)
* [`ValidateModes.REGEX`](datalinks#datalinks.pipeline.ValidateModes.REGEX)
* [`ValidateModes.FIELDS`](datalinks#datalinks.pipeline.ValidateModes.FIELDS)
* [`BaseStep`](datalinks#datalinks.pipeline.BaseStep)
* [`BaseStep.step_type`](datalinks#datalinks.pipeline.BaseStep.step_type)
* [`BaseStep.to_dict()`](datalinks#datalinks.pipeline.BaseStep.to_dict)
* [`LlmStep`](datalinks#datalinks.pipeline.LlmStep)
* [`LlmStep.model`](datalinks#datalinks.pipeline.LlmStep.model)
* [`LlmStep.provider`](datalinks#datalinks.pipeline.LlmStep.provider)
* [`InferenceStep`](datalinks#datalinks.pipeline.InferenceStep)
* [`InferenceStep.derive_from`](datalinks#datalinks.pipeline.InferenceStep.derive_from)
* [`ProcessUnstructured`](datalinks#datalinks.pipeline.ProcessUnstructured)
* [`ProcessUnstructured.step_type`](datalinks#datalinks.pipeline.ProcessUnstructured.step_type)
* [`ProcessUnstructured.helper_prompt`](datalinks#datalinks.pipeline.ProcessUnstructured.helper_prompt)
* [`ProcessStructured`](datalinks#datalinks.pipeline.ProcessStructured)
* [`ProcessStructured.step_type`](datalinks#datalinks.pipeline.ProcessStructured.step_type)
* [`ReverseGeo`](datalinks#datalinks.pipeline.ReverseGeo)
* [`ReverseGeo.step_type`](datalinks#datalinks.pipeline.ReverseGeo.step_type)
* [`Normalize`](datalinks#datalinks.pipeline.Normalize)
* [`Normalize.step_type`](datalinks#datalinks.pipeline.Normalize.step_type)
* [`Normalize.target_cols`](datalinks#datalinks.pipeline.Normalize.target_cols)
* [`Normalize.mode`](datalinks#datalinks.pipeline.Normalize.mode)
* [`Normalize.helper_prompt`](datalinks#datalinks.pipeline.Normalize.helper_prompt)
* [`Validate`](datalinks#datalinks.pipeline.Validate)
* [`Validate.step_type`](datalinks#datalinks.pipeline.Validate.step_type)
* [`Validate.mode`](datalinks#datalinks.pipeline.Validate.mode)
* [`Validate.columns`](datalinks#datalinks.pipeline.Validate.columns)
* [`Pipeline`](datalinks#datalinks.pipeline.Pipeline)
* [`Pipeline.to_list()`](datalinks#datalinks.pipeline.Pipeline.to_list)
# Installation
Source: https://docs.landfall.ai/pythonsdk/sdk-install
Install the DataLinks Python SDK using pip or uv.
To install the SDK, simply use `pip` or `uv`:
```shell script theme={null}
pip install datalinks
# or
uv add datalinks
```
If you want to install the package in editable development mode (includes pytest, tox, and twine):
1. Clone the repository from your version-control system.
2. Create a virtual environment with your tool of choice.
3. Run the following:
```shell script theme={null}
pip install -e ".[dev]"
# or
uv pip install -e ".[dev]"
```
# SDK QuickStart
Source: https://docs.landfall.ai/pythonsdk/sdk-quickstart
Get started with SDK with a few simple steps.
This short guide shows you how to set up the SDK, run a basic query, and get your first useful results.
For a step-by-step walkthrough that uses real example data to solve a practical scenario, see the QuickStart API/SDK Guide.
### 1. Install
[Install the DataLinks SDK](sdk-install) if you haven't already.
### 2. Configure
Ensure the required environment variables are set:
| Variable | Required | Description |
| - | - | - |
| `DL_HOST` | Yes | Base URL of the DataLinks API (e.g. `https://api.datalinks.com`) |
| `DL_API_KEY` | Yes | Your DataLinks API key (JWT token) |
| `DL_NAMESPACE` | Yes | Target namespace for ingest and query operations |
| `OBJECT_NAME` | No | Object name scoping for ingest/query URLs; defaults to empty |
If you use the **Ingest Proxy** (`IngestProxyAPI`) for auto-modelled pipelines, set these instead:
| Variable | Required | Description |
| - | - | - |
| `DL_INGEST_PROXY` | Yes | Base URL of the ingest proxy (e.g. `https://your-proxy.vercel.app`) |
| `DL_API_KEY` | Yes | Your DataLinks API key (shared with the main API) |
| `DL_USERNAME` | Yes | Your DataLinks username |
| `DL_NAMESPACE` | Yes | Target namespace |
Alternatively, you can use a `.env` file in the root of your project.
### 3. Ingest and query
```python theme={null}
from datalinks.api import DataLinksAPI, DLConfig
# Initialize configuration
config = DLConfig.from_env()
# Instantiate API client
client = DataLinksAPI(config=config)
# Sample rows
rows = [
{"brand": "Gilette", "category": "Razor", "product": "Heated Razor"},
{"brand": "Oral-B", "category": "Electric Toothbrush", "product": "iO Series 10"},
]
# Ingest pre-structured rows directly (no pipeline required)
result = client.ingest(data=rows)
# Query data
data = client.query_data(include_metadata=False)
print(data)
```
## CLI Usage
The SDK also provides a built-in CLI that can be extended:
```shell theme={null}
datalinks-client [-h] --verbose
```
# A2A Protocol Reference
Source: https://docs.landfall.ai/reference/a2a-protocol
Endpoints, AgentCard, agent catalog, and streaming event types for the DataLinks Agent2Agent (A2A) interface.
DataLinks exposes its agents as standard **Agent2Agent (A2A) 1.0** servers. This page is the lookup reference for the endpoints, authentication, agent catalog, message format, and streaming event types. For a step-by-step walkthrough, see [Connect an Agent to DataLinks over A2A](/a2a/general-a2a).
A2A support is in **preview**: available to every account with no extra rate limits, but the surface may still change.
## Endpoints
A2A lives under the API base URL, `https://api.datalinks.com/api/v1`. Each agent is reachable at three endpoints under `{host}/a2a`:
| Method | Path | Auth | Description |
| - | - | - | - |
| `GET` | `/a2a` | Public | List the agents this server exposes. Returns a JSON array of agent objects (`name`, `url`, `agentCard`). |
| `GET` | `/a2a/{agent}/.well-known/agent-card.json` | Public | Return the agent's **AgentCard** discovery document. |
| `POST` | `/a2a/{agent}` | Bearer | Send messages to the agent (JSON-RPC 2.0, supports streaming). |
## Authentication
The `POST` endpoint requires a **bearer token**: your DataLinks API token, sent as `Authorization: Bearer `. The same token you use for the REST API and Python SDK works here.
The AgentCard declares a single security scheme named **`bearer`** (`bearerFormat: JWT`). A compliant client reads `card.security_requirements` and registers the token for each declared scheme rather than assuming a scheme name.
See [Get an API token](/how-to/get-api-token) for issuing a token.
## Agent Catalog
`GET /a2a` returns the two agents DataLinks exposes:
| Agent | Purpose |
| - | - |
| `nl2ql` | Natural-language-to-query. Translates a plain-language question into a structured query, runs it against the target namespace, and returns the generated query plus the result rows. |
| `assistant` | Retrieval-augmented answering. Plans a multi-step query strategy, runs the queries, and synthesises an answer. Same assistant as the `ask` endpoint. Accepts a `contextId` to continue a prior conversation. |
Example response:
```json theme={null}
[
{
"name": "nl2ql",
"url": "https://api.datalinks.com/api/v1/a2a/nl2ql",
"agentCard": "https://api.datalinks.com/api/v1/a2a/nl2ql/.well-known/agent-card.json"
},
{
"name": "assistant",
"url": "https://api.datalinks.com/api/v1/a2a/assistant",
"agentCard": "https://api.datalinks.com/api/v1/a2a/assistant/.well-known/agent-card.json"
}
]
```
Don't hardcode a single agent name. Call `GET /a2a` and select from the returned list so your client keeps working as the catalog changes.
## Message Format
Send messages with the JSON-RPC method `SendMessage` (single response) or `SendStreamingMessage` (SSE stream). The message parts carry your question. The **target namespace is passed in the message metadata**, not in the URL or body:
```json theme={null}
{
"jsonrpc": "2.0",
"id": "1",
"method": "SendStreamingMessage",
"params": {
"message": {
"messageId": "",
"role": "ROLE_USER",
"parts": [{ "text": "How many orders were placed today?" }],
"metadata": { "namespace": "" }
},
"configuration": { "acceptedOutputModes": ["text", "data"] }
}
}
```
| Field | Required | Notes |
| - | - | - |
| `message.messageId` | Yes | Unique per message (a UUID). |
| `message.role` | Yes | `ROLE_USER` for requests. |
| `message.parts` | Yes | The question. The agent reads the first `TextPart`. |
| `message.metadata.namespace` | Yes | The namespace the agent queries. The server reads this key from the message metadata. |
| `message.metadata.model` | No | Override the LLM model the agent uses. |
| `message.metadata.provider` | No | Override the LLM provider the agent uses. |
| `message.contextId` | No | *(assistant only)* A prior `contextId` to continue a multi-turn conversation. |
| `configuration.acceptedOutputModes` | No | Output modes the client accepts, for example `["text", "data"]`. |
### JSON-RPC methods
| Method | Purpose |
| - | - |
| `SendMessage` | Send a message and get a single `Task` result. |
| `SendStreamingMessage` | Send a message and stream events over SSE. |
| `GetTask` / `CancelTask` | Task lifecycle methods. Both agents are effectively stateless over A2A, so these return not-found / unsupported. |
| `GetExtendedAgentCard` | Return the extended AgentCard (richer skill descriptions and examples). |
## Streaming Events
A streaming call (`SendStreamingMessage`) returns Server-Sent Events. Each SSE `data:` line is a JSON-RPC response wrapping one event under `result`:
```json theme={null}
{ "jsonrpc": "2.0", "id": "1", "result": { "statusUpdate": { "…": "…" } } }
```
Each event's `payload` is exactly one of the following (proto3-JSON oneof; the wrapper key is shown in parentheses):
| Event (`payload`) | Wrapper key | Meaning |
| - | - | - |
| `task` | `task` | The overall task, its `id`, `status.state`, and any `artifacts`. |
| `status_update` | `statusUpdate` | A transition in the task's lifecycle. Carries a `TaskState` and may include a message. |
| `message` | `message` | A message from the agent, containing parts. |
| `artifact_update` | `artifactUpdate` | A produced result delivered as an artifact, containing parts. |
Task states are reported via `TaskState`, for example `TASK_STATE_WORKING`, `TASK_STATE_COMPLETED`, `TASK_STATE_FAILED`. Read `response.WhichOneof("payload")` to branch on the event type.
### `nl2ql` stream sequence
`nl2ql` emits four events: a `working` status, the generated query as an artifact, the rows as an artifact, then a `completed` status. A real stream for "list the tickets" (`taskId` / `contextId` / `timestamp` elided):
```json theme={null}
{"jsonrpc":"2.0","id":"1","result":{"statusUpdate":{"status":{"state":"TASK_STATE_WORKING"}}}}
{"jsonrpc":"2.0","id":"1","result":{"artifactUpdate":{"artifact":{"artifactId":"generated-query","name":"generated-query","parts":[{"text":"Ontology(\"test1/tickets\").limit(100)"}]}}}}
{"jsonrpc":"2.0","id":"1","result":{"artifactUpdate":{"artifact":{"artifactId":"result-data","name":"result-data","parts":[{"data":{"rows":[{"id":"1","subject":"Cannot login","status":"open","created_at":"2024-01-15T10:00:00Z","__metadata":{"score":1.0}},{"id":"2","subject":"Billing issue","status":"closed","created_at":"2024-01-16T12:00:00Z","__metadata":{"score":1.0}}]}}]}}}}
{"jsonrpc":"2.0","id":"1","result":{"statusUpdate":{"status":{"state":"TASK_STATE_COMPLETED"}}}}
```
### `assistant` stream sequence
`assistant` streams its planning and execution: a `submitted` task, `working` statuses (with the plan carried as a text + data part, then each step's instruction), one `step-N` artifact per query step, then the final `answer` artifact and a `completed` status. A real (trimmed) stream:
```json theme={null}
{"jsonrpc":"2.0","id":"1","result":{"task":{"id":"…","contextId":"…","status":{"state":"TASK_STATE_SUBMITTED"},"artifacts":[],"history":[]}}}
{"jsonrpc":"2.0","id":"1","result":{"statusUpdate":{"status":{"state":"TASK_STATE_WORKING"}}}}
{"jsonrpc":"2.0","id":"1","result":{"statusUpdate":{"status":{"state":"TASK_STATE_WORKING","message":{"role":"ROLE_AGENT","parts":[{"text":"This step retrieves all support tickets…"},{"data":{"steps":["Find `test1/tickets`."]}}],"messageId":"…-plan"}}}}}
{"jsonrpc":"2.0","id":"1","result":{"statusUpdate":{"status":{"state":"TASK_STATE_WORKING","message":{"role":"ROLE_AGENT","parts":[{"text":"Find `test1/tickets`."}],"messageId":"…-step-0"}}}}}
{"jsonrpc":"2.0","id":"1","result":{"artifactUpdate":{"artifact":{"artifactId":"step-0","name":"step-0","parts":[{"text":"Ontology(\"test1/tickets\").limit(100)"},{"data":{"rows":[{"id":"1","subject":"Cannot login","status":"open","__metadata":{"score":1.0}},{"id":"2","subject":"Billing issue","status":"closed","__metadata":{"score":1.0}}]}}],"metadata":{"index":0,"instruction":"Find `test1/tickets`.","reasoning":"Querying the 'test1/tickets' ontology class with a limit of 100…"}}}}}
{"jsonrpc":"2.0","id":"1","result":{"artifactUpdate":{"artifact":{"artifactId":"answer","name":"answer","parts":[{"text":"There are **2** tickets in the dataset:\n\n* **Cannot login** (Status: open, Created: 2024-01-15)\n* **Billing issue** (Status: closed, Created: 2024-01-16)"}]}}}}
{"jsonrpc":"2.0","id":"1","result":{"statusUpdate":{"status":{"state":"TASK_STATE_COMPLETED"}}}}
```
## Artifacts
| Agent | Artifact | Parts | Contains |
| - | - | - | - |
| `nl2ql` | `generated-query` | `TextPart` | The generated query. |
| `nl2ql` | `result-data` | `DataPart` | The result rows, under a `rows` key. |
| `assistant` | `step-N` | `TextPart` + `DataPart` | Each step's query and its rows. Artifact `metadata` carries `index`, `instruction`, `reasoning`. |
| `assistant` | `answer` | `TextPart` | The final synthesised answer. |
## Part Types
Messages and artifacts contain **parts**. A2A 1.0 parts are proto3-JSON, so a part has no `kind` discriminator; its content is one of:
| Part content | Field | Contains |
| - | - | - |
| `text` | `part.text` | A string. |
| `data` | `part.data` | A structured object. For query results this is `{ "rows": [ … ] }`. |
Convert a `DataPart` to native Python with `google.protobuf.json_format.MessageToDict(part.data)`.
A response can contain both a `TextPart` and a `DataPart`. Clients that handle only text will receive the query or answer but silently drop the result rows. Always handle `DataPart`s.
Rows preserve DataLinks internal `__`-prefixed fields (for example `__metadata`, which carries a match `score`), matching the `/query/ask` wire format. Filter them out if your consumer doesn't need them.
## AgentCard
The discovery document a client fetches before connecting. A live `nl2ql` card:
```json theme={null}
{
"name": "NL2QL",
"description": "Translates a natural-language question into a structured query and executes it.",
"version": "1.0.0",
"capabilities": { "streaming": true, "pushNotifications": false, "extendedAgentCard": true },
"defaultInputModes": ["text"],
"defaultOutputModes": ["text", "data"],
"skills": [
{
"id": "nl2ql",
"name": "Natural language to query",
"description": "Generate and run a query from natural language.",
"tags": ["query", "nl2ql"]
}
],
"securitySchemes": {
"bearer": { "httpAuthSecurityScheme": { "bearerFormat": "JWT", "scheme": "bearer" } }
},
"securityRequirements": [{ "schemes": { "bearer": { "list": [] } } }],
"supportedInterfaces": [
{ "protocolBinding": "JSONRPC", "url": "https://api.datalinks.com/api/v1/a2a/nl2ql", "protocolVersion": "1.0" }
],
"preferredTransport": "JSONRPC"
}
```
The fields the reference client relies on:
| Field | Used for |
| - | - |
| `securityRequirements[].schemes` | The security scheme name(s) to register the bearer token against (`bearer`). |
| `capabilities` / `skills` | What the agent can do. |
| `supportedInterfaces[].url` | Where the client sends messages. |
The `assistant` card is identical in shape, with the `assistant` skill (`tags: ["assistant", "rag", "query"]`).
## Related
* [Connect an Agent to DataLinks over A2A](/a2a/general-a2a) - step-by-step guide.
* [A2A Client example](/pythonsdk/examples#a2a-client-agent2agent-protocol) - complete runnable client.
* [Get an API token](/how-to/get-api-token) - issuing the bearer credential.
# AI Inference
Source: https://docs.landfall.ai/reference/ai_inference
When you ingest data into DataLinks, you can include an inference pipeline that transforms your data before it is stored. The pipeline is a sequence of steps that extract structure from unstructured text, normalize column names to a target schema, validate data quality, and enrich records with geographic coordinates.
Inference steps are specified in the `infer.steps` array of an ingestion request. Steps execute in order: the output of each step becomes the input for the next.
## Step types at a glance
| Step | `type` value | Purpose |
| - | - | - |
| [Table extraction](#table-extraction) | `table` | Extract structured columns from unstructured text |
| [Rows](#rows) | `rows` | Expand a JSON array stored in a column into rows |
| [Normalize](#normalize) | `normalize` | Map extracted column names to a target schema |
| [Validate](#validate) | `validate` | Check data quality and flag invalid rows |
| [ReverseGeo](#reversegeo) | `reverseGeo` | Add latitude/longitude from location names |
## How the pipeline works
The inference can have multiple steps which are combined by our AI. Steps execute sequentially in the order you list them. A typical pipeline follows this pattern:
```
Raw data
→ Table extraction (unstructured text → columns)
→ Normalize (inconsistent column names → target schema)
→ Validate (flag rows that fail quality checks)
→ Stored in DataLinks
```
You can use any combination of steps. A single step is fine. So is skipping straight to normalize if your data is already tabular but has inconsistent column names.
The full request structure looks like this:
```python theme={null}
import requests
response = requests.post(
"https://api.datalinks.com/api/v1/ingest/namespace/datasetName",
headers={"Authorization": "Bearer YOUR_TOKEN"},
json={
"data": [
{"col1": 123, "col2": "foo", "colWithATable": "direction,size;up,large"},
{"col1": 444, "col2": "bar"}
],
"infer": {
"steps": [
{
"type": "table",
"deriveFrom": "colWithATable",
"helperPrompt": "This text contains a table comma separated, but instead of line breaks we are using a ';'."
}
]
}
}
)
```
In this specific example we specify we want a **table** extracted from a specific column. This will extract all columns available from the text provided. You can use the **helperPrompt** to guide the AI system to have higher accuracy during extraction.
## Step reference
### Table extraction
The Table step is designed to extract a table from any text input, whether it's free-form text such as a restaurant review, a flight log report, or a financial instrument notice. When used independently, this step may produce columns that are somewhat arbitrary and often require normalization afterward. It functions by extracting data to create new columns and appending them to the rows of the existing table.
```python theme={null}
{
"type": "table",
"deriveFrom": "source_column",
"helperPrompt": "Optional prompt to guide extraction."
}
```
| Field | Required | Description |
| - | - | - |
| `type` | Yes | Must be `"table"` |
| `deriveFrom` | Yes | Name of the column containing unstructured text |
| `helperPrompt` | No | Instructions to the LLM about what to extract and how |
**Pro tip:** Calling an extraction with just the **table** step is a great way to see what kind of **structured** data can be generated from your unstructured data.
When processing diverse inputs, the LLM will invent column names based on what it finds. Different rows may produce different names for the same concept (e.g., `phone_number` vs `telephone`). Follow table extraction with a [normalize](#normalize) step to consolidate these into a consistent schema.
### Rows
If a table is stored in JSON format within a column, you can directly transform it into a structured table. The JSON should consist of an array of objects, with each key in the objects mapped to a new column.
```python theme={null}
{
"type": "rows",
"deriveFrom": "json_column"
}
```
| Field | Required | Description |
| - | - | - |
| `type` | Yes | Must be `"rows"` |
| `deriveFrom` | Yes | Name of the column containing a JSON array |
For example, if a column contains `[{"name": "Alice", "age": 34}, {"name": "Bob", "age": 28}]`, the Rows step produces two rows with `name` and `age` columns.
### Normalize
Use the Normalize step to consolidate the schema or column space. In this step, specify the columns you want to include in the final table that will be indexed into DataLinks.
```python theme={null}
{
"type": "normalize",
"mode": "all-in-one",
"targetCols": {
"full_name": "The person's full name, without titles like Dr. or Mr.",
"age": "Age as a number",
"company": "Company or organization name",
"email": "Email address"
},
"helperPrompt": "This data comes from professional directory listings."
}
```
| Field | Required | Description |
| - | - | - |
| `type` | Yes | Must be `"normalize"` (or `"normalise"`) |
| `mode` | Yes | One of `"all-in-one"`, `"field-by-field"`, or `"embeddings"` |
| `targetCols` | Yes | Object mapping desired column names to descriptions |
| `helperPrompt` | No | Domain context to help the LLM interpret column names |
#### Choosing a mode
The normalize step supports three different modes:
| Mode | How it works | Best for |
| - | - | - |
| `all-in-one` | Uses a single prompt to normalize all columns at once | Good default for most cases. Fast and effective when columns are reasonably self-explanatory. |
| `field-by-field` | Normalizes each field individually, which can be more accurate for complex schemas | Complex schemas where columns may be ambiguous. More accurate but slower. |
| `embeddings` | Uses embeddings to match column names, which can be faster and more efficient for large datasets | High-volume ingestion with predictable, consistent source schemas. Fastest option. |
The descriptions you provide in `targetCols` matter. They are the primary signal the LLM uses to decide which extracted column maps to which target. Be specific: `"Email address"` is better than `"email"`, and `"Annual salary in USD"` is better than `"salary"`.
### Validate
The Validate step ensures data quality by validating the content of specified columns. It supports three validation modes:
```python theme={null}
{
"type": "validate",
"mode": "regex|rows|fields",
"columns": ["column1", "column2"]
}
```
| Field | Required | Description |
| - | - | - |
| `type` | Yes | Must be `"validate"` |
| `mode` | Yes | One of `"regex"`, `"rows"`, or `"fields"` |
| `columns` | Yes | Array of column names to validate |
#### Validation modes
1. **regex**: Validates columns using regular expressions
2. **rows**: Validates entire rows based on the specified columns
3. **fields**: Validates individual fields in the specified columns
Validated rows will include a `__valid` field indicating whether the validation passed.
### ReverseGeo
The ReverseGeo step adds geographical coordinates (latitude and longitude) based on location names in a specified column.
> **Note:** ReverseGeo is an optional feature not enabled by default. Contact your account representative to add it to your workspace.
```python theme={null}
{
"type": "reverseGeo",
"deriveFrom": "locationColumnName"
}
```
| Field | Required | Description |
| - | - | - |
| `type` | Yes | Must be `"reverseGeo"` |
| `deriveFrom` | Yes | Name of the column containing location names |
This step will add a new column named `{locationColumnName}_latlong` containing the coordinates.
Example output:
| city | city\_latlong |
| - | - |
| New York | 40.7128,-74.0060 |
| London | 51.5074,-0.1278 |
## Writing effective helper prompts
The `helperPrompt` field is available on both the table extraction and normalize steps. It is the most impactful parameter for inference quality. A good prompt gives the LLM the context it needs to make accurate decisions.
**Be specific about the schema you expect.** Name the columns and describe their format:
```
Extract the following fields: MemberName (full name without academic
titles like Dr. or Prof.), Address (street address), City, Country
(normalize all country names to English), Phone (include country code,
replacing a leading 0 with the appropriate code), Email, Website.
```
**Describe the source format** so the LLM knows how to parse it:
```
This text contains a semicolon-delimited table. Columns are separated
by commas and rows are separated by semicolons.
```
**Call out edge cases and transformation rules:**
```
If a phone number does not include a country code (+XX), add the
appropriate code based on the country. If latitude/longitude are
present, format them as "lat,long".
```
**State what to include and exclude:**
```
Extract all entries. Do not skip any. Omit personal honorifics like
Ms., Mr., Frau, Herr from names.
```
## Choosing an LLM
Steps that use LLM inference (table extraction and normalize) accept optional `model` and `provider` fields to specify which model runs the step.
```python theme={null}
{
"type": "table",
"deriveFrom": "notes",
"helperPrompt": "Extract product names and prices.",
"model": "your-model-here",
"provider": "openai"
}
```
| Field | Required | Description |
| - | - | - |
| `model` | No | The model identifier |
| `provider` | No | The provider name (e.g., `"openai"`, `"ollama"`) |
When omitted, DataLinks uses its default model. Smaller models are faster and cheaper for simple extractions; larger models handle ambiguous or complex text better.
## Complete example
This example takes unstructured directory entries, extracts structured fields, normalizes the schema, and validates the results:
```python theme={null}
import requests
# Configuration
API_TOKEN = "YOUR_TOKEN"
NAMESPACE = "your_namespace"
DATASET = "dental_practices"
# Sample data: unstructured directory entries
data = [
{"entry": "Dr. Alice Chen, Northwind Dental, 42 Oak St, Springfield, (555) 012-3456, alice@northwind.com"},
{"entry": "Bob Martinez - Rivera Family Dentistry. 100 Main Ave, Portland OR 97201. bob@riveradental.com"}
]
# Inference pipeline configuration
inference_steps = {
"steps": [
# Step 1: Extract structured fields from free text
{
"type": "table",
"deriveFrom": "entry",
"helperPrompt": "Each entry is a dental practice listing. Extract the practitioner name (without Dr. or similar titles), practice name, street address, city, state, phone number (with +1 country code), and email."
},
# Step 2: Normalize to consistent schema
{
"type": "normalize",
"mode": "all-in-one",
"targetCols": {
"practitioner": "Full name of the dentist, without titles",
"practice_name": "Name of the dental practice",
"address": "Street address",
"city": "City name",
"state": "US state name or abbreviation",
"phone": "Phone number with +1 country code",
"email": "Email address"
},
"helperPrompt": "These are US-based dental practices."
},
# Step 3: Validate email and phone fields
{
"type": "validate",
"mode": "fields",
"columns": ["email", "phone"]
}
]
}
# Make the API request
response = requests.post(
f"https://api.datalinks.com/api/v1/ingest/{NAMESPACE}/{DATASET}",
headers={
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
},
json={
"data": data,
"infer": inference_steps
}
)
# Check result
if response.status_code == 200:
print("Ingestion successful!")
print(response.json())
else:
print(f"Error: {response.status_code}")
print(response.text)
```
## Saving inference definitions
When you create a dataset, you can save a data description and field definition that DataLinks uses during future ingestion calls. This avoids repeating the same configuration on every request.
Set the definition at creation time via the `inferDefinition` field on [Create new dataset](/api-reference/ingest/create-new-dataset), or update it later with [Update infer definition](/api-reference/dataset/update-inference-definition).
```python theme={null}
{
"dataDescription": "Dental practice directory entries with contact information.",
"fieldDefinition": "practitioner=Full name without titles\npractice_name=Name of the dental practice\naddress=Street address\ncity=City\nstate=US state\nphone=Phone with country code\nemail=Email address"
}
```
# Automodeler
Source: https://docs.landfall.ai/reference/automodeler
The Automodeler is an AI-powered pipeline that converts raw data into structured, interconnected datasets inside DataLinks. You upload your data through the web platform, describe what you want to do with it, and the Automodeler handles schema design, data ingestion, and relationship discovery.
## How the Automodeler works
### The conversation with the agent
The Automodeler agent is a data consultant you interact with via chat. The flow is as follows:
1. **You upload your data.** CSV, PDF, Excel, JSON, or any structured format you have.
2. **Goal setting.** "What do you want to do with this data? Who will use it?"
3. **Real conversation.** The agent analyzes the data silently, then asks specific questions. It does not blindly accept everything you say. If your goal does not align with the data, it tells you. If it is vague, it asks you to be more precise.
4. **Plan building.** Once it has enough information, it shows you the datasets it will create as visual cards, for example "Issues," "People," "Technologies," with their respective fields.
5. **Approval request.** You can approve, edit, or reject the plan.
6. **Pipeline run.** If approved, it executes the Automodeler to create the datasets.
7. **Review.** It shows the result on the board. You can modify fields, rename elements, add or remove datasets.
8. **Ingestion.** Once you are satisfied, the agent pushes the data into the datasets so they are ready to use.
At any point, you can request changes and the agent will adapt.
### Agent tools
The agent has access to a set of tools that it uses as needed:
| Tool | Description |
| - | - |
| `analyze_data` | Silently analyzes the data, columns, types, quality, and patterns. |
| `ask_user` | Asks a question with clickable options and buttons. |
| `request_approval` | Requests approval with Approve, Edit, and Reject buttons. |
| `craft_prompt` | Builds the visual plan with proposed entities shown as UI cards. |
| `deep_plan` | Shows a step-by-step execution plan. |
| `run_pipeline` | Executes the Automodeler, either the full pipeline or a partial run. |
| `edit_dataset` | Edits a dataset: rename, add, or remove fields. |
| `create_dataset` | Creates a new dataset from scratch. |
| `generate_samples` | Generates sample rows to preview a dataset. |
| `get_board_state` | Checks the current board state, datasets, fields, and relationships. |
| `report_status` | Updates the UI status bar. |
### The pipeline
The pipeline has four phases. Each phase builds on the output of the previous one, and the pipeline includes quality gates and human-in-the-loop checkpoints throughout.
#### **Phase 1: Modeling**
Objective: take raw data and determine which datasets to create.
The pipeline checks DataLinks to see if datasets already exist in this namespace. If they do, they are used as a base so the Automodeler builds on your existing structure rather than starting from scratch.
The AI enriches your goal, suggests entities, and refines language. It also assesses its own confidence. If confidence is low (at or below 60%), the pipeline pauses and asks clarification questions. Your answers are folded back into the prompt for the remaining steps.
This is the most important step. The AI takes each row of your data and creates 10 to 15 new columns representing classifications, categories, entity types, and extracted attributes.
If a row contains multi-valued fields, the expansion splits it into multiple rows. For example, if you have a row with `labels: "bug, performance"`, the expansion produces two rows, one for "bug" and another for "performance." This is called row multiplication.
With many new columns created, the AI groups them into logical datasets. Columns related to people go into a "People" dataset, technologies into "Technologies," and so on. Each dataset gets a natural identifier, for example a person's name or an issue number.
Key rule: this is NOT a relational database. There are no foreign keys. Each dataset is independent. Relationships are discovered later based on the data itself.
The AI writes descriptions for each dataset and each field, explaining what they represent and how they are used. If a description comes back empty, the pipeline falls back to using the dataset name.
Quality gate. The AI checks whether the datasets make sense, whether entities are well separated, whether they match your stated goal, and whether there is unnecessary overlap. If it fails, the pipeline loops back to Step 2 and retries, up to three times.
#### Phase 2: Preview
The pipeline generates sample rows for each dataset so you can preview the result before proceeding.
If enabled, the pipeline pauses here and shows you the result. You can:
* **Approve** as-is
* **Edit** fields, names, and descriptions
* **Reject** and restart
The pipeline only continues to ingestion after you explicitly approve. If your session token has expired during a long review, the pipeline silently refreshes it before proceeding.
#### Phase 3: Ingestion
Creates the approved dataset structures in DataLinks.
Pushes real data into each dataset. The data goes through a two-step inference process: first extracting relevant fields from each row, then normalizing values, cleaning, and standardizing them. All datasets are ingested in parallel.
#### Phase 4: Ontology
**Evaluate relationships.** After the data is ingested, the system discovers relationships between datasets. It works at three levels:
* **Level 1, Self-links.** Same column within the same dataset. Automatically approved.
* **Level 2, Same name.** Identical column names across datasets, for example `author_name` and `person_name`. Automatically approved when values match.
* **Level 3, Different names.** Columns with different names but potentially the same meaning. The AI evaluates semantic similarity before linking.
### Summary diagram
## Pipeline configuration
The Automodeler accepts a configuration object that controls which phases run and how the pipeline behaves.
| Parameter | Type | Description |
| - | - | - |
| `namespace` | string | The target namespace for created datasets. |
| `userPrompt` | string | Your description of what you want to do with the data. If omitted, a smart default prompt is used. |
| `data` | array | The raw data to model and ingest. |
| `maxRowsForModeling` | number | Maximum rows sent to the LLM during modeling. Larger datasets are sampled down to this limit to control cost and latency. |
| `maxEvalRetries` | number | Maximum evaluation loop retries before accepting the result. Default is 3. |
| `maxSampleRows` | number | Number of sample rows generated per dataset during preview. |
| `enableHumanInTheLoop` | boolean | When `true`, the pipeline pauses at clarification and schema review checkpoints. When `false`, it runs end-to-end without stopping. |
| `model` | boolean | When `false`, skips the modeling phase. Use with `predefinedSchema` to ingest into a known structure. |
| `ingest` | boolean | When `false`, runs only modeling without creating datasets or pushing data. Useful for previewing what the Automodeler would produce. |
| `ontology` | boolean | When `false`, skips relationship discovery after ingestion. |
| `predefinedSchema` | array | Dataset schemas to use instead of running the modeling phase. The pipeline skips directly to ingestion. |
| `llm.provider` | string | The LLM provider to use. Falls back to an alternative if the requested one has no API key configured. |
| `llm.model` | string | The specific model to use. If omitted, a default is selected based on the provider. |
| `llm.explosionTemperature` | number | Temperature for the expansion step. |
| `llm.coalescenceTemperature` | number | Temperature for the coalescence step. |
| `llm.evaluationTemperature` | number | Temperature for the evaluation step. |
| `llm.ontologyTemperature` | number | Temperature for the relationship evaluation step. |
### Hooks and checkpoints
The pipeline uses three hook points for human-in-the-loop interaction:
1. **Clarification hook** (during prompt refinement). Fires when the AI's confidence in interpreting your goal is at or below 60%. The pipeline emits questions to the UI and waits for your answers before continuing.
2. **Schema review hook** (after sample data generation). Fires after modeling completes. The pipeline presents the proposed datasets and sample rows, then waits for your approval.
3. **Token refresh hook** (before ingestion). If your session token has expired during a long review, the pipeline refreshes it before proceeding.
### Tracing and observability
Every pipeline run generates a `RunTrace` object that records the full execution history:
* A unique `runId` for correlation
* Timestamps for start and completion
* Duration of each step
* Every LLM call with input/output token counts
* Number of evaluation iterations
* A summary with totals for LLM calls, tokens used, datasets generated, fields generated, and links activated
Traces are persisted incrementally after each step, so partial traces are available even if the pipeline fails midway.
### Error Handling
The pipeline classifies errors into two categories:
* **Retryable errors** are transient failures (network timeouts, rate limits) that the pipeline can recover from by retrying.
* **Fatal errors** are unrecoverable failures that stop the pipeline and surface the error to the user.
Quality gates after the expansion and coalescence steps validate output structure before proceeding. If a step produces invalid output, the pipeline retries the modeling loop rather than continuing with bad data.
## How To Use the Automodeler
This section walks through using the Automodeler in the DataLinks web platform, step by step.
### Prerequisites
Before you begin, make sure you have:
* A DataLinks account with access to the web platform
* A namespace where you want to create datasets (see [How To Create a Namespace](how-to/create-namespace) if you need one)
* A data file to upload (JSON, CSV, Excel, or PDF)
Navigate to your namespace in the DataLinks web platform and select **Create a new dataset**. Enter a name for your project and select your namespace from the dropdown. Click **Next** to continue.
On the next screen, upload your data file. The Automodeler accepts JSON, CSV, Excel, and PDF formats. Select your file and click **Next**. The agent begins analyzing your data immediately.
The agent asks what you want to do with this data and who will use it. Be as specific as you can. For example, instead of "organize this data," you might say "I want to track which questions are about which topics and which supporting documents back up each answer."
The agent does not blindly accept everything you say. If your goal does not align with the data, it tells you. If it is vague, it asks follow-up questions to get more precise. This is a real conversation: the agent analyzes the data silently, then responds with specific questions based on what it found.
If the AI's confidence in understanding your goal is low, it pauses and asks clarification questions. Answer them and the pipeline incorporates your responses before continuing.
Once the agent has enough information, it builds a visual plan showing the datasets it intends to create. Each dataset appears as a card with its name, fields, and a brief description. For example, you might see cards for "Questions," "Supporting Facts," and "Context Paragraphs," each with their respective fields listed.
At this point the agent asks for your approval. You have three options:
* **Approve** to proceed with the proposed plan
* **Edit** to modify fields, rename datasets, or adjust descriptions
* **Reject** to start over with a different approach
You can request changes at any point and the agent adapts. For example, you might ask it to split a dataset into two, merge two datasets into one, or add a field it missed.
Once you approve, the agent runs the Automodeler pipeline. The UI shows a step-by-step progress indicator as the pipeline works through expansion, coalescence, description, and evaluation.
If the evaluation step determines that the proposed schema has issues (poor entity separation, unnecessary overlap, or a mismatch with your goal), the pipeline automatically retries the modeling loop up to three times before presenting the best result.
After modeling completes, the pipeline generates sample rows for each dataset so you can preview what your data will look like. Review the sample data carefully. This is your last chance to request changes before ingestion begins.
After the pipeline finishes, the result appears on a board view. You can see all created datasets with their fields and data. At this stage you can still make adjustments:
* Rename fields or datasets
* Add or remove fields
* Add or remove entire datasets
The agent is available throughout this step to make changes on your behalf.
Once you are satisfied with the structure, confirm the ingestion. The agent creates the dataset structures in DataLinks, then pushes your data through a two-step inference process: first extracting relevant field values from each row, then normalizing, cleaning, and standardizing them.
All datasets are ingested in parallel. The pipeline includes short pauses between creating dataset structures and pushing data to allow for propagation. For large files, the full ingestion can take a few minutes.
If your session has been open for a while (for example, during a long schema review), the pipeline automatically refreshes your authentication token before starting ingestion. You may see a brief "Session expired" message; this is handled automatically.
After ingestion, the system automatically evaluates potential relationships between your datasets. It looks for self-links within datasets, columns with matching names across datasets, and columns with different names but semantically similar content. Confirmed relationships are activated and become part of your Knowledge Map.
Once this step completes, your data is fully structured and interconnected. You can query it with AutoRAG, explore the Knowledge Map, or connect it to external applications.
### Tips
**Write a clear prompt.** The more specific your description, the better the Automodeler's output. Mention the entities you care about, the questions you want to answer, and any specific structure you have in mind.
**Review sample data before approving.** The sample rows give you a concrete preview of what each dataset will contain. It is easier to fix the schema before ingestion than after.
**Use the conversation.** The agent is interactive. If the first plan does not match what you need, explain what you want changed. You can go back and forth as many times as you need.
**Large files are sampled automatically.** During modeling, the pipeline works with a subset of your data to keep LLM calls fast and cost-effective. All rows are used during actual ingestion.
# Query Language
Source: https://docs.landfall.ai/reference/query_commands
## Introduction
The DataLinks Query Language is a powerful domain-specific language designed to query and manipulate datasets within the DataLinks ecosystem. It provides a flexible and intuitive way to access data, apply filters, establish relationships between datasets, and sort results.
This language is particularly useful for:
* Retrieving data from specific datasets
* Filtering data based on various conditions
* Discovering relationships between different datasets
* Traversing complex data structures
* Sorting and limiting result sets
* Counting, summing, averaging and grouping records
## Basic Syntax
The query language follows a fluent interface pattern, where methods are chained together to build complex queries. The basic structure of a query is:
```js theme={null}
Ontology("").().()...
```
or
```js theme={null}
OntologyObject("").().()...
```
Both `Ontology` and `OntologyObject` are valid entry points for creating a query.
## Querying Datasets
### Basic Dataset Query
To query a dataset, specify its name:
```js theme={null}
Ontology("Movies")
```
Dataset names are case-insensitive, so `Ontology("movies")` and `Ontology("Movies")` refer to the same dataset.
### Namespaced Datasets
Datasets can be organized in namespaces. To query a namespaced dataset, use the format:
```js theme={null}
Ontology("Namespace/Dataset")
```
For example:
```js theme={null}
Ontology("Companies/Orange")
```
If a dataset name is ambiguous (exists in multiple namespaces), you must specify the namespace to avoid errors.
## Filtering Data
### Basic Filtering
To filter data, use the `filter()` method with a comparison expression:
```js theme={null}
Ontology("Movies").filter(year == 2012)
```
or
```js theme={null}
Ontology("Movies").filter().column("year == 2012")
```
### Comparison Operators
The query language supports the following comparison operators:
* `==` (equal to)
* `!=` (not equal to)
* `<` (less than)
* `<=` (less than or equal to)
* `>` (greater than)
* `>=` (greater than or equal to)
Examples:
```js theme={null}
Ontology("Movies").filter(year < 2000)
Ontology("Movies").filter(budget >= 10000000)
Ontology("Movies").filter(name != "Titanic")
```
### Logical Operators
Combine multiple conditions using logical operators:
* `&&` (AND)
* `||` (OR)
Examples:
```js theme={null}
Ontology("Movies").filter(year == 2005 && director == "Christopher Nolan")
Ontology("Movies").filter(year == 2005 || year == 2006)
```
### Nested Conditions
Use parentheses to create complex nested conditions:
```js theme={null}
Ontology("Movies").filter(director == "Mark Osborne" && (year == 2005 || year == 2006))
```
### Filter Expression Syntax
The query language provides flexible syntax for filter expressions:
1. **Quoting Field Names**: Field names can be unquoted, single-quoted, or double-quoted:
```js theme={null}
name == "value"
"name" == "value"
'name' == "value"
```
2. **Quoting Values**: String values can be unquoted (for simple strings) or quoted:
```js theme={null}
title == dog
title == "dog"
title == 'dog'
```
3. **Multi-word Values**: Multi-word string values must be quoted:
```js theme={null}
title == "The Matrix"
```
Note: Unquoted multi-word values (e.g., `title == The Matrix`) are not supported.
## Linking Datasets
### Searching Related Datasets
To find related datasets, use the `searchAround()` and `find()` methods:
```js theme={null}
Ontology("Movies").searchAround().find("Actor")
```
This searches for actors related to movies based on defined links between the datasets.
### Specifying Search Depth
You can specify the depth of the search (how many hops to traverse):
```js theme={null}
Ontology("Movies").searchAround(2).find("Actor")
```
A depth of 2 means it will look for relationships that are two links away.
### Following Specific Links
To follow specific columns when linking datasets:
```js theme={null}
Ontology("Movies").searchAround().find("Actor").follow("director")
```
This specifies that the link between Movies and Actor should be through the "director" column.
You can specify multiple columns to follow:
```js theme={null}
Ontology("Movies").searchAround().find("Actor").follow("director", "producer")
```
## Sorting and Limiting Results
### Sorting Results
Sort results in ascending or descending order:
```js theme={null}
Ontology("Movies").sortAscending("year")
Ontology("Movies").sortDescending("budget")
```
Alternative syntax:
```js theme={null}
Ontology("Movies").sort("year", "ASC")
Ontology("Movies").sort("budget", "DESC")
```
### Multiple Sort Criteria
Sort by multiple fields:
```js theme={null}
Ontology("Actor").sortDescending("nationality").sortAscending("age")
```
This sorts actors first by nationality in descending order, then by age in ascending order.
### Limiting Results
Limit the number of results returned:
```js theme={null}
Ontology("Movies").limit(5)
```
You can also limit results at different stages of a complex query:
```js theme={null}
Ontology("Movies").limit(10).searchAround().find("Actor").limit(5)
```
This limits the movies to 10 and the related actors to 5.
## Aggregations
Most aggregations replace the matched records with a computed result; `countLinked()` and `sumLinked()` instead keep every record and enrich it. Place the aggregation after `find()` when the query traverses links, or directly after `Ontology()` / `filter()` otherwise; after it, only `filter()`, `sortAscending()` / `sortDescending()` and `limit()` may follow (plus `maxDepth()` for the linked aggregations). `groupBy()` is always paired with one `count()`, `sum()` or `avg()`, and that pair counts as a single aggregation. Only one aggregation is supported per query — do not combine `count()`, `sum()`, `avg()`, `groupBy(…).()`, `countLinked()` or `sumLinked()` with each other.
| Operation | Returns |
| - | - |
| `count()` | One total: the number of matched records |
| `sum("col")` | One total: the sum of `col` across matched records |
| `avg("col")` | One value: the average of `col` across matched records |
| `groupBy("g").count()` | One row per distinct value of `g`, with `count.g` |
| `groupBy("g").sum("v")` | One row per distinct value of `g`, with `sum.v` |
| `groupBy("g").avg("v")` | One row per distinct value of `g`, with `avg.v` |
| `countLinked("Target")` | Each matched record, enriched with `_link_count` |
| `sumLinked("Target", "col")` | Each matched record, enriched with `sum.col` |
### Counting Records
`count()` returns a single number: how many records matched.
```js theme={null}
Ontology("Movies").count()
Ontology("Movies").filter(year < 2000).count()
Ontology("Movies").filter(director == "Mark Osborne").searchAround().find("Actor").count()
```
Aggregations placed directly on the root dataset are exact. After a `searchAround().find()` they run over the traversed records, which are capped at 10,000 — use `countLinked()` / `sumLinked()` when you need exact per-record totals over links.
### Sum and Average
`sum()` and `avg()` compute a single total or average over a numeric column. The column argument is mandatory.
```js theme={null}
Ontology("Movies").sum("budget")
Ontology("Movies").filter(year == 2012).sum("budget")
Ontology("Movies").avg("budget")
Ontology("Movies").filter(director == "Christopher Nolan").avg("budget")
```
`sum()` and `avg()` only work on numeric columns.
### Grouping
`groupBy()` groups the matched records by the distinct values of one or more columns and returns one synthetic row per group. It must be followed by `count()`, `sum()` or `avg()`:
```js theme={null}
Ontology("Movies").groupBy("director").count()
Ontology("Movies").groupBy("director").sum("budget")
Ontology("Movies").groupBy("director").avg("budget")
```
Each returned row holds the group value(s) plus the computed metric, named after the operation and column:
* `groupBy("director").count()` → `director`, `count.director`
* `groupBy("director").sum("budget")` → `director`, `sum.budget`
* `groupBy("director").avg("budget")` → `director`, `avg.budget`
Within `groupBy()`, `sum()` and `avg()` accept several value columns at once, producing one metric per column:
```js theme={null}
Ontology("Movies").groupBy("director").sum("budget", "revenue")
```
Group by several columns to get one row per distinct **combination** of values:
```js theme={null}
Ontology("Movies").groupBy("director", "year").sum("budget")
Ontology("Movies").groupBy("director", "year").count()
```
The count metric is then named after all the group columns in order:
* `groupBy("director", "year").sum("budget")` → `director`, `year`, `sum.budget`
* `groupBy("director", "year").count()` → `director`, `year`, `count.director.year`
The query language has no `distinct()` operation, but `groupBy("column").count()` returns one row per distinct value, so the number of rows returned is the number of distinct values. At most 10,000 groups are returned (the largest by record count unless sorted), and grouping a column with more than 100,000 distinct values is rejected:
```js theme={null}
Ontology("Movies").groupBy("director").count()
```
### Filtering Groups
A `filter()` placed **before** the aggregation narrows the records that get aggregated. A `filter()` placed **after** a `groupBy()` aggregation applies to the groups it produced, comparing against the emitted metric — the equivalent of SQL's `HAVING`. Both can appear in the same query:
```js theme={null}
Ontology("Movies").groupBy("director").count().filter(count.director > 2)
Ontology("Movies").groupBy("director").sum("budget").filter(sum.budget > 100000000)
Ontology("Movies").filter(year == 2024).groupBy("director").sum("budget").filter(sum.budget > 1000000)
```
After a `groupBy()` aggregation only the group column(s) and the metric exist on the rows. A filter that names any other column is rejected.
### Sorting and Limiting Groups
Sort grouped results by their metric to get the top or bottom N groups:
```js theme={null}
Ontology("Movies").groupBy("director").sum("budget").sortDescending("sum.budget").limit(5)
Ontology("Movies").groupBy("director").count().sortDescending("count.director").limit(10)
```
Always pair `limit()` with a sort on the metric. Without a sort, groups come back ordered by record count, so an unsorted `limit()` returns the largest groups rather than the highest totals.
Sorting on the bare value column (e.g. `sortDescending("budget")` instead of `sortDescending("sum.budget")`) is rejected, because that column no longer exists on the grouped rows.
### Counting and Summing Linked Records
`countLinked("X")` / `sumLinked("X", …)` keep the matched records and add an aggregate over the linked records in `X` to each one. By default only direct (one-hop) links count:
```js theme={null}
Ontology("Actor").countLinked("Movies")
```
Returns every actor, each with `_link_count`: the number of movies they appear in.
```js theme={null}
Ontology("Actor").sumLinked("Movies", "boxOffice").sortDescending("sum.boxOffice").limit(10)
```
Returns the 10 actors whose movies earned the most, each with `sum.boxOffice`.
`sumLinked()` accepts several numeric columns at once; each adds its own `sum.` field:
```js theme={null}
Ontology("Actor").sumLinked("Movies", "budget", "boxOffice")
```
Returns every actor, each with `sum.budget` and `sum.boxOffice`.
If `X` is further away, add `maxDepth(n)`. The in-between datasets are found for you, and the results stay on your original records:
```js theme={null}
Ontology("Actor").sumLinked("Screenings", "ticketsSold").maxDepth(2)
```
Returns every actor, each with `sum.ticketsSold`: the tickets sold across all screenings of all their movies (Actor → Movies → Screenings). Each screening counts once per actor, however many paths reach it.
To get the aggregate per record of an in-between dataset instead, traverse to it first and aggregate from there. The results then belong to that dataset, not to the one you started from:
```js theme={null}
Ontology("Actor").searchAround().find("Movies").sumLinked("Screenings", "ticketsSold")
```
Returns movies, not actors, each with `sum.ticketsSold` for that movie's own screenings.
A `filter()` placed after `countLinked()` / `sumLinked()` applies to the **linked** dataset's columns and restricts which linked records are counted or summed:
```js theme={null}
Ontology("Movies").countLinked("Actor").filter(age < 40)
```
Unlike `groupBy()` metrics, the `_link_count` and `sum.` fields produced by `countLinked()` / `sumLinked()` cannot be used in a `filter()`. Apply any threshold on the returned rows instead.
## Natural Language Query Generation
The system can generate queries from natural language questions. For example:
Natural language: "What is the age of the director of Braveheart?"
Generated query:
```js theme={null}
OntologyObject("Movies").filter(name == "Braveheart").searchAround().find("Actor")
```
## Examples
### Basic Query Examples
1. Find all movies:
```js theme={null}
Ontology("Movies")
```
2. Find a specific movie:
```js theme={null}
Ontology("Movies").filter(name == "Argo")
```
3. Find movies released before 2000:
```js theme={null}
Ontology("Movies").filter(year < 2000)
```
### Relationship Query Examples
1. Find actors who directed movies:
```js theme={null}
Ontology("Movies").searchAround().find("Actor")
```
2. Find movies directed by actors under 60:
```js theme={null}
Ontology("Actor").filter(age < 60).searchAround().find("Movies")
```
3. Find actors who directed movies, then find other actors who worked with them:
```js theme={null}
Ontology("Actor").filter(age < 60).searchAround().find("Movies").searchAround().find("Actor")
```
### Sorting and Limiting Examples
1. Find the 3 oldest movies:
```js theme={null}
Ontology("Movies").sortAscending("year").limit(3)
```
2. Find the 5 highest budget movies:
```js theme={null}
Ontology("Movies").sortDescending("budget").limit(5)
```
### Aggregation Examples
1. Count movies released before 2000:
```js theme={null}
Ontology("Movies").filter(year < 2000).count()
```
2. Total budget per director, top 5:
```js theme={null}
Ontology("Movies").groupBy("director").sum("budget").sortDescending("sum.budget").limit(5)
```
3. Directors with more than two movies:
```js theme={null}
Ontology("Movies").groupBy("director").count().filter(count.director > 2)
```
4. Movies ranked by number of linked actors:
```js theme={null}
Ontology("Movies").countLinked("Actor").sortDescending("_link_count").limit(10)
```
## **Error Handling**
If a query returns unexpected results:
1. Check field names and table relationships for correctness.
2. Ensure the depth in `searchAround` is sufficient for your query.
3. Debug by breaking the query into smaller parts and testing them individually.
# Understanding the UI: Home Page
Source: https://docs.landfall.ai/ui/home
Move between tasks quickly, review your recent activity, and navigate to other areas of the platform
The Home page is the first screen you see after signing in. It gives you a clear view of the data spaces your team has created and the datasets that live inside them.
## Page layout
The interface is organized into two main sections: a left navigation panel and a central workspace.
### Left navigation menu
The navigation menu provides quick access to key areas of the platform.
* **Home:** Returns you to this main dashboard from anywhere in the application.
* **Create a new dataset:** Opens the workflow for uploading or building a new dataset.
* **Connections Overview:** Gives you access to semantic relationships and linked entities once your data has been interconnected.
* **Recents:** Helps you pick up where you left off by listing items you viewed or edited recently.
* **Settings:** Lets you manage personal preferences, credentials, and related account details.
At the bottom of this panel, you can view your profile, including your name and associated email address. A control is provided to collapse or expand the sidebar, depending on your preferred workspace layout.
## Central workspace: namespaces and datasets
The main area of the Home page focuses on the data available to you and your team.
### Namespace groups
Data is presented in grouped sections. Each group represents a *namespace*, which acts as a container for related datasets. Namespaces help teams organize collections of data that belong to a shared project or theme.
Each namespace card displays:
* The namespace name
* The number of datasets it contains
* The datasets themselves, shown as individual tiles
* Permissions indicators, such as lock icons when access is restricted
* A menu for namespace-level actions
### Dataset tiles
Within each namespace, datasets are displayed as separate tiles. Each tile includes:
* A dataset icon
* The dataset name
* The owner or creator of the dataset
* A permission indicator if the dataset is restricted
* An options menu for dataset actions
These tiles act as your primary entry point into working with data. Selecting a tile opens the dataset so you can review its schema, manage ingestion, or explore its connections.
## Tools and controls
### Search bar
A search field above the workspace lets you filter through visible content. You can type part of a namespace or dataset name to quickly locate what you need.
### Layout toggle
A view control next to the search bar allows you to switch between grid and list layouts. Use whichever view makes it easier to scan through your data.
## Summary
The Home page functions as your central hub. It organizes your datasets into meaningful groups, provides tools for searching and viewing, and offers quick navigation to other areas of the platform. The layout supports both exploration and focused work, giving you an efficient starting point each time you log in.
# Understanding the UI: Create a New Dataset
Source: https://docs.landfall.ai/ui/new-dataset
The Create a new dataset window defines the properties of your new dataset
When you select **Create a new dataset**, a setup window opens to guide you through the first steps of building a dataset. This window helps you define the dataset’s identity, place it within the right namespace, and choose its visibility before you upload any data.
## Dataset name
The first field asks you to provide a dataset name. This name appears throughout the platform, so it helps to choose something meaningful and clear.
Use underscores instead of hyphens when naming datasets.
Dataset names use an underscore format. This style makes names easier to reference programmatically and keeps them consistent across the platform.
## Namespace
Every dataset belongs to a namespace. A namespace is a group that brings related datasets together. You will either select an existing namespace or enter the name for a new one. Namespace names also follow the underscore format.
If you plan to create several datasets that share a theme or domain, placing them in the same namespace helps keep everything organized and easier to browse later.
## Dataset visibility
This setting determines who can view the dataset once it is created.
* **Private**\
A private dataset is visible only to you and your team.
* **Public**\
A public dataset is available for anyone across the DataLinks platform to view and connect to. Public datasets can help others link their data or understand shared concepts.
You can switch the visibility before creation by using the toggle in the modal.
## Action buttons
At the bottom of the modal, you will find two buttons.
* **Cancel** closes the window without creating anything.
* **Create dataset and upload data** completes the creation step and moves you to the upload workflow, where you can add files and begin ingestion.
This flow ensures that the dataset exists before any data is added to it.
## Creating a Dataset via the API
The modal includes a link to **Create a dataset via API**, which opens a panel showing the request format for creating datasets programmatically. This option is helpful if you are building automated pipelines or managing datasets through scripts.
For detailed instructions, see [How to create a dataset](/how-to/create-dataset).
# Understanding the UI: Connections Overview
Source: https://docs.landfall.ai/ui/ontology-explorer
View and manage connections at the namespace level.
The Connections Overview helps you understand how datasets relate to each other within a namespace, and how those datasets connect to other datasets in other namespaces. It provides a visual layout of the datasets you have selected and offers tools for reviewing, adding, and removing connections (semantic links) between them.
This view is especially helpful when you want to refine how objects connect, explore inferred relationships, or adjust the structure of your context graph.
## Visual workspace
The center of the screen displays your datasets as nodes. Each node represents a dataset, and lines between nodes show existing active links. The layout adjusts automatically as you work, allowing you to zoom or pan to keep everything in view.
When you select a dataset in the workspace, the right-side panel opens to show link details. This panel is where you review suggested connections and manage the links already in place.
## Namespace selector
At the top-right corner of the page, the namespace selector lets you switch between namespaces.
When you switch namespaces, only the datasaets in that namespace and with active or suggested connections to that dataset appear.
## Saving your connections
After adding or removing links, select **Save connections** to store your changes. Saving updates the semantic structure for all datasets in the namespace.
## View controls
You can manage the dataset visualtion using the view controls in the bottom-left corner of your screen.
Click the **plus (+)** or **minus (-)** buttons to zoom in or out, the **frame button** to fit all datasets in your namespace into view, and the **lock button** to freeze datasets in place (so you can't accidently click a dataset and select it).
## Link details panel
When you click a dataset in the Connections Overview, it opens the Link details panel, where you can manage suggested and active links.
### Suggested Links
When you click a dataset, the suggested links panel appears and lists potential relationships between fields across your datasets. These suggestions come from the platform’s semantic analysis. Each row shows:
* The source field
* The target field
* A score that reflects link relevance
* The match type
You can select a suggested link by clicking the row. When selected, the row turns green, indicating that the link is ready to be added. A plus sign appears on the right side of the row. Selecting the plus sign moves the link into the Active Links section.
You can select several suggested links at once by using the checkbox at the top of the list.
### Active links
The Active Links section shows the links that are currently part of the connections for the selected namespace. Each active link displays:
* The source field
* The target field
* A relevance score
* The match type
If you select an active link, the row highlights in red. A minus sign appears on the right side of the row. Selecting the minus sign removes the link from the active list.
You can clear all selected links using the **Clear selection** option at the top of the section.
## Summary
The Connections Overview gives you an intuitive way to inspect and adjust how datasets connect. You can accept or reject suggested relationships, refine existing ones, and shape the knowledge structure that powers downstream analysis and semantic queries. The workspace, link panels, and controls provide a clear path for exploring and improving your connections as your data evolves.
# Understanding the UI: Settings
Source: https://docs.landfall.ai/ui/settings
Find your official DataLinks username and generate API tokens.
The Settings page is where you manage your account details and access credentials. From here, you can update your password and create or manage API tokens used to authenticate requests.
You can open Settings from the left navigation at any time.
## Page layout
The Settings page is organized into two tabs:
* Account settings
* Tokens
Each tab focuses on a specific set of actions and keeps sensitive operations clearly separated.
## Account settings
Use Account settings to manage your login credentials.
### Update your password
This section lets you change the password associated with your account.
Fields
* Current password: Your existing password
* New password: The password you want to switch to
* Confirm new password: Re-enter the new password to confirm
To update your password:
1. Enter your current password.
2. Enter a new password.
3. Re-enter the new password to confirm.
4. Click **Update password**.
If the update is successful, the new password takes effect immediately.
## Tokens
Tokens are used to authenticate API requests. Each token represents a secure credential that can be used in your applications or integrations.
When no tokens exist, this page shows an empty state with a shortcut to create one.
### Create a new token
Select Create a new token to generate an API token.
On the Create a new token screen, provide the following:
* Token name: A descriptive name to help you recognize the token later
* Expiration date: An optional expiration date for the token
After creating a token, copy it and store it securely. For security reasons, the token value is only shown once.
See [How to get an API token](/how-to/get-api-token) for detailed instructions.
### Managing tokens
From the Tokens tab, you can:
* View existing tokens
* Add new tokens
* Remove tokens that are no longer needed
Deleting a token immediately revokes its access.
## Security notes
* Keep tokens private and store them securely.
* Rotate tokens regularly and remove any that are no longer in use.
* Use password updates if you believe your account credentials may be compromised.