# Telden API Quickstart — Connect Your Agent

This page gets you from zero to a working agent connection in under five minutes. Every `curl` command is copy-pasteable and returns verifiable output. No MCP client required — just a terminal and a Telden API key.

## Prerequisites

- A Telden workspace (sign up at [telden.eu](https://telden.eu/))
- An API key with the scopes you need (create in Settings → Integrations)
  - **Trial plan:** `skus:read` and `extractions:read` for inspection
  - **Pro plan:** add `extractions:write`, `export`, and `import:write` for full automation
- `curl` and `jq` installed (pre-installed on macOS and most Linux distributions)

## Test your connection

Before running any workflow, verify your API key is valid and your workspace is reachable:

```bash
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" https://telden.eu/api/v1/skus?limit=1 | jq .
```

Expected response (200 OK):

```json
{
  "data": [],
  "next_cursor": null,
  "has_more": false,
  "auth_method": "apikey"
}
```

A 401 error means your API key is missing or invalid. A 403 means the key lacks the `skus:read` scope.

If you receive a valid response (empty `data` array is fine for a fresh workspace), your connection works. Refer to the examples below for the three core agent workflows.

## Choose your start path

Telden's empty-workspace UI offers three equally valid start modes. API and MCP users map to the same primitives — no separate MCP tool per UI tier:

| Start mode | When to use | Primary API / MCP primitives |
|------------|-------------|------------------------------|
| **Upload everything** | Mixed CSVs, PDFs, certificates, or folder exports | Bulk upload on `/app/upload`; document upload via MCP `uploadDocument` (`extractions:write`) |
| **Guided setup** | Spreadsheet-first catalog with deliberate column mapping and review | `POST /api/import` or MCP `importSkus` (`import:write`); then `uploadDocument` and `triggerExtraction` |
| **Start from scratch** | No files yet — create products and supporting records by hand | `POST /api/skus` (session cookie) or the `/app/skus?new=1` UI |

The walkthrough below follows **guided setup** (import → upload → extract → export). For upload-all, skip to Step 4 with documents attached to existing or auto-created SKUs. For manual creation, use `POST /api/skus` with a session cookie before uploading evidence.

### Create a SKU manually (session)

`POST /api/skus` requires a signed-in browser session (same auth as the app UI). Agents driving a headless browser can preview the request with `dry_run=true`, then create the record with an `Idempotency-Key`. MCP users can use `importSkus` with one structured row and `dry_run: true` as the MCP equivalent.

Dry-run preview:

```bash
curl -s -X POST \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: manual-sku-preview-001" \
  -b "sb-access-token=$TELDEN_SESSION_TOKEN" \
  -d '{
    "sku_code": "MANUAL-001",
    "product_name": "Manual Quickstart Widget",
    "category_code": "general"
  }' \
  "https://telden.eu/api/skus?dry_run=true" | jq .
```

Expected preview response (200 OK):

```json
{
  "dry_run": true,
  "can_create": true,
  "normalized_sku": {
    "sku_code": "MANUAL-001",
    "product_name": "Manual Quickstart Widget",
    "gtin": null,
    "category_code": "general",
    "external_sku_id": null
  },
  "category": { "selected_code": "general", "defaulted": false },
  "requirement_profile": { "would_attach_default": true, "id": "019a..." },
  "plan_limit": { "plan": "pro", "limit": 10000, "current_count": 0 },
  "blocking_reason": null
}
```

```bash
curl -s -X POST \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: manual-sku-create-001" \
  -b "sb-access-token=$TELDEN_SESSION_TOKEN" \
  -d '{
    "sku_code": "MANUAL-001",
    "product_name": "Manual Quickstart Widget",
    "category_code": "general"
  }' \
  "https://telden.eu/api/skus" | jq .
```

**Auth:** session cookie (`sb-access-token`), not API key
**Safety:** `dry_run=true` validates duplicate codes, demo-workspace blocking, billing limits, selected/default category, and default requirement-profile attachment without creating a SKU, activity event, requirement evaluation, or Inngest event.
**MCP equivalent:** call `importSkus` with `rows: [{ sku_code, product_name, category_code }]`, `dry_run: true`, and an `idempotency_key`; then repeat with `dry_run: false` when ready.
**UI equivalent:** `/app/skus?new=1` (Start from scratch tier on the empty workspace page)

## Quick examples

Replace `$TELDEN_API_KEY` with your actual `tdn_`-prefixed key.

### List SKUs (`skus:read`)

```bash
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" \
  "https://telden.eu/api/v1/skus?limit=5" | jq .
```

Expected response (200 OK):

```json
{
  "data": [
    {
      "id": "a1b2c3d4-...",
      "workspace_id": "w1x2y3z4-...",
      "sku_code": "EXAMPLE-001",
      "product_name": "Example Product",
      "gtin": null,
      "category_code": "general",
      "review_state": "red",
      "approval_state": "draft",
      "created_at": "2026-01-15T10:30:00Z"
    }
  ],
  "next_cursor": "2026-01-15T10:30:00Z",
  "has_more": true,
  "auth_method": "apikey"
}
```

**Scope required:** `skus:read`
**Plan:** Trial + Pro

### Trigger extraction (`extractions:write`)

First list your SKUs to get a SKU ID, then trigger re-extraction on its documents:

```bash
# Dry-run first — preview without executing
curl -s -X POST \
  -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  "https://telden.eu/api/v1/skus/YOUR_SKU_ID/re-extract?dry_run=true" | jq .
```

Expected dry-run response (200 OK):

```json
{
  "dry_run": true,
  "message": "[DRY RUN] Would trigger extraction for 3 document(s) of SKU a1b2c3d4-...",
  "total": 3,
  "extraction_configured": true,
  "documents": [
    { "id": "d1e2f3g4-...", "storage_path": "ws_xyz/sku_id/declaration.pdf" }
  ]
}
```

When ready to execute, drop the `dry_run=true` parameter:

```bash
curl -s -X POST \
  -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  "https://telden.eu/api/v1/skus/YOUR_SKU_ID/re-extract" | jq .
```

Expected success response (200 OK):

```json
{
  "message": "Re-extraction triggered for 3/3 documents",
  "triggered": 3,
  "total": 3,
  "dispatch_failed": 0,
  "extraction_configured": true,
  "documents": [
    {
      "id": "d1e2f3g4-...",
      "processing_state": "queued",
      "usability_state": "pending",
      "extraction_error_code": null,
      "extraction_error_message": null,
      "extraction_attempts": 1,
      "extracted_facts_count": 0
    }
  ]
}
```

**Scope required:** `extractions:write` (also accepts `skus:write`)
**Plan:** Pro only
**Safety:** Always use `dry_run=true` first. Use `Idempotency-Key` for automated calls.

### Export a SKU dossier (`export`)

```bash
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" \
  "https://telden.eu/api/skus/YOUR_SKU_ID/export" | jq .
```

Expected response (200 OK):

```json
{
  "exported_at": "2026-01-15T12:00:00.000Z",
  "sku": {
    "id": "a1b2c3d4-...",
    "sku_code": "EXAMPLE-001",
    "product_name": "Example Product",
    "gtin": null,
    "category_code": "general",
    "review_state": "green",
    "approval_state": "approved",
    "created_at": "2026-01-15T10:30:00Z"
  },
  "dossier_fields": [
    {
      "field_key": "product_name",
      "label": "Product Name",
      "value": "Example Product",
      "status": "auto",
      "confidence": 0.95,
      "provenance": {
        "document_id": "d1e2f3g4-...",
        "document_filename": "declaration.pdf",
        "page": 2
      }
    }
  ],
  "entities": [],
  "documents": [],
  "warnings": [],
  "issues": [],
  "audit_summary": {
    "unresolved_issues": 0,
    "active_warnings": 0,
    "documents": 0
  },
  "completeness": {
    "status": "sparse",
    "dossier_completeness_pct": 10,
    "fields_extracted": 1,
    "fields_filled": 1,
    "fields_total": 10,
    "has_extraction_provenance": true,
    "documents_count": 0,
    "unresolved_issues": 0,
    "active_warnings": 0,
    "review_state": "green",
    "publish_ready": false
  }
}
```

For CSV format, add `?format=csv`:

```bash
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" \
  "https://telden.eu/api/skus/YOUR_SKU_ID/export?format=csv"
```

**Scope required:** `export`
**Plan:** Pro only

### Import SKUs (`import:write`)

The complete import workflow (with CSV file creation, dry-run, and execution) is
documented in the [Pro write-side workflow](#pro-write-side-workflow-api-key--import--upload--extract--read-results--export) above. See also Step 2 in the MCP tool_call section for the MCP equivalent.

## Pro write-side workflow: API key → import → upload → extract → read results → export

This is the complete copy-pasteable walkthrough for an agent or developer
connecting to a Pro workspace. Every `curl` command is runnable as-is after
you set `$TELDEN_API_KEY`. The examples use `https://telden.eu`; replace with
`https://stage.telden.eu` for staging validation.

### Step 1: Create a Pro API key

Go to **Settings → Integrations** in the Telden dashboard or use the API:

```bash
# Create a Pro API key with all write scopes (requires session cookie)
curl -s -X POST \
  -H "Content-Type: application/json" \
  -b "sb-access-token=$TELDEN_SESSION_TOKEN" \
  -d '{
    "name": "Agent Quickstart Key",
    "scopes": ["skus:read", "extractions:read", "extractions:write", "export", "import:write"]
  }' \
  "https://telden.eu/api/workspace/api-keys" | jq .
```

Growth/Trial workspaces can create one evaluation key, but it is read-only and
limited to `skus:read` and `extractions:read`. The write-side workflow below
requires a Pro key with the full supported scope set.

Expected response (201 Created):

```json
{
  "data": {
    "id": "key_abc123...",
    "name": "Agent Quickstart Key",
    "key": "tdn_v1_abc123...",
    "key_prefix": "tdn_v1",
    "scopes": ["skus:read", "extractions:read", "extractions:write", "export", "import:write"],
    "created_at": "2026-05-18T12:00:00Z"
  }
}
```

> **The full key value is only returned once.** Copy it immediately — you
> cannot retrieve it later. Set `export TELDEN_API_KEY="tdn_v1_..."` for the
> examples below.

**Minimum scopes for the write-side workflow:**

| Scope | Needed for |
|-------|-----------|
| `skus:read` | Listing SKUs, listing documents |
| `extractions:read` | Reading import/extraction reports |
| `extractions:write` | Document upload, triggering extraction |
| `export` | Exporting SKU dossiers |
| `import:write` | CSV import of SKUs |

### Step 2: Import SKUs via CSV

Create a CSV file and import it with `POST /api/import`. Always dry-run first.

```bash
# Create a minimal CSV
cat > /tmp/quickstart-import.csv << 'CSV'
sku_code,product_name,category_code,gtin
QUICKSTART-001,Quickstart Cotton T-Shirt,apparel,0123456789012
QUICKSTART-002,Quickstart Ceramic Mug,houseware,0987654321098
CSV

# Dry-run — preview without creating SKUs
curl -s -X POST \
  -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: import-preview-$(uuidgen)" \
  -F "file=@/tmp/quickstart-import.csv" \
  "https://telden.eu/api/import?dry_run=true" | jq .
```

Expected dry-run response (200 OK):

```json
{
  "dry_run": true,
  "batch_id": null,
  "total": 2,
  "created": 2,
  "updated": 0,
  "skipped": 0,
  "failed": 0,
  "status": "completed",
  "errors": [],
  "skippedRows": [],
  "failedRows": [],
  "parse_metadata": {
    "format": "csv",
    "encoding": "utf-8",
    "delimiter": ",",
    "sourceFilename": "quickstart-import.csv"
  }
}
```

When the dry-run looks good, execute for real. **Real import via API key requires
human approval** — add the `X-Human-Approval-Token` header (any non-empty value):

```bash
curl -s -X POST \
  -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: import-exec-$(uuidgen)" \
  -H "X-Human-Approval-Token: approved" \
  -F "file=@/tmp/quickstart-import.csv" \
  "https://telden.eu/api/import" | jq .
```

Expected response (200 OK):

```json
{
  "dry_run": false,
  "batch_id": "019a1234-5678-...",
  "total": 2,
  "created": 2,
  "updated": 0,
  "skipped": 0,
  "failed": 0,
  "status": "completed",
  "errors": [],
  "skippedRows": [],
  "failedRows": []
}
```

**Scope required:** `import:write`
**Plan:** Pro only
**Safety:** Always use `?dry_run=true` first. `X-Human-Approval-Token` is required
for non-dry-run API key imports.

### Step 3: List your imported SKUs to get their IDs

After import, list your SKUs to capture the IDs for the next steps:

```bash
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  "https://telden.eu/api/v1/skus?limit=5" | jq '.data[] | {id, sku_code, product_name}'
```

Set the SKU ID you want to work with:

```bash
SKU_ID="019a1234-5678-..."  # replace with a real ID from the listing above
```

### Step 4: Upload a document

Documents are uploaded through the MCP JSON-RPC endpoint. Encode a PDF and
call the `uploadDocument` tool.

```bash
# Create a minimal test PDF
echo '%PDF-1.4 test content' > /tmp/quickstart-doc.pdf

# Encode and upload via MCP
PDF_B64=$(base64 -w0 /tmp/quickstart-doc.pdf)

curl -s -X POST \
  -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d "$(jq -n \
    --arg sku "$SKU_ID" \
    --arg content "$PDF_B64" \
    '{
      jsonrpc: "2.0",
      id: 1,
      method: "tools/call",
      params: {
        name: "uploadDocument",
        arguments: {
          sku_id: $sku,
          content: $content,
          filename: "quickstart-doc.pdf",
          mime_type: "application/pdf",
          document_type: "test_report",
          trigger_extraction: false,
          dry_run: false
        }
      }
    }')" \
  "https://telden.eu/api/mcp" | jq .
```

Expected response (200 OK):

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"message\":\"Document uploaded successfully.\",\"dry_run\":false,\"document_id\":\"019a2234-...\",\"filename\":\"quickstart-doc.pdf\",\"extraction_triggered\":false,\"duplicate\":false}"
      }
    ]
  }
}
```

Capture the document ID:

```bash
DOC_ID="019a2234-..."  # replace with the document_id from the response
```

**Scope required:** `extractions:write`
**Plan:** Pro only
**Safety:** Set `dry_run: true` to preview. Set `trigger_extraction: false` to
upload without triggering extraction.

### Step 5: Trigger extraction and poll for completion

Trigger re-extraction on the SKU (all documents), then poll for status:

```bash
# Dry-run first
curl -s -X POST \
  -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  "https://telden.eu/api/v1/skus/$SKU_ID/re-extract?dry_run=true" | jq .
