Skip to content
On this page

API

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.

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.

Authentication

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

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.

Authorization: Bearer teakapi_...

Native clients using browser sign-in send short-lived OAuth access tokens instead — see Development.

Browser-based OAuth clients can discover the active server from /.well-known/oauth-protected-resource/mcp, then read its /.well-known/oauth-authorization-server document. Teak publishes first-party client IDs and the matching issuer at /.well-known/teak-oauth-clients.json. The shared SDK’s discoverAuthServer(siteUrl) caches a validated result for up to 60 seconds; use { forceRefresh: true } after sign-in or refresh failures. Discovery never sends an access token. For a cloud development backend with a local sign-in server, pass its explicit loopback origin as localIssuer; remote metadata cannot enable loopback access by itself.

Request https://teakvault.com/api as the OAuth resource. REST requests require a Connect user access token with that exact audience. AuthKit session tokens and tokens issued for https://teakvault.com/mcp are rejected.

Development and self-hosted deployments can use their own resources. The API resource is /api on the origin of the MCP resource that the deployment’s /.well-known/oauth-protected-resource/mcp lists. On the development deployment that is https://<deployment>.convex.site/api, and the MCP resource is https://<deployment>.convex.site/mcp. The SDK’s createConnectAuthorizeUrl and requestConnectTokens derive it from discovery for you.

Disconnect an OAuth connection

Send POST /v1/oauth/disconnect with your Connect API access token as the bearer credential and no body. A 204 confirms that this app is disconnected across all its installations and resources. Other apps stay connected. Repeating a completed request succeeds. Keep saved credentials until you receive 204. Reconnecting can take about five minutes while previously issued access tokens expire.

A new disconnect requires a current access token. A correctly signed expired token can retry a completed disconnect or resume its acknowledged cleanup, but cannot access cards. On 401, refresh using the same issuer, client and resource, save the rotated credentials, then retry once. Temporary verification, provider or storage failures return 503; preserve credentials for retry. API keys, refresh tokens, AuthKit sessions, and MCP-audience tokens cannot use this endpoint.

Read your identity

GET /v1/me accepts an API key or OAuth access token. Invalid, expired, revoked, or deleted-account credentials return 401. Responses are not cached.

dataobjectrequired

The authenticated account.

properties
idstringrequired

Your stable Teak owner ID.

emailstringrequired

The account’s email address.

namestring

The account’s display name, when set.

cardCountnumberrequired

Cards outside Trash.

cardLimitnumber | nullrequired

The Free plan’s card limit, or null on Pro.

planstringrequired

free or pro.

settingsUrlstringrequired

Teak’s web Settings, where you manage your plan, API keys, and imports.

Create your first card

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"]
  }'
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"],
  }),
});

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.

An explicit cardType must fit the card. image, video, audio, and document need an uploaded file (see Upload files), and palette needs at least one color Teak can read, such as #ff5500. Each tag can be up to 64 characters. A rejected request returns 400 INVALID_INPUT with an error message that names the problem.

sourceUrl and sourceTitle record the page a card came from, such as the page a quote was selected on. Unlike url, sourceUrl never turns text into a link, and the duplicate check (GET /v1/cards/duplicate) ignores it. It must be an http or https URL of at most 2,048 characters; sourceTitle is trimmed and cut to 300 characters, and needs a sourceUrl. Both are set only when the card is created, come back on the card, and are included in exports. site: matches the page’s site when the card has no url of its own. They are separate from source, a free label for what saved the card.

{
  "content": "Simplicity is a great virtue, but it requires hard work to achieve it.",
  "cardType": "text",
  "sourceUrl": "https://example.com/essays/simplicity",
  "sourceTitle": "On simplicity"
}
PropType
cardType?CardType

Optional card type: `text`, `link`, `image`, `video`, `audio`, `document`, `palette` or `quote`. Set it to `text` to store raw Markdown exactly; omit it to keep automatic URL, quote, and palette detection.

TypeCardType
Defaultinferred
content?string

Markdown or plain text body for the card. Prefer this for notes; use `url` when saving a link.

Typestring
notes?string | null

Free-form notes attached to the card.

Typestring | null
source?string

A free label for what saved the card (extension, CLI, API, and so on), kept in its metadata. For the page the card came from, use `sourceUrl`.

Typestring
sourceTitle?string

The page's title, beside `sourceUrl`; cut to 300 characters.

