---
title: API
description: Authenticate, create cards, upload files, and build reliable Teak integrations
---

Teak provides a public REST API for saving, querying, and syncing cards. Use this guide for integration decisions and the generated reference for every operation, field, schema, and code sample.

<CardGroup cols={2}>
  <Card title="API Reference" href="/reference" icon="code">
    Explore the complete OpenAPI contract and samples in curl, JavaScript, and Python.
  </Card>
  <Card title="MCP" href="/docs/mcp" icon="bot">
    Connect an AI client to Teak through its remote MCP server.
  </Card>
</CardGroup>

## Base URL

Production: `https://teakvault.com/api/v1`

The machine-readable OpenAPI document is available at `https://teakvault.com/api/openapi.json`. Local development URLs are in the [Development guide](/docs/development).

## Authentication

Every API request uses a bearer token in the `Authorization` header.

:::warning[Keep secrets out of logs]
Never commit `teakapi_` keys or paste them into public issue trackers. Rotate a key immediately if it leaks.
:::

Generate a long-lived `teakapi_` key in Teak Settings. Each account can have
up to 10 active keys, and new keys can be created at most five times per
minute. Use **Revoke all keys** if you need to clear every key, including any
that no longer appear in the list.

```bash
Authorization: Bearer teakapi_...
```

Native clients using browser sign-in send short-lived OAuth access tokens instead — see [Development](/docs/development#public-api-and-mcp).

## Create your first card

<CodeGroup>
```bash curl
curl https://teakvault.com/api/v1/cards \
  --request POST \
  --header "Authorization: Bearer $TEAK_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: first-card" \
  --data '{
    "content": "# Project notes\n\nKeep the original spacing.",
    "cardType": "text",
    "tags": ["project"]
  }'
```

```js JavaScript
const response = await fetch("https://teakvault.com/api/v1/cards", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TEAK_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "first-card",
  },
  body: JSON.stringify({
    content: "# Project notes\n\nKeep the original spacing.",
    cardType: "text",
    tags: ["project"],
  }),
});
```
</CodeGroup>

## Create behavior

`cardType` is optional. Set it to `text` to preserve raw Markdown exactly, even when the content looks like a URL, quote, or color. When omitted, Teak automatically detects links, quotes, and palettes.

<AutoTypeTable path="types/create-card.ts" name="CreateCardBody" />

A create response includes the new `cardId` and its Teak app URL. See **Create Card** in the [API Reference](/reference) for the complete request and response schemas.

## Upload files

File cards use a direct upload flow, so large bytes never pass through the REST API.

1. **Create an upload**

    Send the file name, media type, and size to `POST /v1/uploads`.

2. **Upload the bytes**

    `PUT` the file to the returned presigned URL and retain its `ETag` response header.

3. **Create the card**

    Send the returned `fileKey`, retained `fileEtag`, and file metadata to `POST /v1/cards`.

Files can be up to 100 MB. Uploaded `.md` and `.markdown` files become editable text cards, must contain valid UTF-8, and are limited to 512 KiB. `.mdx` remains a document.

Supported uploads include common image, audio, video, source, design-token, document, archive, and Figma formats. The [API Reference](/reference) is the canonical source for accepted fields and validation responses.

## Reliable requests

| Prop | Type | Default | Description |
| - | - | - | - |
| `X-Request-Id?` | `response header` | - | Returned for support and log correlation. |
| `Idempotency-Key?` | `request header` | - | Safely retries card creation and bulk operations without duplicating work. |
| `RateLimit-Limit?` | `response header` | - | Appears with Remaining, Reset, and Retry-After rate-limit headers. |

Bulk operations accept up to 100 items. Cursor-based listing and the card changes endpoint are available for integrations that need complete pagination or incremental sync.

## Endpoint overview

| Route | Purpose |
| --- | --- |
| `/v1/cards` | Create or list cards |
| `/v1/uploads` | Start a direct file upload |
| `/v1/cards/bulk` | Create or update multiple cards |
| `/v1/cards/changes` | Read incremental card changes |
| `/v1/tags` | List tags |
| `/v1/cards/:cardId` | Read, update, or delete one card |
| `/v1/cards/:cardId/favorite` | Favorite or unfavorite one card |

See `/openapi.json` or the [API Reference](/reference) for methods, parameters, schemas, and response examples.

## Search and favorites

Search and favorites are filtered card listings. `GET /v1/cards` supports
`q`, `type`, `tag`, `sort`, `createdAfter`, `createdBefore`, `limit`,
`cursor`, `favorited`, and `include`:

- Search: `GET /v1/cards?q=design&include=content,metadata`
- Favorites: `GET /v1/cards?favorited=true&include=content,metadata`

The `include=content,metadata` pair returns full-card fields. The list
response is paginated (`items` plus `pageInfo`).

Image card responses include intentionally different URLs: `thumbnailUrl` is the optimized grid rendition, `compactUrl` is a smaller rendition for compact lists, `placeholderUrl` is a tiny loading placeholder, `detailUrl` is the optimized full-card rendition, and `fileUrl` remains the signed original used for downloads. These URLs are temporary, signed credentials — consume them as returned instead of storing or modifying them. See the [API Reference](/reference) for field details.

## Errors

Errors keep a stable `{ code, error }` shape and can also include `requestId`, `details`, or `retryAt`.

```json
{
  "code": "INVALID_INPUT",
  "error": "Body must include `content` or `url`"
}
```

Use the returned request ID when contacting [Support](/docs/support). For MCP tool contracts, continue to the dedicated [MCP guide](/docs/mcp).

## Check for a saved URL

`GET /v1/cards/duplicate?url=<encoded-url>` accepts the same bearer credentials as other card endpoints. It checks the exact URL against your non-deleted cards and returns `{ "cardId": "..." }`, or `{ "cardId": null }` when no match exists. The URL must use HTTP or HTTPS and be at most 8,192 characters. Invalid input returns 400; invalid or revoked credentials return 401.

