AI agents: Logbook can be used programmatically. Start at https://logbook.zone/llms.txt. Remote MCP server: https://logbook.zone/mcp. Read-only REST API: https://logbook.zone/api/v1. Local MCP server: npx -y @logbookprotocol/mcp.

Logbook Beta on Sui Testnet. Learn more →
Documentation

Learn Logbook

Everything you need to create blockchain-verified surveys, understand the technology, and get the most out of Logbook.

DocsAPI & SDK
22 min read

API & SDK

Everything the Logbook web app does can be done without it. The web app is just one client of the protocol.

#Four ways to integrate

You want toUse
Let an AI agent (claude.ai, Claude Code, Cursor, your own bot) run polls and voteMCP: remote connector or local server; or draft links with no integration
Create campaigns, respond, manage address lists, read private results from your own codeTypeScript SDK
Create or read groups from your own codeTypeScript SDK, as building blocks (see Groups in the SDK)
Show public campaigns and results in a dashboard, a bot or a non-JavaScript appREST API (read-only)
Full control, any language with a Sui SDKThe Move package directly
Why is the REST API read-only? Two things cannot be done by a server on your behalf without breaking the trust model: signing (a response must come from the participant's own address, one response per address) and encryption (private campaigns are encrypted before they leave the participant's device, so that not even Logbook can read them). Both happen inside the SDK, on your side.

#TypeScript SDK

bash
npm install @logbookprotocol/sdk @mysten/sui @mysten/seal
ts
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { LogbookClient } from '@logbookprotocol/sdk';

const logbook = new LogbookClient({ signer: Ed25519Keypair.fromSecretKey(process.env.SUI_KEY) });