```

Expected dry-run response (200 OK):

```json
{
  "dry_run": true,
  "message": "[DRY RUN] Would trigger extraction for 1 document(s) of SKU 019a1234-5678-...",
  "total": 1,
  "extraction_configured": true,
  "documents": [
    { "id": "019a2234-...", "storage_path": "ws_xyz/sku_id/..." }
  ]
}
```

```bash
# Execute extraction
curl -s -X POST \
  -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: extract-$(uuidgen)" \
  "https://telden.eu/api/v1/skus/$SKU_ID/re-extract" | jq .
```

Expected response (200 OK):

```json
{
  "message": "Re-extraction triggered for 1/1 documents",
  "triggered": 1,
  "total": 1,
  "dispatch_failed": 0,
  "extraction_configured": true,
  "documents": [
    {
      "id": "019a2234-...",
      "processing_state": "queued",
      "usability_state": "pending",
      "extraction_error_code": null,
      "extraction_error_message": null,
      "extraction_attempts": 1,
      "extracted_facts_count": 0
    }
  ]
}
```

Extraction runs asynchronously. Poll for completion using the document listing
endpoint:

```bash
# Poll until the document shows extraction results
# (processing_state will change from "queued" → "processing" → "completed")
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  "https://telden.eu/api/skus/$SKU_ID/documents" | jq '
    .documents[] | {id, filename, processing_state, usability_state, extraction_attempts, extracted_facts_count}'