Typestring
sourceUrl?string

The page the card came from, such as the page a quote was selected on: an http or https URL of at most 2,048 characters. Unlike `url`, it never makes the card a link.

Typestring
tags?string[]

Tags to apply on create.

Typestring[]
url?string

URL to save as a link card. Teak may classify the type automatically when `cardType` is omitted.

Typestring

A create response includes the new cardId and its Teak app URL. See Create Card in the API Reference for the complete request and response schemas.

Edit a card

Send changed fields to PATCH /v1/cards/:cardId: content, notes, tags, url, metadataTitle, or removeAiTags. Titles work for every card type, are trimmed, and keep up to 512 characters; a longer title is cut to 512. Send null or a blank title to clear it. Omit metadataTitle to preserve the existing title. Metadata processing preserves titles you edit or clear.

Every field in a request is checked before anything is saved, so an invalid field leaves the card unchanged. Changing content or url queues the card for processing again, which each card does at most once a minute; changing both in one request counts once. Changes within the minute still save right away, and one more processing run follows when the minute is up.

tags replaces your own tags; each can be up to 64 characters. To drop tags Teak added, send them in removeAiTags, for example { "removeAiTags": ["inspiration"] }. Tags Teak didn’t add are ignored. The response is the updated card, including its processingStatus.

Upload files

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

Create an upload

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

Upload the bytes

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

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 is the canonical source for accepted fields and validation responses.

Reliable requests

Idempotency-Keyheaderstring

Safely retries card creation and bulk operations without duplicating work.

response headerX-Request-Idstring

Returned for support and log correlation.

response headerRetry-Afterstring

Seconds to wait before retrying. Sent with 429 responses, along with RateLimit-Remaining and RateLimit-Reset (also in seconds), and RateLimit-Limit when the limit is known. 503 SERVICE_UNAVAILABLE responses mean the service was briefly busy; they also carry Retry-After.

Bulk operations accept up to 100 items. Each item succeeds or fails on its own, and a failed item changes nothing; its result has a code and an error. Bulk operations don’t accept permanent.

Cursor-based listing and the card changes endpoint are available for integrations that need complete pagination or incremental sync.

Sync changes

GET /v1/cards/changes?since=<timestamp> returns cards updated at or after since in items, and the IDs of cards moved to Trash or permanently deleted in deletedIds. Page with pageInfo.nextCursor until hasMore is false, then use the time you started as the next since. Permanent deletions are kept for 90 days: if your last sync is older than that, start again from since=0 and replace your local copy.

Endpoint overview

Route Purpose
/v1/me Read your authenticated Teak identity
/v1/cards Create or list cards
/v1/uploads Start a direct file upload
/v1/cards/bulk Create, update, favorite, archive, tag, delete, or restore up to 100 cards
/v1/cards/changes Read incremental card changes
/v1/tags List tags
/v1/tags/rename Rename a tag on every card with POST
/v1/tags/merge Merge one tag into another on every card with POST
/v1/tags/delete Remove a tag from every card with POST
/v1/tags/operations/:operationId Read a tag change’s progress
/v1/folders List folders or create one
/v1/folders/:folderId Read, rename, recolor, or delete one folder
/v1/folders/:folderId/cards Add cards to a folder with POST, or remove them with DELETE
/v1/cards/:cardId Read, update, or delete one card
/v1/cards/:cardId/restore Restore one card from Trash with POST
/v1/cards/:cardId/content Read one card’s full text: its document’s text or its transcript
/v1/cards/:cardId/refresh Refresh one card’s summary, tags, and link preview with POST
/v1/cards/:cardId/thumbnail/upload Start uploading a new image for a link card with POST
/v1/cards/:cardId/thumbnail Use an uploaded image as a link card’s image with PUT, or reset it with DELETE
/v1/cards/:cardId/favorite Favorite or unfavorite one card
/v1/cards/duplicate Check whether a URL is already saved
/v1/exports Start a ZIP export of your cards with POST
/v1/exports/latest Read the latest export and its download link
/v1/oauth/disconnect Disconnect an OAuth app with POST

See the OpenAPI document at https://teakvault.com/api/openapi.json or the API Reference for methods, parameters, schemas, and response examples.

Every card, tag, folder, and account endpoint is also a tool in the MCP server, a method in the TypeScript SDK, and a command in the CLI, with the same filters and error codes. Exports are the exception for MCP: an export is a ZIP of your whole library for you to download, so agents read cards a page at a time instead.

