ioZen Docs

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:

Scopescontactrecord
read:casesnullnull
read:cases, read:contactsfillednull
read:cases, read:recordsnullfilled
read:cases, read:contacts, read:recordsfilledfilled

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)

On this page