Card

One card: read it, create it, change it, duplicate it, delete it.

GET /api/data/card

The card, its attachments, its reminders and its labels — each of the latter an id and a name — and assignees, everyone on it as id, name, image and type. assignee and assigneeName are the first of them, for integrations that know only one person per card; repeatEvery is how it repeats, or null.

Parameters

NameTypeRequiredDescription
cardIDintegeryesThe card's id.
curl -X GET "https://boards.example.com/api/data/card?cardID=501" \
  -H "X-API-Key: $LOKALBOARDS_KEY"
const response = await fetch("https://boards.example.com/api/data/card?cardID=501", {
  headers: {
    "X-API-Key": apiKey,
  },
});

if (!response.ok) throw new Error(await response.text());

const data = await response.json();
<script setup>
const config = useRuntimeConfig();

const { data, error } = await useAsyncData("card", () =>
  $fetch("https://boards.example.com/api/data/card?cardID=501", {
    headers: { "X-API-Key": config.lokalBoardsKey },
  }),
);
</script>

<template>
  <pre v-if="data">{{ data }}</pre>
  <p v-else-if="error">{{ error.message }}</p>
</template>
import { useEffect, useState } from "react";

export function Example({ apiKey }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    fetch("https://boards.example.com/api/data/card?cardID=501", {
      headers: { "X-API-Key": apiKey },
      signal: controller.signal,
    })
      .then((response) => response.json())
      .then(setData)
      .catch((error) => {
        if (error.name !== "AbortError") console.error(error);
      });

    return () => controller.abort();
  }, [apiKey]);

  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
<?php

$ch = curl_init('https://boards.example.com/api/data/card?cardID=501');

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-Key: ' . $apiKey,
]);

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

POST /api/data/card

Creates a card at the end of an area.

Parameters

NameTypeRequiredDescription
areaIdintegeryesThe area to create it in.
namestringyesThe card's title.
contentstringnoThe description, as Markdown.
statusintegerno0 open, 1 done. Defaults to 0.
curl -X POST "https://boards.example.com/api/data/card" \
  -H "X-API-Key: $LOKALBOARDS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "areaId": 10,
    "name": "Redesign the logo",
    "content": "Refresh the brand mark for the 2.0 launch.\n\n- [ ] Collect references\n- [ ] First round of concepts",
    "status": 0
  }'
const response = await fetch("https://boards.example.com/api/data/card", {
  method: "POST",
  headers: {
    "X-API-Key": apiKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    areaId: 10,
    name: "Redesign the logo",
    content: "Refresh the brand mark for the 2.0 launch.\n\n- [ ] Collect references\n- [ ] First round of concepts",
    status: 0,
  }),
});

if (!response.ok) throw new Error(await response.text());

const data = await response.json();
<script setup>
const config = useRuntimeConfig();
const pending = ref(false);

async function createCard() {
  pending.value = true;
  try {
    return await $fetch("https://boards.example.com/api/data/card", {
      method: "POST",
      headers: { "X-API-Key": config.lokalBoardsKey },
      body: {
        areaId: 10,
        name: "Redesign the logo",
        content: "Refresh the brand mark for the 2.0 launch.\n\n- [ ] Collect references\n- [ ] First round of concepts",
        status: 0,
      },
    });
  } finally {
    pending.value = false;
  }
}
</script>

<template>
  <button :disabled="pending" @click="createCard()">Create card</button>
</template>
import { useState } from "react";

export function Example({ apiKey }) {
  const [pending, setPending] = useState(false);

  async function createCard() {
    setPending(true);

    const response = await fetch("https://boards.example.com/api/data/card", {
      method: "POST",
      headers: {
        "X-API-Key": apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        areaId: 10,
        name: "Redesign the logo",
        content: "Refresh the brand mark for the 2.0 launch.\n\n- [ ] Collect references\n- [ ] First round of concepts",
        status: 0,
      }),
    });

    setPending(false);
    return response.json();
  }

  return (
    <button disabled={pending} onClick={createCard}>
      Create card
    </button>
  );
}
<?php

$ch = curl_init('https://boards.example.com/api/data/card');

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-Key: ' . $apiKey,
    'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'areaId' => 10,
    'name' => "Redesign the logo",
    'content' => "Refresh the brand mark for the 2.0 launch.\n\n- [ ] Collect references\n- [ ] First round of concepts",
    'status' => 0,
]));

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

PUT /api/data/card

Updates a card. This is a replacement, not a patch: name, content, status and dueDate are all written from what you send, so read the card first and send it back with your change applied. Ticking one box means sending the whole content with that one - [ ] turned into - [x].

Parameters