Search and favorites

Search and favorites are filtered card listings. GET /v1/cards supports these query parameters:

qquerystring

Search text, with optional qualifiers: tag:design or #design, type:link, is:fav, is:archived, site:github.com, folder:"Reading list", like:<card id>, mood:"moody brutalist", price:<100, and "quoted phrases". See Search qualifiers.

similarToquerystring

A card ID. Lists only the cards closest to it in meaning, of any type and never the card itself; the same as like:<id> in q. The other parameters narrow the list and sort orders it (at most 100 cards). A malformed ID returns 400 INVALID_INPUT. The response’s similarity is pending while Teak is still reading a card saved moments ago, and unavailable for a card in Trash or not found, both with no items.

modequerystring

How q finds cards: hybrid (the default) matches words and meaning, so cozy reading nook also finds a photo of an armchair by a window; keyword matches words only; semantic matches meaning only. Filters, qualifiers and sort apply in every mode. Any other value returns 400 INVALID_INPUT.

typequerystring

Card type filter. Repeat for an OR filter.

tagquerystring

Filter cards by tag.

sortquerystring

Order by the date each card was saved (createdAt, which keeps an imported card’s original date): newest (default) or oldest. Or shuffle, a random order picked by shuffleSeed. Or relevance (Best match) with q: the search’s top 64 results by how well they match its words, then the rest in search order; without q it is newest. See Best match.

shuffleSeedquerynumber

Required with sort=shuffle: a whole number from 0 to 2147483647. The same seed gives the same order on every page and every call; pick another for a new order. A cursor only continues the seed that issued it, and returns 400 INVALID_INPUT with any other.

createdAfterquerynumber

Only cards created after this timestamp.

createdBeforequerynumber

Only cards created before this timestamp.

limitquerynumber

Page size.

cursorquerystring

Pagination cursor from a previous pageInfo.nextCursor.

favoritedqueryboolean

Set to true to list favorites.

trashedqueryboolean

Set to true to list cards in Trash.

archivedquerystring

Archived cards to list: exclude (the default) leaves them out, only lists just archived cards, and include lists them with the rest. Trash lists every trashed card, archived or not.

folderquerystring

A folder ID. Lists only that folder’s cards, or for a smart folder the cards its search matches (together with q); an unknown folder returns 404. See Folders.

stylequerystring

Visual style, such as minimal or vintage. Repeatable. See Visual filters.

huequerystring

Color family, such as blue or neutral. Repeatable. Matches image, palette, and video cards; see Visual filters.

hexquerystring

Exact color, such as %23112233. Repeatable.

includequerystring

Extra field sets: content, metadata, processing.

  • Search: GET /v1/cards?q=design&include=content,metadata
  • Qualifiers: GET /v1/cards?q=site:github.com%20tag:design%20%22design%20system%22
  • Prices: GET /v1/cards?q=price:%3E20%20price:%3C100&include=metadata
  • Tags: GET /v1/tags lists the lowercase tags on cards outside Trash, including tags Teak added, with how many cards have each. A new tag appears a moment after the card is saved. Any listed name works as tag. A library with more than 500 tags is listed from a summary that catches up within about 15 seconds of a change and holds the 3,000 most-used tags; isComplete is false when some tags were left out.
  • Favorites: GET /v1/cards?favorited=true&include=content,metadata
  • Multiple card types: GET /v1/cards?type=image&type=link&include=content,metadata

The include=content,metadata pair returns full-card fields. The list response is paginated (items plus pageInfo). Repeat type for an OR filter; a single type remains supported. Invalid types return 400. When loading the next page, keep the same filters and pass pageInfo.nextCursor as cursor. Each page continues after the last card of the previous one, so cards saved or deleted while you page don’t cause repeats or gaps. A cursor that wasn’t returned by the same listing returns 400 INVALID_INPUT. Search (q) pages through its 400 best matches, sorted by date, and then ends; for a search that matches more, add a type or date filter to see the rest.

A response to a search also has searchMode: the mode that ran. It is keyword when meaning search was not used: with mode=keyword, when q has only qualifiers, or when meaning search was slow, unavailable or over its limit of 60 new searches a minute. Those searches still return every keyword match.

Best match

