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.
Quickstart
Check your key and save a first card with a live request form.
API Reference
Explore the complete OpenAPI contract with samples in curl, JavaScript, TypeScript, Python, Go, and more.
MCP
Connect an AI client to Teak through its remote MCP server.
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.
dataobjectrequiredThe authenticated account.
properties
idstringrequiredYour stable Teak owner ID.
emailstringrequiredThe account’s email address.
namestringThe account’s display name, when set.
cardCountnumberrequiredCards outside Trash.
cardLimitnumber | nullrequiredThe Free plan’s card limit, or null on Pro.
planstringrequiredfree or pro.
settingsUrlstringrequiredTeak’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"
}
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.
CardTypeinferredcontent?string
Markdown or plain text body for the card. Prefer this for notes; use `url` when saving a link.
stringnotes?string | null
Free-form notes attached to the card.
string | nullsource?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`.
stringsourceTitle?string
The page's title, beside `sourceUrl`; cut to 300 characters.
stringsourceUrl?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.
stringtags?string[]
Tags to apply on create.
string[]url?string
URL to save as a link card. Teak may classify the type automatically when `cardType` is omitted.
stringA 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-KeyheaderstringSafely retries card creation and bulk operations without duplicating work.
X-Request-IdstringReturned for support and log correlation.
Retry-AfterstringSeconds 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:
qquerystringSearch 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.
similarToquerystringA 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.
modequerystringHow 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.
typequerystringCard type filter. Repeat for an OR filter.
tagquerystringFilter cards by tag.
sortquerystringOrder 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.
shuffleSeedquerynumberRequired 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.
createdAfterquerynumberOnly cards created after this timestamp.
createdBeforequerynumberOnly cards created before this timestamp.
limitquerynumberPage size.
cursorquerystringPagination cursor from a previous pageInfo.nextCursor.
favoritedquerybooleanSet to true to list favorites.
trashedquerybooleanSet to true to list cards in Trash.
archivedquerystringArchived 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.
folderquerystringA 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.
stylequerystringVisual style, such as minimal or vintage. Repeatable. See Visual filters.
huequerystringColor family, such as blue or neutral. Repeatable. Matches image, palette, and video cards; see Visual filters.
hexquerystringExact color, such as %23112233. Repeatable.
includequerystringExtra 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/tagslists 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 astag. 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;isCompleteisfalsewhen 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:
codestringrequiredA stable, machine-readable error code such as INVALID_INPUT or RATE_LIMITED.
errorstringrequiredA human-readable message.
retryAtnumberFor 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.
Replace a link’s image
A link card’s image can be one you choose instead of the one Teak found. It takes three calls:
POST /v1/cards/:cardId/thumbnail/uploadwith{ "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.PUTthe bytes touploadUrlwith the sameContent-TypewithinexpiresInseconds.PUT /v1/cards/:cardId/thumbnailwith{ "fileKey": "..." }checks the image and returns the card, withlinkPreviewImageUrlshowing it andthumbnailOverridden: 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/folderswith{ "name": "Reading", "color": "blue" }creates a folder and returns201. Names are 1 to 300 characters and unique ignoring case; a taken name returns409 CONFLICT, and an account can have up to 500 folders (409 LIMIT_REACHED).POST /v1/folderswith{ "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 (#designbecomestag:design). Afolder:in it must name one of your manual folders, or the request returns400.GET /v1/folders/:folderIdreturns one folder with the same fields; another account’s folder, or one that doesn’t exist, returns404.PATCH /v1/folders/:folderIdrenames or recolors a folder;"color": nullclears the color.{ "query": "..." }changes a smart folder’s search.DELETE /v1/folders/:folderIddeletes a folder and returns204. Its cards are kept and leave the folder shortly after.POST /v1/folders/:folderId/cardswith{ "cardIds": [...] }adds up to 100 cards, andDELETEwith the same body removes them. The response listsupdatedIds,failedIds, andfolderLimitIds(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’sfolderIds, returns409 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/renamewith{ "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 returns409 TAG_EXISTS: merge into it instead.POST /v1/tags/mergewith{ "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/deletewith{ "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.