```

**Scope required:** `extractions:write` (trigger), `skus:read` (poll)
**Plan:** Pro only
**Safety:** Always dry-run first. Use `Idempotency-Key` for automated calls.
Extraction typically completes in 10–60 seconds per document.

### Step 6: Read extraction results

After extraction completes, read the full SKU dossier with provenance:

```bash
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  "https://telden.eu/api/skus/$SKU_ID" | jq .
```

Expected response (200 OK):

```json
{
  "id": "019a1234-5678-...",
  "sku_code": "QUICKSTART-001",
  "product_name": "Quickstart Cotton T-Shirt",
  "gtin": "0123456789012",
  "category_code": "apparel",
  "review_state": "needs_review",
  "approval_state": "draft",
  "blocker_count": 2,
  "blockers": [
    {
      "code": "missing_document_declaration_of_conformity",
      "severity": "blocker",
      "message": "Declaration of Conformity document is missing."
    }
  ],
  "can_generate_draft": false,
  "can_publish": false,
  "created_at": "2026-05-18T12:00:00Z"
}
```

To see which fields were extracted with provenance (source document + confidence):

```bash
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  "https://telden.eu/api/skus/$SKU_ID/export?format=json" | jq '.dossier_fields[] | {field_key, value, status, confidence, provenance}'
```

**Scope required:** `skus:read`
**Plan:** Trial + Pro

### Step 7: Export the enriched SKU dossier

```bash
# Export as JSON
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  "https://telden.eu/api/skus/$SKU_ID/export?format=json" | jq .