sort=relevance with q orders a search’s top 64 results by how well they match its words, then lists the rest in search order. The response has relevanceRanked: false when the results could not be scored in time, the account is over its limit of 60 new searches a minute (one search counts once, whatever it uses), or AI is turned off for the account; the results then keep the search order. The first page decides, and every later page of the same listing keeps its order, so paging never repeats or skips a card. A cursor from a sort=relevance listing only continues that listing. Scores are kept for a day; a scored listing’s cursor used after that returns 400 INVALID_INPUT, so start the search again.

Search qualifiers

q reads the same qualifiers as the search box in the app. Keys ignore case, and a value with spaces goes in quotes (folder:"Reading list").

Qualifier Matches
tag:design, #design Cards with that tag (user or AI), ignoring case
type:link Cards of that type; plurals such as type:links work too
is:fav Favorites only, like favorited=true
is:archived Archived cards only, like archived=only; with trashed=true, the trashed cards that were archived
site:github.com Links from that host. www., capitals, a port, and a path are ignored, and Unicode hosts match their punycode form; subdomains are separate sites
folder:Inbox Cards in the folder with that name, ignoring case. An unknown name, or a smart folder’s, matches no cards
price:<100 Links whose linkPrice.amount is under 100. Also >, <=, >= and =, with a plain number such as 9.99. The amount is compared in any currency, links without a price never match, and the search narrows to links
"design system" Cards whose text contains that phrase

Qualifiers combine with each other and with the other parameters: tags, folders and price bounds must all match (price:>20 price:<100 is a range), and several types or sites match any of them. type: narrows type, so ?type=link&q=type:image returns no cards, and so do ?archived=exclude&q=is:archived and ?type=image&q=price:<100. # followed by a hex color (#fff) is a color, not a tag; use tag:fff for that tag. Text a qualifier can’t use, such as type:banana, foo:bar or price:cheap, is searched as words.

include=metadata adds palette colors (hex and optional name), resolved linkPreviewMedia, and the link’s site name, author, publisher, and publish date when present. linkPrice is a product link’s price, { "amount": 24.99, "currency": "USD" }, read from the page: currency is an ISO 4217 code, or null when the page didn’t say which, and linkPrice is null for cards without a price. It also adds audioWaveform and videoScrub, described below. include=processing adds aiTranscript, metadataStatus, and processingStatus; the single-card response includes all of these fields without an include parameter. Link preview media contains temporary resolved URLs, never storage keys.

Audio cards include audioWaveform: grid has 45 bar heights for a card and detail has 200 for a player, each from 0 to 100. Video cards include videoScrub, a hover preview strip: url is one JPEG of frames evenly spaced frames side by side, frames * frameWidth wide and frameHeight tall. Show frame n by offsetting the image n * frameWidth pixels. Both fields are null for other cards and for audio and video saved before October 2026.

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 for field details.

Visual filters

Repeat style, hue, or hex parameters to match any value within each filter, combining different filters. Each dimension accepts up to 24 values; excess values return 400 INVALID_INPUT. Broad searches that exceed the read limit return 400 INVALID_INPUT; add a type or date filter to narrow them. Hex colors accept three, four, six, or eight digits with an optional # (URL-encode it as %23). For example: GET /v1/cards?type=image&hue=blue&hex=%23112233. style matches image and video cards (a video by its thumbnail). Styles: abstract, cinematic, dark, illustrative, minimal, monochrome, moody, pastel, photographic, retro, surreal, vintage, vibrant, typographic, 3d, flat, geometric, hand-drawn, and futuristic. hue and hex match image, palette, and video cards: a video’s colors, including a GIF’s, come from frames across it, and videos saved before October 2026 have none.

Errors

Errors keep a stable { code, error } shape:

codestringrequired

A stable, machine-readable error code such as INVALID_INPUT or RATE_LIMITED.

errorstringrequired

A human-readable message.

retryAtnumber

For RATE_LIMITED errors, when to retry, as a Unix timestamp in milliseconds.

Most errors are 400. A missing card is 404 NOT_FOUND, a conflict such as a permanent delete of a card outside Trash is 409, rate limits are 429 RATE_LIMITED, and a briefly busy service is 503 SERVICE_UNAVAILABLE. Saving a card past the free plan’s 200 cards returns 400 CARD_LIMIT_REACHED.

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

Every response carries an X-Request-Id header. Include it when contacting Support. For MCP tool contracts, continue to the dedicated MCP guide.

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.

