{"record":{"id":"1721e44526f8a6a9","repo":"Budibase/budibase","slug":"string-based-bookmark-not-supported","errorCode":null,"errorMessage":"String based bookmark not supported.","messagePattern":"String based bookmark not supported\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/pro/src/sdk/auditLogs/auditLogs.ts","lineNumber":121,"sourceCode":"    }\n    if (log.appId) {\n      enrichedLog.app =\n        workspace || deleted(log.appId, ResourceType.APP, log.fallback)\n    }\n    enriched.push(enrichedLog)\n  }\n  return enriched\n}\n\nexport async function fetch(params: AuditLogSearchParams) {\n  if (!(await isAuditLogsEnabled())) {\n    throw new Error(\"Audit logs not available - license required.\")\n  }\n\n  const filter: SearchFilters = await getSearchFilters(params)\n\n  if (typeof params.bookmark === \"string\") {\n    throw new Error(\"String based bookmark not supported.\")\n  }\n\n  const response = await auditLogs.searchSQL(filter, params.bookmark)\n  return {\n    hasNextPage: response.hasNextPage,\n    bookmark: response.bookmark,\n    data: await enrich(response.rows),\n  }\n}\n\n// like the fetch, but without pagination (uses filtered replication)\nexport function download(params: AuditLogSearchParams): {\n  promise: Promise<void>\n  stream: Readable\n} {\n  params = fillDates(params)\n  return auditLogs.dump(params)\n}","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/pro/src/sdk/auditLogs/auditLogs.ts#L103-L139","documentation":"Audit log search in Budibase Pro accepts pagination bookmarks as structured objects, not raw strings. When a caller passes a string bookmark (e.g. a CouchDB-style pagination token) into fetch, it throws this error instead of attempting an invalid search. It signals the caller is using the wrong bookmark format for this API.","triggerScenarios":"Calling fetch (auditLogs.fetch) with params.bookmark set to a string value, e.g. passing a bookmark string returned by a different search API or copied from a URL query parameter instead of the object-shaped bookmark returned by this API's previous response.","commonSituations":"Migrating code from the older string-based pagination API to the SQL-backed audit log search; hand-crafting pagination requests from the builder HTTP API where bookmark was serialized to a string; copying a bookmark from server logs.","solutions":["Use the bookmark object returned by the previous fetch response (response.bookmark) verbatim rather than a string.","If you have a string bookmark, convert/round-trip it through the API that produced it, or start a fresh search with no bookmark.","Update client code/types so bookmark is typed as the structured SearchFilters-compatible bookmark, not string.","Ensure the tenant has the audit logs license; if you fell back to a legacy string-bookmark path due to licensing, upgrade the license."],"exampleFix":"// before\nawait auditLogs.fetch({ bookmark: \"eyJpZCI6...\" })\n// after\nconst first = await auditLogs.fetch({})\nawait auditLogs.fetch({ bookmark: first.bookmark })","handlingStrategy":"validation","validationCode":"if (typeof params.bookmark === \"string\") {\n  throw new TypeError(\"bookmark must be the structured object from a previous fetch response\")\n}","typeGuard":"function isStructuredBookmark(b: unknown): b is Record<string, unknown> {\n  return typeof b === \"object\" && b !== null && !Array.isArray(b)\n}","tryCatchPattern":null,"preventionTips":["Always pass through the bookmark object returned by the previous response untouched","Never serialize/deserialize the bookmark to a plain string between calls","Type the params so bookmark cannot be a string in your client code"],"tags":["pagination","bookmark","audit-logs","license"],"backgroundTag":"invalid-pagination-bookmark","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}