# Export as CSV
curl -s -H "Authorization: Bearer $TELDEN_API_KEY" \
  -H "Accept: application/json" \
  "https://telden.eu/api/skus/$SKU_ID/export?format=csv"
```

**Scope required:** `export`
**Plan:** Pro only
**Formats:** `json` (default) or `csv`

## MCP tool_call equivalents (steps 2–6)

For agents using the MCP endpoint directly, here are the equivalent JSON-RPC
`tools/call` messages for each write-side step. All requests go to
`POST https://telden.eu/api/mcp` with `Authorization: Bearer $TELDEN_API_KEY`
and `Content-Type: application/json`.

### Step 2: importSkus

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "importSkus",
    "arguments": {
      "csv_text": "sku_code,product_name,category_code\nQUICKSTART-001,Quickstart Widget,general\nQUICKSTART-002,Quickstart Mug,houseware",
      "dry_run": true,
      "idempotency_key": "import-preview-001"
    }
  }
}
```

**Scope required:** `import:write`

### Step 4: uploadDocument

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "uploadDocument",
    "arguments": {
      "sku_id": "019a1234-5678-...",
      "content": "<base64-encoded-pdf>",
      "filename": "declaration.pdf",
      "mime_type": "application/pdf",
      "document_type": "declaration_of_conformity",
      "dry_run": false,
      "trigger_extraction": true
    }
  }
}
```