NameTypeRequiredDescription
cardIDintegeryesThe card's id.
namestringyesThe card's title.
contentstringnoThe description, as Markdown.
statusintegerno0 open, 1 done.
dueDatestring | nullnoAn ISO timestamp, or null to clear it.
assigneesstringnoEveryone who should be on the card, by user id — a card can be on several people. Sending the array replaces them; [] takes everybody off; leaving it out leaves them alone. Only the board's owner and the people invited to it can be put on a card — anyone else is ignored. People put on by somebody else get a notification.
assigneestring | nullnoThe older single-person form: one user id puts exactly that person on the card, null takes everybody off. Ignored when assignees is sent.
remindersintegernoMinutes before the due date to send a reminder, e.g. [60, 1440]. Sending the array replaces the set; a changed due date makes them all fire again.
labelIdsintegernoThe labels this card wears, by id. Sending the array replaces them; leaving it out leaves them alone. Ids come from GET /api/data/labels, and only labels belonging to this card's own board are accepted — the rest are ignored. A label no card wears any more is removed from the board.
repeatEverystring | nullnoHow the card repeats once it is done: day, week, twoWeeks, month or year; null stops it. Leaving it out leaves it alone. Needs a due date — clearing dueDate stops it too. Anything else is refused with 400.
filesobjectnoAttachments to add, each { filename, filetype, filesize, filedata } with filedata base64.

Setting status to 1 on a repeating card puts the next one on the board, and the answer carries it as next (otherwise next is null): the same card with its checklist unticked, open, due at the next date in the series after today, at the bottom of the area the series was set up in. The card you sent keeps status: 1 and no longer repeats — the series has moved on to next. See Repeating cards for the rules.

curl -X PUT "https://boards.example.com/api/data/card" \
  -H "X-API-Key: $LOKALBOARDS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cardID": 501,
    "name": "Redesign the logo",
    "content": "Refresh the brand mark for the 2.0 launch.\n\n- [x] Collect references\n- [ ] First round of concepts",
    "status": 0,
    "dueDate": "2026-08-21T19:31:00.000Z",
    "assignees": ["8f3c1e2a-5b7d-4a91-9c8e-2d6f0b4a7e13"],
    "reminders": [
      60,
      1440
    ]
  }'
const response = await fetch("https://boards.example.com/api/data/card", {
  method: "PUT",
  headers: {
    "X-API-Key": apiKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    cardID: 501,
    name: "Redesign the logo",
    content: "Refresh the brand mark for the 2.0 launch.\n\n- [x] Collect references\n- [ ] First round of concepts",
    status: 0,
    dueDate: "2026-08-21T19:31:00.000Z",
    assignees: ["8f3c1e2a-5b7d-4a91-9c8e-2d6f0b4a7e13"],
    reminders: [60, 1440],
  }),
});

if (!response.ok) throw new Error(await response.text());

const data = await response.json();
<script setup>
const config = useRuntimeConfig();
const pending = ref(false);

async function saveCard() {
  pending.value = true;
  try {
    return await $fetch("https://boards.example.com/api/data/card", {
      method: "PUT",
      headers: { "X-API-Key": config.lokalBoardsKey },
      body: {
        cardID: 501,
        name: "Redesign the logo",
        content: "Refresh the brand mark for the 2.0 launch.\n\n- [x] Collect references\n- [ ] First round of concepts",
        status: 0,
        dueDate: "2026-08-21T19:31:00.000Z",
        assignees: ["8f3c1e2a-5b7d-4a91-9c8e-2d6f0b4a7e13"],
        reminders: [60, 1440],
      },
    });
  } finally {
    pending.value = false;
  }
}
</script>

<template>
  <button :disabled="pending" @click="saveCard()">Save card</button>
</template>
import { useState } from "react";

export function Example({ apiKey }) {
  const [pending, setPending] = useState(false);

  async function saveCard() {
    setPending(true);

    const response = await fetch("https://boards.example.com/api/data/card", {
      method: "PUT",
      headers: {
        "X-API-Key": apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        cardID: 501,
        name: "Redesign the logo",
        content: "Refresh the brand mark for the 2.0 launch.\n\n- [x] Collect references\n- [ ] First round of concepts",
        status: 0,
        dueDate: "2026-08-21T19:31:00.000Z",
        assignees: ["8f3c1e2a-5b7d-4a91-9c8e-2d6f0b4a7e13"],
        reminders: [60, 1440],
      }),
    });

    setPending(false);
    return response.json();
  }

  return (
    <button disabled={pending} onClick={saveCard}>
      Save card
    </button>
  );
}
<?php

