Export a Process Board
Page cases from a Process Board and write them to a CSV file.
The API returns JSON pages. This guide lists your Process Boards, walks every case, and writes a CSV file. The key needs the read:cases scope. Test cases stay out unless you set include_test to true.
List Process Boards
curl "https://app.iozen.ai/api/v1/process-boards" \
-H "Authorization: Bearer $IOZEN_API_KEY"Each board includes its id, name, currency, and phases. Use the board id in the next call.
One page of cases looks like this:
curl "https://app.iozen.ai/api/v1/process-boards/BOARD_ID/cards?limit=100" \
-H "Authorization: Bearer $IOZEN_API_KEY"Page every case
Keep requesting with cursor until has_more is false. next_cursor is the cursor for the following page.
async function fetchAllCases(apiKey, boardId) {
const cases = []
let cursor = undefined
let hasMore = true
while (hasMore) {
const url = new URL(`https://app.iozen.ai/api/v1/process-boards/${boardId}/cards`)
url.searchParams.set('limit', '100')
if (cursor) url.searchParams.set('cursor', cursor)
const response = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
})
const body = await response.json()
if (!body.success) throw new Error(body.error.message)
cases.push(...body.data.cards)
hasMore = body.data.pagination.has_more
cursor = body.data.pagination.next_cursor
}
return cases
}Tags and linked records
Each case includes description, comment_count, tags, contacts, and records.
tags is { id, name }. Use a tag as the close reason: keep cases in a closed phase whose tag name is Won.
const won = cases.filter(
(card) => card.phase.name === 'Closed' && card.tags.some((tag) => tag.name === 'Won'),
)A linked record carries its values when the key has read:records. Match the Record Type with records[].record_type.key, then read records[].record.values.<field_key>.
function voucherNumber(card) {
const link = card.records.find((item) => item.record_type.key === 'voucher')
return link?.record?.values.voucher_number ?? null
}id, is_primary, and record_type ({ id, key, name }) are always present. contact and record follow the key:
| Scopes | contact | record |
|---|---|---|
read:cases | null | null |
read:cases, read:contacts | filled | null |
read:cases, read:records | null | filled |
read:cases, read:contacts, read:records | filled | filled |
Write a CSV file
Columns: id, title, description, phase, tags, comment_count, amount, currency, assignees, created_at. amount is a decimal string such as 1500.00, or empty when the case has no amount. phase is the current phase name. tags and assignees join names with a semicolon. description is empty when the case has none.
function csvCell(value) {
const text = value == null ? '' : String(value)
if (/[",\n]/.test(text)) return `"${text.replaceAll('"', '""')}"`
return text
}
function casesToCsv(cases) {
const header = ['id', 'title', 'description', 'phase', 'tags', 'comment_count', 'amount', 'currency', 'assignees', 'created_at']
const lines = cases.map((card) =>
[
card.id,
card.title,
card.description ?? '',
card.phase.name,
card.tags.map((tag) => tag.name).join('; '),
card.comment_count,
card.amount ?? '',
card.currency,
card.assignees.map((assignee) => assignee.name ?? assignee.id).join('; '),
card.created_at,
]
.map(csvCell)
.join(','),
)
return [header.join(','), ...lines].join('\n')
}
const cases = await fetchAllCases(apiKey, boardId)
const csv = casesToCsv(cases)