Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×

Question regarding API

Hello, my wife plays dnd on roll 20. I am working with Claude Code (I honestly have no idea what I am doing besides following directions). I am trying to get a site made for her to keep track of things all in one place because we are both scatter-brained. She wants to be able to contain session logs/character sheets and some other stuffs all on the site I am working on. One of the things we were trying to do is have character sheets linked to her roll 20 campaign so changes can be made from the tracker website and get pushed to roll20/ vice versa.  Is this possible? Been struggling with it the last 2 days. Not sure if I am feeding direction into claude wrong or if it is just not possible.  Below is the script Claude came up with. Any advice appreciated thanks.  var CHARACTER_MAP = { }; var TRACKED_ATTRS = { 'hp':true,'hp_max':true,'hp_temp':true,'ac':true,'speed':true, 'initiative':true,'hit_dice':true,'hit_dice_total':true, 'strength':true,'dexterity':true,'constitution':true, 'intelligence':true,'wisdom':true,'charisma':true, 'level':true,'xp':true, 'spell_slots_l1':true,'spell_slots_l2':true,'spell_slots_l3':true, 'spell_slots_l4':true,'spell_slots_l5':true,'spell_slots_l6':true, 'spell_slots_l7':true,'spell_slots_l8':true,'spell_slots_l9':true, 'spell_slots_total_l1':true,'spell_slots_total_l2':true,'spell_slots_total_l3':true, 'spell_slots_total_l4':true,'spell_slots_total_l5':true,'spell_slots_total_l6':true, 'spell_slots_total_l7':true,'spell_slots_total_l8':true,'spell_slots_total_l9':true }; var pendingOut = {}; function httpPost(path, data, onDone) { fetch(SERVER_URL + path, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + SYNC_TOKEN }, body: JSON.stringify(data) }).then(function(res) { return res.json(); }).then(function(json) { if (onDone) onDone(json); }).catch(function(e) { log('[Sync] POST error: ' + e.message); }); } function httpGet(path, onDone) { fetch(SERVER_URL + path, { method: 'GET', headers: { 'Authorization': 'Bearer ' + SYNC_TOKEN } }).then(function(res) { return res.json(); }).then(function(json) { onDone(json); }).catch(function(e) { log('[Sync] GET error: ' + e.message); }); } function flushPending(trackerCharId, roll20CharId) { var buf = pendingOut[trackerCharId]; if (!buf) return; var fields = {}; var hasFields = false; for (var k in buf) { if (k !== '_timer') { fields[k] = buf[k]; hasFields = true; } } if (!hasFields) return; pendingOut[trackerCharId] = {}; httpPost('/api/roll20/push', {characterId: trackerCharId, roll20CharId: roll20CharId, fields: fields}, function(result) { log('[Sync] Pushed ' + result.applied + ' field(s) for ' + trackerCharId + (result.conflicts > 0 ? ' (' + result.conflicts + ' conflict(s))' : '')); }); } function bufferChange(trackerCharId, roll20CharId, attrName, val) { if (!pendingOut[trackerCharId]) pendingOut[trackerCharId] = {}; pendingOut[trackerCharId][attrName] = val; clearTimeout(pendingOut[trackerCharId]._timer); pendingOut[trackerCharId]._timer = setTimeout(function() { flushPending(trackerCharId, roll20CharId); }, 3000); } function applyPending(roll20CharId, items) { if (!items || !items.length) return; var applied = 0; for (var i = 0; i < items.length; i++) { var fields = items[i].fields; if (!fields) continue; for (var attrName in fields) { var val = fields[attrName]; var attrs = findObjs({_type: 'attribute', characterid: roll20CharId, name: attrName}); if (attrs.length > 0) { attrs[0].set({current: val}); } else { createObj('attribute', {characterid: roll20CharId, name: attrName, current: val}); } applied++; } } if (applied > 0) { log('[Sync] Applied ' + applied + ' update(s) to ' + roll20CharId); } } function pollPending() { for (var roll20CharId in CHARACTER_MAP) { (function(r20Id) { var trackerCharId = CHARACTER_MAP[r20Id]; httpGet('/api/roll20/pending?characterId=' + encodeURIComponent(trackerCharId), function(items) { applyPending(r20Id, items); }); }(roll20CharId)); } } on('change:attribute', function(obj) { var roll20CharId = obj.get('characterid'); var trackerCharId = CHARACTER_MAP[roll20CharId]; if (!trackerCharId) return; var attrName = obj.get('name'); if (!TRACKED_ATTRS[attrName]) return; var val = parseFloat(obj.get('current')); if (isNaN(val)) return; bufferChange(trackerCharId, roll20CharId, attrName, val); }); on('chat:message', function(msg) { if (msg.type !== 'api') return; var trimmed = msg.content.trim(); var spaceIdx = trimmed.indexOf(' '); var cmd = (spaceIdx === -1 ? trimmed : trimmed.slice(0, spaceIdx)).toLowerCase(); if (cmd === '!sync-status') { var linked = Object.keys(CHARACTER_MAP); var lines = ['<b>Sync</b> Server: ' + SERVER_URL, 'Linked: ' + linked.length]; for (var i = 0; i < linked.length; i++) { lines.push(linked[i] + ' -> ' + CHARACTER_MAP[linked[i]]); } sendChat('Sync', lines.join('<br>')); } else if (cmd === '!sync-push') { var count = 0; for (var r20Id in CHARACTER_MAP) { flushPending(CHARACTER_MAP[r20Id], r20Id); count++; } sendChat('Sync', 'Pushed for ' + count + ' character(s).'); } else if (cmd === '!sync-pull') { pollPending(); sendChat('Sync', 'Polling...'); } }); on('ready', function() { log('[Sync] Ready. Server: ' + SERVER_URL + ' | Characters: ' + Object.keys(CHARACTER_MAP).length); if (Object.keys(CHARACTER_MAP).length === 0) { log('[Sync] WARNING: CHARACTER_MAP is empty.'); } function schedulePoll() { setTimeout(function() { pollPending(); schedulePoll(); }, 5000); } schedulePoll(); });
1774476147
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Hi Mazechain! Welcome to the world of scripting!  There are a couple of issues working against you. One is structural, the other is insurmountable due to how the API works. First, when writing an API script, be aware that all scripts are concatenated into one gian single script at runtime. Your script is not properly scoped. I.e. any variables you use can be confused for identically named variables in other scripts. You can ask Claude to help you with this; it's an easy fix. Basically, you need to wrap your entire script into a self-contained block, so that all of it's variable names do not pollute the global namespace. For similar reasons, I'd suggest using let  to var . Let stays within it's own scope, whereas var can be detected outside of it's functions. Var "leaks" upward. When in doubt, just avoid var. Secondly, and insurmountably: The Roll20 API runs in a sandbox. It cannot read or write data outside the Roll20 environment, primarily for security and privacy reasons, as well as avoiding piracy. You cna keep centralized data within a single game by reading and writing it to a handout for instance, but you can't interat with another place on the web. That would require a userscript: a script that runs in the browser environment and controlled by a browser extension like TamperMonkey or GreaseMonkey. Poorly designed scripts of this sort have the potential to really mess up your game, so I'd avoid that route until you are a bit more agile with scripting, at the very least.
By single game, you mean the whole instance correct? or per launch of game? Currently going with just reading from roll20 until I am more acclimated then going for the next step forward with browser extension
1774484311
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
By "single game" I mean that the sandbox of one game cannot interact with the elements of a different game. If you have created multiple games they cannot "talk to one another" in any way. When you ask about "per launch", I assume you mean persistence of data between sessions? You can write data to a state object that will persist between launches. That can hold up to 2 MB, so it's best for holding configuration options and the like, rather than reams of data. A better way to store that persistently is to write the data to a handout, treating it like a file. If you write data to a handout, then that handout could be Transmogrified from one game to another, but that is hardly automatic. You could also output JSON or text data to a handout or to chat that could be copied/pasted to someplace your external site could read, but again, this would be a tedious and manually intensive solution. Since I don't see a Pro or Elite tag next to your name, I assume you are writing this for a game created by a Pro or Elite user?
1774535562
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
I'd recommend just using roll20 for what you are building the site for. It already has character sheets, journal items for keeping track of what is going on, maps for arranging encounters, etc. If your system doesn't have a character sheet available on roll20, you can always build one (assuming one of you has a pro+ subscription).