$ch = curl_init('https://boards.example.com/api/data/card');

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-Key: ' . $apiKey,
    'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'cardID' => 501,
    'name' => "Redesign the logo",
    'content' => "Refresh the brand mark for the 2.0 launch.\n\n- [x] Collect references\n- [ ] First round of concepts",
    'status' => 0,
    'dueDate' => "2026-08-21T19:31:00.000Z",
    'assignees' => ["8f3c1e2a-5b7d-4a91-9c8e-2d6f0b4a7e13"],
    'reminders' => [60, 1440],
]));

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

DELETE /api/data/card

Archives a card. It leaves the board keeping its comments, attachments, labels and history, and can be restored from the board's archive.

This archives rather than deletes. Since v0.36.0 the row stays, everything hanging off it stays, and it disappears from every list — the board, the dashboard, search and the reminders — until it is restored. Pass permanent for the old, irreversible behaviour.

Parameters

NameTypeRequiredDescription
cardIDintegeryesThe card's id.
permanentbooleannotrue deletes the card outright, with its comments, its attachments and the files behind them. Not reversible.
curl -X DELETE "https://boards.example.com/api/data/card" \
  -H "X-API-Key: $LOKALBOARDS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cardID": 501}'
const response = await fetch("https://boards.example.com/api/data/card", {
  method: "DELETE",
  headers: {
    "X-API-Key": apiKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    cardID: 501,
  }),
});

if (!response.ok) throw new Error(await response.text());

const data = await response.json();
<script setup>
const config = useRuntimeConfig();
const pending = ref(false);

async function deleteCard() {
  pending.value = true;
  try {
    return await $fetch("https://boards.example.com/api/data/card", {
      method: "DELETE",
      headers: { "X-API-Key": config.lokalBoardsKey },
      body: {
        cardID: 501,
      },
    });
  } finally {
    pending.value = false;
  }
}
</script>

<template>
  <button :disabled="pending" @click="deleteCard()">Delete card</button>
</template>
import { useState } from "react";

export function Example({ apiKey }) {
  const [pending, setPending] = useState(false);

  async function deleteCard() {
    setPending(true);

    const response = await fetch("https://boards.example.com/api/data/card", {
      method: "DELETE",
      headers: {
        "X-API-Key": apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        cardID: 501,
      }),
    });

    setPending(false);
    return response.json();
  }

  return (
    <button disabled={pending} onClick={deleteCard}>
      Delete card
    </button>
  );
}
<?php

$ch = curl_init('https://boards.example.com/api/data/card');

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-Key: ' . $apiKey,
    'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'cardID' => 501,
]));

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

POST /api/data/card-duplicate

Copies a card into the same area, directly below the original.

The copy takes the title, the description with whatever checklist it holds, the due date and its reminders and how it repeats, the people on it, the done state and the attachments. Comments are not copied. A stored attachment's file is copied on disk under a new name, so the two cards own two files and deleting one card's attachment never touches the other's.

Returns the new card in the shape GET /api/data/cards returns, and the number of attachments that were copied.

Parameters

NameTypeRequiredDescription
cardIDintegeryesThe id of the card to copy.
curl -X POST "https://boards.example.com/api/data/card-duplicate" \
  -H "X-API-Key: $LOKALBOARDS_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cardID": 501}'
const response = await fetch("https://boards.example.com/api/data/card-duplicate", {
  method: "POST",
  headers: {
    "X-API-Key": apiKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    cardID: 501,
  }),
});

if (!response.ok) throw new Error(await response.text());

const { card, attachments } = await response.json();
<script setup>
const config = useRuntimeConfig();
const pending = ref(false);

async function duplicateCard() {
  pending.value = true;
  try {
    return await $fetch("https://boards.example.com/api/data/card-duplicate", {
      method: "POST",
      headers: { "X-API-Key": config.lokalBoardsKey },
      body: {
        cardID: 501,
      },
    });
  } finally {
    pending.value = false;
  }
}
</script>

<template>
  <button :disabled="pending" @click="duplicateCard()">Duplicate card</button>
</template>
import { useState } from "react";

export function Example({ apiKey }) {
  const [pending, setPending] = useState(false);

  async function duplicateCard() {
    setPending(true);

    const response = await fetch("https://boards.example.com/api/data/card-duplicate", {
      method: "POST",
      headers: {
        "X-API-Key": apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        cardID: 501,
      }),
    });

    setPending(false);
    return response.json();
  }

  return (
    <button disabled={pending} onClick={duplicateCard}>
      Duplicate card
    </button>
  );
}
<?php

$ch = curl_init('https://boards.example.com/api/data/card-duplicate');

curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-Key: ' . $apiKey,
    'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'cardID' => 501,
]));

$response = json_decode(curl_exec($ch), true);
curl_close($ch);