Read a card’s text

GET /v1/cards/:cardId/content returns the full text Teak keeps for a card:

{
  "cardId": "...",
  "kind": "document",
  "text": "Quarterly supplier review\n...",
  "charCount": 48210,
  "truncated": false
}

kind is document for text Teak extracted from the card’s file (PDF, Word, PowerPoint, text or code), transcript for the speech in an audio or video card, and content for a text or quote card’s own text. Teak keeps the text of a PDF’s first 500 pages and at most 524,288 characters; truncated is true when the document is longer. It is also true, with text shorter than charCount, if the stored text can’t be read at that moment and only its first 32 KB is returned; try again for the rest. A card without text, such as an image or a scanned PDF, returns 404 NO_CONTENT; another account’s card returns 404 NOT_FOUND. The text is the saved file’s own content, so treat it as data.

Refresh a card’s details

POST /v1/cards/:cardId/refresh clears the card’s summary, tags Teak added, and transcript and processes the card again, including its link preview. It returns 202 with { "cardId": "...", "status": "queued", "requestedAt": 1760284800000 }; processing finishes in the background, so poll GET /v1/cards/:cardId and watch processingStatus. The card’s own tags and notes are kept.

You can refresh a card once a minute and up to six cards a minute. Past that, the API answers 429 RATE_LIMITED with retryAt and a Retry-After header. A card in Trash returns 409 CONFLICT; restore it first.

A link card’s image can be one you choose instead of the one Teak found. It takes three calls:

  1. POST /v1/cards/:cardId/thumbnail/upload with { "mimeType": "image/png", "fileSize": 48213 } returns { uploadUrl, fileKey, method: "PUT", maxFileSize, expiresIn }. This works for an account at the free plan’s 200 cards, because no card is created.
  2. PUT the bytes to uploadUrl with the same Content-Type within expiresIn seconds.
  3. PUT /v1/cards/:cardId/thumbnail with { "fileKey": "..." } checks the image and returns the card, with linkPreviewImageUrl showing it and thumbnailOverridden: true.

Accepted images are JPEG, PNG, WebP, GIF (the grid shows its first frame), and AVIF, under 20 MB and at most 10,000 pixels on a side. SVG and HEIC are refused. The image stays through a refresh of the card’s details. DELETE /v1/cards/:cardId/thumbnail goes back to the image Teak found (or its screenshot of the page), deletes the uploaded one, and returns the card; a card already on its own image is returned unchanged.

Status Code When
400 TYPE_MISMATCH The card isn’t a link
400 UNSUPPORTED_TYPE The image isn’t one of the accepted types
400 INVALID_INPUT The image couldn’t be read, or is too many pixels
404 NOT_FOUND Another account’s card, or a fileKey that isn’t this card’s upload
409 CONFLICT The card is in Trash
413 FILE_TOO_LARGE The image is 20 MB or more
429 RATE_LIMITED More than 20 image changes in a minute; see retryAt

Archive cards

Archiving hides a card from listings and search without deleting it; it still counts toward your card limit. Send PATCH /v1/cards/:cardId with { "isArchived": true } to archive a card and { "isArchived": false } to return it. Cards include isArchived and archivedAt. List archived cards with GET /v1/cards?archived=only. GET /v1/tags counts only cards outside Trash and Archive.

Hidden cards

A card whose tags include secret or hidden (any case) is hidden: GET /v1/cards leaves it out of every list and search, including Favorites, Archive, Trash, folders, and Shuffle, unless the search names one of those tags, as q=%23secret, q=tag:hidden, or tag=secret. The word secret alone doesn’t show it. Cards include isHidden, which is read-only: change the tags to change it. GET /v1/cards/:cardId, GET /v1/cards/changes, and exports still return hidden cards. Tags Teak adds (aiTags) never hide a card.

Folders

