> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ndi.nace.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a workspace

> Provision a durable corpus, upload files, and make them searchable

A workspace is persistent storage plus derived outputs. Uploading registers a
ledger row. Ingestion is a separate, billable call that builds the search
index and metadata catalog.

<Tabs>
  <Tab title="Python">
    ```python theme={"dark"}
    from ndi_sdk import NdiClient

    with NdiClient() as client:
        workspace = client.workspaces.create(name="fy25-audit")
        upload = client.jobs.wait(
            client.files.upload(
                workspace.workspace_id,
                "report.pdf",
                path="reports/report.pdf",
            ).job_id
        )
        client.jobs.wait(
            client.ingestion.ingest(
                workspace.workspace_id,
                path_prefix="reports/",
            ).job_id,
            timeout=600,
        )
        hits = client.tools.hybrid_search(
            workspace.workspace_id,
            query="total liabilities at year end",
            k=5,
        )
        for hit in hits.hits:
            print(hit.path, hit.snippet)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={"dark"}
    import { NdiClient } from "ndi-sdk";

    const client = new NdiClient();
    const workspace = await client.workspaces.create({ name: "fy25-audit" });
    await client.jobs.wait(
      (
        await client.files.upload(workspace.workspace_id, "report.pdf", {
          path: "reports/report.pdf",
        })
      ).job_id,
    );
    await client.jobs.wait(
      (await client.ingestion.ingest(workspace.workspace_id, { path_prefix: "reports/" })).job_id,
      { timeout: 600 },
    );
    const hits = await client.tools.hybridSearch(workspace.workspace_id, {
      query: "total liabilities at year end",
      k: 5,
    });
    for (const hit of hits.hits ?? []) {
      console.log(hit.path, hit.snippet);
    }
    ```
  </Tab>

  <Tab title="curl">
    ```bash theme={"dark"}
    WS=$(curl -s -X POST "$NDI_BASE_URL/v1/workspaces" \
      -H "X-API-Key: $NDI_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"name":"fy25-audit","domain_slug":"generic"}' | jq -r '.workspace_id')

    UPLOAD_JOB=$(curl -s -X POST "$NDI_BASE_URL/v1/workspaces/$WS/files" \
      -H "X-API-Key: $NDI_API_KEY" \
      -F "file=@report.pdf" \
      -F 'metadata={"path":"reports/report.pdf"}')
    FILE_JOB_ID=$(echo "$UPLOAD_JOB" | jq -r '.job_id')

    STATUS=$(curl -s "$NDI_BASE_URL/v1/jobs/$FILE_JOB_ID" -H "X-API-Key: $NDI_API_KEY" | jq -r '.status')
    until [[ "$STATUS" == "succeeded" || "$STATUS" == "failed" || "$STATUS" == "cancelled" ]]; do
      sleep 3
      STATUS=$(curl -s "$NDI_BASE_URL/v1/jobs/$FILE_JOB_ID" -H "X-API-Key: $NDI_API_KEY" | jq -r '.status')
    done

    INGEST_JOB=$(curl -s -X POST "$NDI_BASE_URL/v1/workspaces/$WS/ingestions" \
      -H "X-API-Key: $NDI_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"selection":{"type":"path_prefix","path_prefix":"reports/"}}' | jq -r '.job_id')

    STATUS=$(curl -s "$NDI_BASE_URL/v1/jobs/$INGEST_JOB" -H "X-API-Key: $NDI_API_KEY" | jq -r '.status')
    until [[ "$STATUS" == "succeeded" || "$STATUS" == "failed" || "$STATUS" == "cancelled" ]]; do
      sleep 3
      STATUS=$(curl -s "$NDI_BASE_URL/v1/jobs/$INGEST_JOB" -H "X-API-Key: $NDI_API_KEY" | jq -r '.status')
    done

    curl -s -X POST "$NDI_BASE_URL/v1/workspaces/$WS/tools/hybrid-search" \
      -H "X-API-Key: $NDI_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"query":"total liabilities at year end","k":5}'
    ```
  </Tab>
</Tabs>

`client.upload_and_ingest(...)` collapses the upload and ingest calls for a
single file.

## Creation options

| Field                        | Default         | Meaning                                                    |
| ---------------------------- | --------------- | ---------------------------------------------------------- |
| `name`                       | required        | Human-readable identifier                                  |
| `domain_slug`                | `generic`       | Taxonomy vocabulary                                        |
| `access.labels`              | `[]`            | Frozen `{name, description}` objects from the catalog      |
| `access.default_label`       | unset           | Applied when a file is uploaded without a classified label |
| `knowledge_graph.auto_build` | `false`         | Chain a graph build after every ingestion                  |
| `retention_policy`           | server defaults | Derived / source / job TTLs                                |

See [Access labels](/guides/access-labels) for the catalog and the workspace
allowed set.

## List, stats, delete

```python theme={"dark"}
from ndi_sdk import NdiClient

with NdiClient() as client:
    for workspace in client.workspaces.iter_all(name_contains="audit"):
        print(workspace.workspace_id, workspace.name)
        print(client.workspaces.stats(workspace.workspace_id))
```

Delete requires `confirm_name` matching the workspace name. It returns a
`workspace_delete` job and cannot be undone.

## Next

* [Large uploads](/guides/large-uploads)
* [Ingest and reconcile](/guides/ingest-reconcile)
* [Search](/guides/search)
* [Workspaces concept](/concepts/workspaces)