const { campaignId, url, password } = await logbook.createCampaign({ title: 'Board election', questions: [{ text: 'Candidate', type: 'single_choice', options: ['Ana', 'Boris', 'Chen'] }], endTime: Date.now() + 3 * 24 * 3600 * 1000, access: 'allowlist', // 'open' | 'link' | 'allowlist' visibility: 'after_end', // 'everyone' | 'access' | 'voters' | 'creator' | 'after_end' allowlist: ['0x...'], });

await voter.submitResponse(campaignId, [0]); const { results, complete } = await logbook.getResults(campaignId);

The SDK covers reading (getCampaign, openCampaign, listCampaigns, getResults, getDocuments, getReport), writing (createCampaign, updateCampaign, deleteCampaign, updateAllowlist, submitResponse) and transaction preparation for apps that sign elsewhere (prepareCreateCampaign, prepareSubmitResponse). It works in Node 22.12+ and in browsers, with a Sui keypair or with your own signing callbacks (wallets, KMS); @mysten/sui and @mysten/seal are peer dependencies.

Answers are validated against the contract's rules before anything is sent. If the creator edits the campaign or removes an address from its list between reading and sending, the contract refuses the response, and submitResponse prepares it again and resends it once. Results come back decrypted for whatever your account is allowed to read; complete says whether every response was counted, and locked, failed and invalid responses are counted separately. The creator of a password campaign gets its password back at any time with getCampaignPassword.

#Groups in the SDK

From SDK 0.7.0 LogbookClient has methods for groups, built on the same code the site runs:

MethodWhat it does
listGroups({ limit, cursor, search })the public groups, a page at a time
getGroup(id, { invitationLink?, personalLink? })one group: its texts (a private group's for its owner, admins and current members), how people join, who counts once, what the account is to it and which ways in are open to it now
myGroups()the groups of the account, the campaigns that wait for its response, the groups it was appointed to or offered
listGroupMembers(id), listGroupCampaigns(id)the members now (with what their cards show to members), and the campaigns for the members of a group
joinGroup(id, { invitationLink?, personalLink?, card? })join by the Join button, an invitation link, or the personal link of a seat on a list
applyToGroup(id, { text?, card? }), withdrawApplication(id), leaveGroup(id)apply (the text and the card are encrypted for the admins), take the application back, leave
createGroup({ name, private?, ways, cardFields?, cardRequired? })a group of your own, public or private, with how people join and its member card
createInvitation(id, { places, expiresAt })an invitation link (a secret, returned once)
listApplications(id), reviewApplications(id, { approve?, reject? }), removeGroupMembers(id, members)for the owner and admins
createCampaign({ ..., groups, waitingPeriodMs? })a campaign for the members of groups you run
A call the contract would refuse (already a member, joining closed, a wait after a rejection, not an admin) fails with NOT_ALLOWED before anything is signed. A group that needs a check the client cannot get fails with NOT_AVAILABLE: a World ID check (not built), or an email check where the site has none switched on, or where the client holds no Google sign-in to show the check server. A client that does hold one passes googleIdToken (a function that returns Google's id_token of the session while it is fresh; it is sent to checkServerUrl only, by default https://logbook.zone, never to appUrl): joinGroup then asks the check server once and the join carries its statement. An agent's session keeps no Google token, so such a group is joined on the site; after that the account has its mailbox confirmed in the community, and joinGroup opens the other groups of that community without asking again. Who pays follows the rules of the site (see Who Pays in Groups). A group is changed the way it was created: one created with sponsored gas takes its owner's and admins' calls only through the sponsor (SPONSOR_REQUIRED otherwise), one created on its creator's own gas only on own gas. What the Logbook treasury paid for, only the treasury frees: an application it paid for is approved, and withdrawn, through the sponsor in any group, so reviewApplications sends a selection of both kinds in two transactions, and a client without sponsor gets SPONSOR_REQUIRED before anything is signed. The one application that cannot be approved at all (see Who Pays in Groups) is named in skipped and not sent.

Not on the client: transferring a group, appointing and removing admins, the tree of groups, a list of seats, the settings of a group, archiving and deleting. They are done on the site, or with the building blocks below.

The building blocks are exported too:

  • GroupTx: builds every call of the module groups into one transaction. createGroup(input, configure) begins a group, sets it up and shares it; there are builders for invitations, joining with a card, applications and their approval, admins and ownership, the tree of groups, lists, and audience(...) with createGroupCampaign for a campaign for the members of groups
  • Readers: fetchGroup, fetchMemberShards, fetchMembership, fetchMembersPage, fetchRequestsPage, fetchInvitations, fetchScopeGroups, fetchGroupCampaigns, fetchMyGroups, fetchGroupCatalogue and others
  • Lists as Merkle trees (buildCommunityRoster, buildFilterList, verifyRosterPath), invitation links and personal links (generateLinkKey, signInvite, signRosterEntry)
  • The card form and cards, and sealing and opening with Seal (sealGroupMetadata, openGroupMetadata, sealCard, openCardParts, sealRequestText, sealAdminFile)
  • GROUP_ERROR and messages for the abort codes of groups in explainTransactionError
These are lower-level than the client: you compose the transaction, sign it and send it yourself (signAndExecute sends it with the client's signer). The type declarations of the package (dist/index.d.ts) name them all.

A campaign for the members of groups (access group) is read by the client, the REST API and the MCP tools: getCampaign, GET /api/v1/campaigns/{id} and logbook_get_campaign return audience (the groups, when the participants were frozen, the waiting period for new members, who counts once). From SDK 0.7.0 the client also answers it and decrypts it, for an account that is one of its participants (a member of one of its groups when it was created): submitResponse sends the response through the account's own record in the group, and openCampaign, getResults and getReport open what its visibility lets that account read; its creator reads it too. Any other account gets NO_ACCESS with the reason, before anything is signed or sent, and getParticipation says in advance whether the account is a participant. The REST API has no account and returns only what is public. deleteCampaign of such a campaign uses the deletion of the groups contract; it cannot be edited.

The full reference is in the SDK README.

Status
The SDK works on Sui Testnet, like the rest of Logbook during beta, and follows the deployed contract: after a new deployment, update to the latest version on npm. The package published on 7 October 2026 needs SDK 0.6.0 and MCP 0.7.0 or later: earlier versions keep talking to the retired package and see none of the new campaigns and groups. The group methods of the client and the group tools come with SDK 0.7.0 and MCP 0.8.0. The on-chain format may still change before mainnet.

#AI agents: MCP

The Model Context Protocol gives an AI agent the Logbook protocol as tools: list and read campaigns, read the documents a campaign is about, create a poll, manage an address list, respond, read results, and work with groups (below). There are two ways to connect an agent, and they differ in one thing: where the key that signs for you lives.

WhatRemote connectorLocal server
Addresshttps://logbook.zone/mcpruns on your computer
Works inany chat with connectors: claude.ai, the Claude apps, ChatGPT, bots run by othersClaude Code, Claude Desktop, Cursor, your own agent
Setupadd the connector, sign in with Googleadd npx -y @logbookprotocol/mcp to the client's config
Accountyour Google (zkLogin) account, or read-onlyyour Google account, the agent's own Sui key, or read-only
Where the session key ison Logbook servers, encrypted, for the session onlyon your computer; Logbook never sees it
Private datadecrypted on Logbook's edge servers for the connected account, within limits per call: 60 encrypted responses, 6 names of private groups, 30 member cards, 10 applicationsdecrypted on your computer, without those limits
Ending a sessionConnected apps on your account page (Settings), disconnecting the app, or asking it to sign out; revoked immediately. It also ends when its proof expires, after 7 days at mostsign the agent out; the key is deleted
Remote connector. Add https://logbook.zone/mcp as a custom connector (in claude.ai: Settings → Connectors → Add custom connector). Logbook shows a consent page; choose Continue with Google to let the app act as your account for up to 7 days (the session ends when its zkLogin proof expires), or Continue without an account for read-only access. Standard OAuth 2.1 with dynamic client registration and PKCE, so other MCP clients work the same way. The trade-off is stated on the consent page: to sign for you without anything installed on your computer, Logbook keeps a temporary signing key for the session on its servers, encrypted. Anyone with that key could act as you during the session, so Logbook itself has to be trusted for its duration. For the same reason, private campaigns and answers your account may read are decrypted on Logbook's edge servers when the app asks for them; the SDK and the local server decrypt on your own machine. One results call decrypts at most 60 encrypted responses there: the rest count as failed and the tally is marked incomplete, so read larger private campaigns on the site, with the SDK or with the local server. In exchange the session can be revoked at any moment: under Connected apps on your account page (Settings), by disconnecting the app, or with the logbook_logout tool.

Local server. Nothing custodial: the agent's own Sui key stays in the process on your machine, and a Google session is a time-limited proof bound to a temporary key that never leaves your computer (2 days by default, 7 at most). Logbook cannot act as you and cannot revoke the session either; signing the agent out deletes the key.

The package is on npm and runs with npx (Node 22 or newer). In Claude Code, add it for your user, so its configuration, and a key if you give one, stays out of your repositories:

bash
claude mcp add logbook --scope user -- npx -y @logbookprotocol/mcp

Other clients take an entry like the one below. A project's .mcp.json is meant to be committed, so never write a key into it: reference an environment variable, which Claude Code fills in when it starts the server.

json
{
  "mcpServers": {
    "logbook": {
      "command": "npx",
      "args": ["-y", "@logbookprotocol/mcp"],
      "env": { "LOGBOOK_PRIVATE_KEY": "${LOGBOOK_PRIVATE_KEY}", "LOGBOOK_SPONSOR": "1" }
    }
  }
}

Without LOGBOOK_PRIVATE_KEY the server is read-only until you sign it in with your Google account (npx -y @logbookprotocol/mcp login, or ask the agent to sign in).

Groups. From MCP 0.8.0 both servers have tools for groups:

ToolWhat it does
logbook_list_groups, logbook_get_group, logbook_my_groups, logbook_list_membersread: the public groups; one group with how people join, who counts once and what the account may do next; the groups of the account and what waits for it; the members of a group
logbook_join_group, logbook_apply_to_group, logbook_withdraw_application, logbook_leave_groupthe account's own membership: join by the Join button, an invitation link or the personal link of a seat; apply with a text and a card; take an application back; leave
logbook_create_group, logbook_create_invitation, logbook_list_applications, logbook_review_application, logbook_remove_memberfor the owner and admins: create a group, make an invitation link, read and decide applications, remove a member
Campaigns for the members of groups go through the campaign tools: logbook_create_campaign takes groups (the account must be their owner or admin), logbook_list_campaigns lists those of one group (groupId) and those that wait for the account's response (waiting), and logbook_get_campaign, logbook_submit_response and logbook_get_results read, answer and tally one for an account that is one of its participants.

The agent is told the rules that matter here. Who is a member of a group, and who applied, is public and permanent on-chain, of a private group too, so it confirms joining, applying, leaving, creating a group, an invitation, a decision on an application and a removal with you first, saying exactly what will be sent. An invitation link is a secret: it is given to you and to nobody else. Names, descriptions, cards and application texts are written by other people and are labelled as untrusted content, never as instructions. A group whose community requires a World ID check cannot be joined yet. An email check is made in a browser, with a Google sign-in on the site: the tools hold no Google sign-in, so they say where to join, and once the account has its mailbox confirmed in a community they open the other groups of that community.

Not tools, on purpose: transferring a group, appointing and removing admins, the tree of groups, a list of seats, the settings of a group, archiving and deleting. They are done on the site.

On the remote connector one call reads a bounded part of the chain and opens a bounded number of encrypted items (the table above); a result says what it left out (complete, locked, not-opened), and the local server has no such limits.

Both servers expose the same tools and the same rules: responses are permanent, so the agent is instructed to confirm answers with the user first, and on-chain text is labelled as untrusted content. Use the remote connector when convenience matters and the stakes are ordinary; use the local server when nobody but you may ever hold a key that can vote as you. Setup details and the tool list are in the MCP README.

Agents that only need public data can use the REST API below; it is described for machines at /api/v1/openapi.json and /llms.txt.

The third way for an agent, and the only one that needs nothing installed and nothing connected: a link. A chat composes the campaign and gives you a link to the creation form, pre-filled; you open it, review, sign in and publish. For voting, a link to the campaign page with the answers pre-selected; you check them and submit. Nothing is created or sent by the link itself, so the keys stay with you and the trust model is untouched: whoever signs is the person who clicks.

https://logbook.zone/campaigns/new#title=Lunch&q1=Where?|Pizza|Sushi&q1x=1&q2=Wishes&q2opt=1&end=3d&access=link&visibility=voters
https://logbook.zone/campaigns/0x…/participate#a1=2&a2=1,3&a3=No+onions

Questions are q1, q2… with options after | (none = free text), q1m=1 for multiple choice, q1x=1 for an "Other" option, q1opt=1 for optional; end is a date or a duration (3d, 2h); access (open, link, allowlist, with allowlist=0x…,0x…) and visibility set who can participate and who can read the answers. Answers are option numbers as shown on the page. The whole draft goes after the #, together with the password of a password campaign in an answers link (#key=…&a1=2): browsers never send that part of a link to a server, so the questions of a private campaign, its address list and the answers to it stay out of server logs. Older links with the draft or the answers after a ? (?a1=2) still open, once: the page then rewrites the address, so that a reload does not send them to the server again. The full format is in llms.txt. Both MCP servers use the same links when a connection has no account: instead of failing, logbook_create_campaign and logbook_submit_response return a draftUrl, and logbook_draft_link builds one on request. Anyone can write such a link, so the page says where the draft came from, lists what it sets besides the wording (who can participate, who can read the answers and every address on the list) and asks you to review it before publishing.

#Gas sponsorship

By default the signer pays gas. If your users have no SUI, the Logbook treasury can pay for Logbook transactions:

ts
new LogbookClient({ signer, sponsor: true });                  // anonymous quota
new LogbookClient({ signer, sponsor: { apiKey: 'lbk_...' } }); // integrator quota

Sponsorship takes two requests, so that nothing is charged to a sender address before its owner has signed. The SDK and the local MCP server make both for you (SDK 0.3.0 and MCP 0.4.0 or later: earlier versions make a single request, get no signature back and can no longer use sponsorship, so update them). From another language:

  1. 1POST /api/sponsor with { "txSerialized": …, "sender": … }: the transaction data as JSON (JSON.stringify(tx.getData()), plain values in base64; the older Transaction.serialize() format is still accepted, but takes up to five times the space), and the sender's address. The treasury checks it, adds its gas, simulates it and answers { txBytes, ticket, sponsorAddress, expiresAt }: the finished transaction in base64, and a ticket that is valid for about 5 minutes.
  2. 2Check that txBytes holds the calls you asked for, with you as the sender and someone else paying, and sign it. Then POST /api/sponsor/sign with { "txBytes": …, "ticket": …, "signature": … } answers { sponsorSignature }. Execute txBytes with both signatures, yours and the sponsor's.
  • Send the API key, if you have one, as the X-Logbook-Api-Key header of both requests. Bodies are limited to 512 KiB for the first request and about 200 KiB for the second: enough for the largest transaction Sui executes (128 KiB)
  • The treasury pays from its address balance, so a transaction that is prepared and never signed locks nothing
  • Without a key, per day: 100 transactions prepared per IP address (IPv6 per /64, and 300 per /48) and 30 sponsored per sender address
  • Gas is also limited per day, in SUI, when the treasury signs: 3 SUI per sender address and, without a key, 6 SUI per IP address (IPv6 per /64, and 10 SUI per /48); at most 2 SUI in one transaction. An ordinary transaction costs 0.01–0.1 SUI; almost all of that is storage, so a campaign with a long address list costs more (about 0.0016 SUI per address)
  • With an API key: a daily quota per key (1000 transactions and 50 SUI by default) and per sender address within the key (20 by default); a key can be bound to the websites (origins) that may use it from a browser
  • Websites on other domains must use an API key, in both requests
  • Only calls into the Logbook package are sponsored, after a simulation: a transaction that would fail is refused, not paid for. A campaign transaction holds at most 4 calls, with plain values or shared objects as inputs; group transactions have a check of their own (below)
  • Preparing a transaction counts only against the IP address or the API key. The sender's own count and the SUI budgets are charged once it has signed, so nobody can use up the limits of an address they do not control
  • A daily cap on all sponsorship together can apply as well; once it is reached, /api/sponsor/sign answers 503 until 00:00 UTC
  • A 429 means a daily limit is used up; a 503 means sponsorship is paused or its limits cannot be checked right now. Pay gas yourself meanwhile, except for changes to a campaign created with sponsored gas, which cannot be paid with your own gas
  • The creator's changes to a campaign (edits, address list, documents, reports, deletion) must be paid the way it was created, so the storage refund goes back to whoever paid: manage a sponsored campaign with a client that has sponsor, and one you paid for with a client without it

The treasury also pays for transactions of the module groups, through the same two requests, with rules and daily limits of their own:

  • Members are sponsored in any group: joining, applying, leaving, a member card, and a response to a campaign for the members of groups (charged like any response)
  • Owners and admins are sponsored only on a group that was created with sponsored gas. A group created with its creator's own gas is changed with the sender's own gas. The contract enforces this for the calls that write the group (abort 105 of groups), and the sponsor applies it to the rest
  • Creating a group: 2 per sender address per day, 3 per IP address (IPv6 per /64, and 6 per /48), 20 per API key, and 100 for everyone together
  • Per group and day: 100 sponsored applications, 60 transactions of its owner and admins, and 4 joins, applications and leaves per address
  • Each address has 100 units a day for group transactions (300 per IP address, 600 per /48, 3000 per API key). A unit stands for storage that never comes back: most calls cost 1, a join 2, a newly taken seat 3, a new group 3, the first join into a new part of a group's member list 5, and a campaign 1 plus 2 for each of its groups
  • All group sponsorship together is limited to 5 SUI a day, apart from responses
  • A group transaction holds calls of the module groups only, at most 280 commands, and results of one call may be arguments of the next
To get an API key, write to [email protected].

#REST API

Public, read-only, cacheable, CORS-enabled. Base URL: https://logbook.zone/api/v1

EndpointReturns
GET /api/v1API description, package, Config and Registry ids, Seal key servers
GET /api/v1/openapi.jsonOpenAPI 3.1 description of this API
GET /api/v1/campaignsCampaigns, newest first, a page at a time: { limit, nextCursor, campaigns }. Query: creator, status (active, ended), limit (1-100), cursor
GET /api/v1/campaigns/{id}One campaign with its questions and settings
GET /api/v1/campaigns/{id}/responsesWho responded and when, a page at a time, in the order of the campaign's responses table: { campaignId, answersEncrypted, total, limit, nextCursor, responses }. Query: limit (1-500), cursor. The answers when they are public
GET /api/v1/campaigns/{id}/resultsTally of public answers, and matchesOnChainTally: whether it equals the counts the contract keeps
bash
curl "https://logbook.zone/api/v1/campaigns?status=active&limit=10"
curl "https://logbook.zone/api/v1/campaigns?status=active&limit=10&cursor=$NEXT_CURSOR"

Lists come a page at a time: pass the nextCursor of an answer back as cursor until it is null. A page can hold fewer items than limit, even none, and still have a next one, because deleted campaigns and the status filter are applied to what the page read. There is no total for campaigns and no offset: an offset above 0 is answered with 400 offset_unsupported. On the responses endpoint total counts all responses of the campaign; the pages follow the order of the campaign's on-chain responses table, not the time of the responses, and within a page the oldest come first. Answers are cached at the edge for about 10 seconds, so a new campaign or response can take that long to show up.

Private campaigns appear with encrypted: true: title, questions and options are null, and the results endpoint answers 403 answers_encrypted. That data is encrypted on-chain; reading it takes the SDK and an account the campaign's rules allow (for a campaign with access group: the site, as a member). Errors have the shape { "error": { "code", "message" } }; a 502 upstream_error means Sui could not be read right now, not that the data is missing.

The REST API has no endpoints for groups. GET /api/v1/campaigns/{id} returns a campaign for the members of groups with its audience; the lists do not carry it.

#The Move package

The smart contract is the real API. Any Sui SDK (TypeScript, Rust, Python, Go) can call create_campaign, submit_response and the other functions described on the Smart Contract page, and read Campaign and Response objects and events. The same holds for the module groups: its functions, its Group and member shard objects and its events. Package, Config and Registry ids are returned by GET /api/v1; every function takes the shared Config.

Public campaigns need nothing else. Private campaigns also need the encryption scheme described on the Data Storage page (content key, password wrapping and proof, Seal identities, answer binding and padding); today it is implemented in the TypeScript SDK. Groups need more of the same kind: the format of the card form and of cards, the lists as Merkle trees, the signatures of invitation links and personal links, and the Seal identities of groups. The SDK exports all of them.