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 often it comes back, 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 often the card comes back: day, week, twoWeeks, month or year; null stops it. Leaving it out leaves it alone. No due date is needed, and clearing dueDate does not stop it. Anything else is refused with 400.
filesobjectnoAttachments to add, each { filename, filetype, filesize, filedata } with filedata base64.

A repeating card is put on the board again by the server itself, each time its rhythm comes round — marking one done does not make the next one, and this endpoint never answers with one. 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);