Skip to main content

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 its ETag.
  • GET /boards/{board_id}/cards returns cursor-paginated cards with filters: column_id, assignee_user_id, label_id, archived (default false), completed, due_before, and due_after. Omitting completed includes 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

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

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:

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.