Folders group cards by hand, and a card can be in up to 50 folders. GET /v1/folders lists your folders by name, each with id, name, color (one of gray, red, orange, amber, green, teal, blue, violet, pink, or null), kind (manual or smart), query, and cardCount, which counts cards that are not archived or in Trash. A smart folder saves a search: its query is that search and its cardCount is null; a manual folder’s query is null.

  • POST /v1/folders with { "name": "Reading", "color": "blue" } creates a folder and returns 201. Names are 1 to 300 characters and unique ignoring case; a taken name returns 409 CONFLICT, and an account can have up to 500 folders (409 LIMIT_REACHED).
  • POST /v1/folders with { "name": "Design links", "kind": "smart", "query": "tag:design type:link" } creates a smart folder. The query uses the search qualifiers, is at most 500 characters, and is stored written the canonical way (#design becomes tag:design). A folder: in it must name one of your manual folders, or the request returns 400.
  • GET /v1/folders/:folderId returns one folder with the same fields; another account’s folder, or one that doesn’t exist, returns 404.
  • PATCH /v1/folders/:folderId renames or recolors a folder; "color": null clears the color. { "query": "..." } changes a smart folder’s search.
  • DELETE /v1/folders/:folderId deletes a folder and returns 204. Its cards are kept and leave the folder shortly after.
  • POST /v1/folders/:folderId/cards with { "cardIds": [...] } adds up to 100 cards, and DELETE with the same body removes them. The response lists updatedIds, failedIds, and folderLimitIds (cards already in 50 folders). Adding a card that is already in the folder succeeds without change. A smart folder holds no cards of its own, so adding to one, or naming one in a card’s folderIds, returns 409 SMART_FOLDER.

Cards include folderIds. PATCH /v1/cards/:cardId with { "folderIds": [...] } replaces a card’s folders, and an empty list takes it out of every folder. List a folder’s cards with GET /v1/cards?folder=<folderId>; like the library, the listing leaves out archived cards and cards in Trash unless you ask for them. For a smart folder, the listing runs its search, and q narrows it: both must match, so ?folder=<id>&q=type:image on a type:link smart folder lists nothing.

Rename, merge, and delete tags

These change a tag on every card at once, in your own tags and in tags Teak added, including cards in Trash and Archive. Tags match ignoring case. Each returns 202 right away with an operationId; the change finishes in the background.

  • POST /v1/tags/rename with { "from": "Design", "to": "design-systems" } renames a tag, written exactly as you send it. A rename to a different casing of the same tag recases it. Renaming onto a tag that already exists returns 409 TAG_EXISTS: merge into it instead.
  • POST /v1/tags/merge with { "from": "ux", "into": "research" } replaces one tag with another. A card that has both keeps one, in the casing it already uses.
  • POST /v1/tags/delete with { "tag": "old" } removes a tag. The cards are kept.

Tags are 1 to 64 characters. A tag no card has returns 404 NOT_FOUND, and one change runs at a time per account: starting another while one runs returns 409 TAG_OPERATION_RUNNING.

Poll GET /v1/tags/operations/:operationId for status (running, completed, or failed), total (cards that had the tag when the change started), processed, and changed. Edits made to a card while the change runs are kept; only the tag changes. GET /v1/tags shows the new names once the change finishes.

Bulk actions

POST /v1/cards/bulk takes an operation and up to 100 items. Besides create, update, favorite ({ cardId, isFavorited }) and delete, it accepts archive, unarchive, and restore (each item { cardId }) and addTags and removeTags (each item { cardId, tags }, up to 20 tags). Tags match ignoring case, and removeTags also clears tags Teak added. Repeating an operation on a card already in that state succeeds without changing it, so retries are safe. The response lists each item’s result and a failedIds array of the cards to retry.

Delete and restore cards

DELETE /v1/cards/:cardId moves a card to Trash. Restore it with POST /v1/cards/:cardId/restore. Both return 204 with no body. List Trash with GET /v1/cards?trashed=true.

A permanent delete of a card outside Trash returns 409 NOT_IN_TRASH: move it to Trash first.

Export your cards

POST /v1/exports starts a ZIP export of your cards and their original files, the same export as Settings → Import/Export Data on the web. It returns 202 with the new job. One export can run at a time (409 while one is running), and you can start one every 7 days (429 with retryAt and Retry-After until then).

Poll GET /v1/exports/latest for job.status (pending, running, ready, failed, canceled, or expired) and progress. Once it’s ready, job.downloadUrl is a short-lived link to the ZIP. Ready exports are kept for 7 days. canStartNew and nextAvailableAt say when you can start the next one.

The ZIP holds manifest.json, cards.json, the original files under files/, bookmarks.html, and cards.csv. manifest.json reports schemaVersion: 2; each card in cards.json has aiTags, aiSummary (when there is one), metadataTitle (when there is one), isArchived, and folders (names). See Export Your Data.

Last updated on

Was this page helpful?