Cards and completion
Cards are the unit of work on a board. A card belongs to one board and one
column, carries its own content fields (title, description, due_date,
priority), and embeds a summary of its collaboration state: assignee IDs,
label IDs, subtask counts, and comment count.
The full representation is documented in the API reference.
Reading cards
GET /cards/{card_id}returns one enriched card with itsETag.GET /boards/{board_id}/cardsreturns cursor-paginated cards with filters:column_id,assignee_user_id,label_id,archived(defaultfalse),completed,due_before, anddue_after. Omittingcompletedincludes both completed and incomplete cards.
Example — list the incomplete cards assigned to a user that are due before a date:
cURL:
BOARD_ID="<board-id>"
ASSIGNEE_ID="<user-id>"
curl -sS -G "$KANBAN_API_BASE_URL/boards/$BOARD_ID/cards" \
-H "Authorization: Bearer $KANBAN_API_TOKEN" \
--data-urlencode "assignee_user_id=$ASSIGNEE_ID" \
--data-urlencode "completed=false" \
--data-urlencode "due_before=2026-08-01T00:00:00Z"
JavaScript:
const baseUrl =
process.env.KANBAN_API_BASE_URL ?? "https://app.filetag.ai/api/kanban/v1";
const token = process.env.KANBAN_API_TOKEN;
const boardId = process.env.KANBAN_BOARD_ID;
const assigneeId = process.env.KANBAN_ASSIGNEE_ID;
async function main() {
const query = new URLSearchParams({
assignee_user_id: assigneeId,
completed: "false",
due_before: "2026-08-01T00:00:00Z",
});
const response = await fetch(
`${baseUrl}/boards/${boardId}/cards?${query}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const payload = await response.json();
if (!response.ok) {
throw new Error(
`${payload.error.code}: ${payload.error.message} (request ${payload.error.request_id})`
);
}
for (const card of payload.data) {
console.log(card.title, "due", card.due_date);
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
Python:
import json
import os
import urllib.parse
import urllib.request
BASE_URL = os.environ.get(
"KANBAN_API_BASE_URL", "https://app.filetag.ai/api/kanban/v1"
)
TOKEN = os.environ["KANBAN_API_TOKEN"]
BOARD_ID = os.environ["KANBAN_BOARD_ID"]
ASSIGNEE_ID = os.environ["KANBAN_ASSIGNEE_ID"]
query = urllib.parse.urlencode(
{
"assignee_user_id": ASSIGNEE_ID,
"completed": "false",
"due_before": "2026-08-01T00:00:00Z",
}
)
request = urllib.request.Request(
f"{BASE_URL}/boards/{BOARD_ID}/cards?{query}",
headers={"Authorization": f"Bearer {TOKEN}"},
)
with urllib.request.urlopen(request) as response:
payload = json.load(response)
for card in payload["data"]:
print(card["title"], "due", card["due_date"])
Creating and updating
POST /columns/{column_id}/cardscreates a card at the start of an active column. Onlytitleis required;descriptionis optional. Requires anIdempotency-Key.PATCH /cards/{card_id}updates any subset oftitle,description,due_date,priority, andcompleted(at least one field), guarded byIf-Match.nullclears the nullable fields (description,due_date,priority).
priority is a closed enum: super_urgent, urgent, important,
nice_to_have, or null.
Completion is a boolean write, a timestamp read
You write completion as a boolean and read it as a timestamp:
PATCH { "completed": true } sets completed_at to the completion time,
PATCH { "completed": false } resets it to null. Setting the desired state
(rather than toggling) makes retries deterministic — repeating
{ "completed": true } cannot un-complete a card.
cURL:
CARD_ID="<card-id>"
curl -sS -i -X PATCH "$KANBAN_API_BASE_URL/cards/$CARD_ID" \
-H "Authorization: Bearer $KANBAN_API_TOKEN" \
-H "Content-Type: application/json" \
-H 'If-Match: "3"' \
-d '{"completed": true}'
JavaScript:
const baseUrl =
process.env.KANBAN_API_BASE_URL ?? "https://app.filetag.ai/api/kanban/v1";
const token = process.env.KANBAN_API_TOKEN;
const cardId = process.env.KANBAN_CARD_ID;
async function main() {
const read = await fetch(`${baseUrl}/cards/${cardId}`, {
headers: { Authorization: `Bearer ${token}` },
});
const etag = read.headers.get("etag");
const response = await fetch(`${baseUrl}/cards/${cardId}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
"If-Match": etag,
},
body: JSON.stringify({ completed: true }),
});
const payload = await response.json();
if (!response.ok) {
throw new Error(
`${payload.error.code}: ${payload.error.message} (request ${payload.error.request_id})`
);
}
console.log("completed at", payload.data.completed_at);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
Python:
import json
import os
import urllib.request
BASE_URL = os.environ.get(
"KANBAN_API_BASE_URL", "https://app.filetag.ai/api/kanban/v1"
)
TOKEN = os.environ["KANBAN_API_TOKEN"]
CARD_ID = os.environ["KANBAN_CARD_ID"]
read = urllib.request.Request(
f"{BASE_URL}/cards/{CARD_ID}",
headers={"Authorization": f"Bearer {TOKEN}"},
)
with urllib.request.urlopen(read) as response:
etag = response.headers.get("ETag")
patch = urllib.request.Request(
f"{BASE_URL}/cards/{CARD_ID}",
data=json.dumps({"completed": True}).encode("utf-8"),
method="PATCH",
headers={
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json",
"If-Match": etag,
},
)
with urllib.request.urlopen(patch) as response:
payload = json.load(response)
print("completed at", payload["data"]["completed_at"])
Moving, archiving, restoring, deleting
POST /cards/{card_id}/movemoves the card to a column/position within its board; see Move cards safely.POST /cards/{card_id}/archivearchives the card;POST /cards/{card_id}/restorerestores it into its original column, or the first active column when the original was archived. Both are bodyless POSTs (any body, even{}, is400 invalid_request) and require bothIdempotency-KeyandIf-Match.DELETE /cards/{card_id}permanently deletes the card and its subtasks, comments, and relationships. It requires thekanban:adminscope andIf-Match, and cannot be undone. Deleting a card never deletes attached files — see File attachments.
Archived cards keep their content and can still be read; active-card
listings exclude them by default (archived=false).
The card cover
A card can have a cover image — an already-uploaded workspace image file displayed on the card. The cover is a separate subresource, not a field of the card representation:
GET /cards/{card_id}/coverreturns the cover summary, or204when there is none visible to you.PUT /cards/{card_id}/cover/{file_id}sets a clean, visible image file as the cover.DELETE /cards/{card_id}/coverclears it (204even when already absent).
Cover changes do not increment the card version, and cover reads are not
covered by the card's ETag. The same file-readiness rules as attachments
apply — see File attachments.