**Scope required:** `extractions:write`

### Step 5: triggerExtraction

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "triggerExtraction",
    "arguments": {
      "target_type": "sku",
      "target_id": "019a1234-5678-...",
      "dry_run": true,
      "idempotency_key": "extract-001"
    }
  }
}
```

**Scope required:** `extractions:write`

### Step 6: get_sku_dossier (read results)

```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "get_sku_dossier",
    "arguments": {
      "sku_id": "019a1234-5678-..."
    }
  }
}
```

**Scope required:** `skus:read`

### Step 7: exportSku

```json
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "exportSku",
    "arguments": {
      "sku_id": "019a1234-5678-...",
      "format": "json"
    }
  }
}
```

**Scope required:** `export`

## Plan tier reference

| Scope | Trial | Pro | Endpoints |
|-------|-------|-----|-----------|
| `skus:read` | ✅ | ✅ | `GET /api/v1/skus`, `GET /api/skus` |
| `extractions:read` | ✅ | ✅ | MCP `get_import_report` |
| `extractions:write` | — | ✅ | `POST /api/v1/skus/:id/re-extract` |
| `export` | — | ✅ | `GET /api/skus/:id/export`, `GET /api/export` |
| `import:write` | — | ✅ | MCP `importSkus` |

**Trial plan:** Read-only access to SKU listings and extraction reports.
**Pro plan:** Full read/write access including extraction triggers, exports, and imports.

All mutation endpoints support `dry_run=true` for safe preview and `Idempotency-Key` for replay safety.

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| 401 Unauthorized | Missing or expired token | Verify `TELDEN_API_KEY` is set and starts with `tdn_` |
| 403 Forbidden / INSUFFICIENT_SCOPE | Key lacks the required scope | Check scope list in Settings → Integrations; upgrade plan if needed |
| 404 Not Found | Wrong SKU ID or workspace mismatch | Verify the SKU belongs to your key's workspace |
| 503 extraction_not_configured | AI extraction backend not running | Contact support or check server extraction configuration |
| `dry_run: true` in response | You included `?dry_run=true` | Remove the parameter to execute for real |

## Next steps

- Configure your MCP client: see [/docs/mcp.md](/docs/mcp.md) for Claude Desktop and Cursor config snippets
- Full API reference: [/docs/api.md](/docs/api.md)
- MCP tool capability matrix: [/docs/mcp.md](/docs/mcp.md)
- OpenAPI contract: [telden.eu/api/openapi.json](https://telden.eu/api/openapi.json)
