

Below is a concise, practical developer guide showing exactly which APIs on api.aurelon.com you should call and how to call them to build the scanner/preview/send tool yourself. It includes request/response shapes, headers, error-handling tips, and working JavaScript examples for each step (configuration → decode QR → preview → send). I also include the exact GUID decoding routine (base64 → binary → Microsoft GUID byte-order swap) your QR format requires.
Configuration — get devices for a siteGET https://api.aurelon.com/api/v2/configuration/site/{site}
(populate device dropdown from Devices[].Name/Devices[].DeviceGUID)
Scan QR (camera or handheld) → decode JobGUID and thumbnail page.
Preview — fetch thumbnail and job details:GET https://api.aurelon.com/api/v2/job/{JobGUID}/thumbnail/{page}GET https://api.aurelon.com/api/v2/job/{JobGUID} (returns XML)
Send job to device:POST https://api.aurelon.com/api/v2/job/{JobGUID}/device/{DeviceGUID}
All API calls must include the MISKey header for authentication.
API base: https://api.aurelon.com/api/v2 (replace if needed)
Auth header: MISKey: <your-miskey-value>
Content types: Use Accept: application/json for JSON endpoints; for job details use Accept: application/xml if you expect XML.
CORS: If building a browser-based UI, ensure the API responds with the appropriate CORS headers for your origin (or run the UI in an environment where the API allows the origin).
Endpoint
GET {API_BASE}/configuration/site/{site}
Header: Accept: application/json
Header: MISKey: <your-miskey>Purpose
Return configuration records for computers and attached devices at the given site. Use this to populate the printer device selector.
Example response (trimmed)

[
{
"ComputerGUID": "8447e5b0-99ac-11e7-a2d0-215af4f8ef85",
"Name": "MacBookPro",
"Modified": "2018-11-30T19:00:24.977",
"Devices": [
{
"DeviceGUID": "2ac34d2a-62a9-461a-80c9-0015ed333d50",
"DriverID": "1601",
"Modified": "2025-12-09T21:18:06.19",
"Name": "Durst Rhotex 322",
"Queues": [{ "QueueGUID":"...", "Name":"Rhotex Q" }]
},
...
]
}
]
What to extract
For each device: Devices[].Name → visible label, Devices[].DeviceGUID → value to store and use for sending the job.
JS example
async function fetchDevices(apiBase, site, MISKey) {
const url = `${apiBase}/configuration/site/${encodeURIComponent(site)}`;
const resp = await fetch(url, { headers: { 'Accept':'application/json', 'MISKey': MISKey }});
if (!resp.ok) throw new Error(`Config fetch failed ${resp.status}`);
return await resp.json(); // parse and extract Devices arrays
}Your QR contains a URL like: http://pfam.cc/id/01JrqHZorpqUEKJso-CZQZdkw/p1
Path /id/01{base64Guid}/{pageToken}
01J prefix indicates a JobGUID. Strip that prefix.
{base64Guid} is URL-safe Base64 of the 16-byte binary GUID. Decoding rules (per your environment): replace _ → /, - → +, then append ==, atob()-decode to bytes (16 bytes).
The binary follows the Microsoft/Windows GUID storage layout (the first 3 fields are little-endian). You must reorder bytes for textual GUID.
{pageToken} like p1 means thumbnail page 1. Default to 1 if absent.
GUID conversion algorithm (JavaScript)
This code decodes the base64 segment and applies the Microsoft GUID byte-order correction:
function decodeBase64GuidToHyphenated(b64seg) {
if (!b64seg) return null;
// apply user rules: '_' -> '/', '-' -> '+', then add '=='
let b64 = b64seg.replace(/_/g,'/').replace(/-/g,'+') + '==';
const bin = atob(b64); // binary string
const bytes = new Uint8Array(bin.length);
for (let i=0;i<bin.length;i++) bytes[i] = bin.charCodeAt(i) & 0xff;
if (bytes.length < 16) { // fallback: return hex
return Array.from(bytes).map(b => ('0'+b.toString(16)).slice(-2)).join('');
}
const hb = b => ('0' + b.toString(16)).slice(-2);
// MS layout -> textual layout
const data1 = hb(bytes[3]) + hb(bytes[2]) + hb(bytes[1]) + hb(bytes[0]);
const data2 = hb(bytes[5]) + hb(bytes[4]);
const data3 = hb(bytes[7]) + hb(bytes[6]);
const data4p1 = hb(bytes[8]) + hb(bytes[9]);
let data4p2 = '';
for (let i = 10; i <= 15; i++) data4p2 += hb(bytes[i]);
return `${data1}-${data2}-${data3}-${data4p1}-${data4p2}`.toLowerCase();
}Parsing the QR URL
Extract path parts.
If the segment after /id/ starts with 01J strip 3 chars. Then decode remainder.
For page token parse p(\d+) or (\d+) to get page number.
Endpoint
GET {API_BASE}/job/{JobGUID}/thumbnail/{page}
Header: Accept: application/json (or Accept: image/*)
Header: MISKey: <your-miskey>Behavior / Response
The API may return either:
Binary image (Content-Type image/png / image/jpeg / application/octet-stream), OR
JSON that contains a base64 payload, e.g. { "ThumbnailBase64": "..." } or { "thumbnail": "..." }, OR
Possibly an array of objects where first contains ThumbnailBase64 or thumbnail.
Display strategy:
If Content-Type is image/* (or application/octet-stream) → resp.blob() then URL.createObjectURL(blob) and set <img>.src.
If JSON → look for ThumbnailBase64, thumbnailBase64, Thumbnail, thumbnail, or nested data.thumbnail, and set <img>.src = "data:image/png;base64," + b64.
JS example
async function fetchThumbnail(apiBase, jobGuid, page, MISKey) {
const url = `${apiBase}/job/${encodeURIComponent(jobGuid)}/thumbnail/${encodeURIComponent(page)}`;
const resp = await fetch(url, { headers: { 'Accept': 'application/json', 'MISKey': MISKey }});
if (!resp.ok) throw new Error('thumbnail failed ' + resp.status);
const ct = resp.headers.get('Content-Type') || '';
if (ct.includes('application/json')) {
const j = await resp.json();
// get base64 in j.ThumbnailBase64 or j.thumbnail or similar
} else if (ct.startsWith('image/') || ct === 'application/octet-stream') {
const blob = await resp.blob();
return URL.createObjectURL(blob);
} else {
// fallback: try resp.text() and parse JSON
}
}Endpoint
GET {API_BASE}/job/{JobGUID}
Header: Accept: application/xml
Header: MISKey: <your-miskey>Behavior
The endpoint returns the job metadata. In your current environment the response is XML, so parse it with DOMParser.
Common fields to extract for the job card:
Title / JobName / Name / DocumentName / FileName
Status / State / JobStatus
PageCount / Pages
SubmittedBy / User / CreatedBy
CreatedAt / SubmittedAt
Priority
Parsing strategy
Use DOMParser().parseFromString(xml, 'application/xml').
Because the XML may have namespaces or different tag names, look for several candidate names (case-insensitive) and search by localName for robustness.
If the response is JSON instead of XML, parse JSON.
JS example
function parseJobXml(xmlText) {
const doc = new DOMParser().parseFromString(xmlText, 'application/xml');
if (doc.getElementsByTagName('parsererror').length) throw new Error('XML parse error');
function find(cands) {
cands = cands.map(s => s.toLowerCase());
// try direct element names, then fallback to scanning localName
for (const cand of cands) {
const nodes = doc.getElementsByTagName(cand);
if (nodes && nodes.length && nodes[0].textContent) return nodes[0].textContent.trim();
}
const all = doc.getElementsByTagName('*');
for (const el of all) {
if (el.localName && cands.includes(el.localName.toLowerCase()) && el.textContent) return el.textContent.trim();
}
return null;
}
return {
JobGUID: find(['JobGUID','JobId','GUID','Id','id']),
Title: find(['Title','JobName','Name','DocumentName','FileName']),
Status: find(['Status','State','JobStatus','JobState']),
PageCount: find(['PageCount','Pages','NumberOfPages'])
// etc...
};
}Endpoint
POST {API_BASE}/job/{JobGUID}/device/{DeviceGUID}
Header: MISKey: <your-miskey>
Header: Accept: application/json
Body: none (or null) — the API expects POST with path paramsBehavior
Sends the job to the device identified by DeviceGUID. Returns success JSON or error.
Use 200/201 to indicate success. On error, inspect status and body.
JS example
async function sendJobToDevice(apiBase, jobGuid, deviceGuid, MISKey) {
const url = `${apiBase}/job/${encodeURIComponent(jobGuid)}/device/${encodeURIComponent(deviceGuid)}`;
const resp = await fetch(url, { method: 'POST', headers: { 'MISKey': MISKey, 'Accept': 'application/json' }});
if (!resp.ok) {
const txt = await resp.text().catch(()=>null);
throw new Error('Send failed: ' + resp.status + ' ' + txt);
}
return await resp.json().catch(()=>null);
}401 / 403: MISKey missing/invalid. Show a message to user to check MISKey from Hub (User Profile → Settings → External API).
404 job: JobGUID may be wrong; implement a helpful message.
CORS: Browser apps need API CORS headers. If you get network/CORS errors, either run a backend proxy or ensure the API allows your origin.
Binary vs JSON thumbnail: handle both; fall back to resp.text() if content-type unknown and attempt JSON parse for base64.
GUID decode fallback: If decoded GUID doesn’t correspond to a job, you can optionally try the decoded bytes without the Microsoft swap (or try alternative byte orders) and test by GET /job/{GUID} to see which GUID resolves.
Retries: For network instability, retry thumbnail fetch once; for idempotent GETs it’s safe to retry.

async function onScan(scannedText) {
// 1) decode
const decoded = decodeScanResult(scannedText); // decodeScanResult returns {jobGuid, page}
if (!decoded) throw new Error('Could not decode JobGUID');
const { jobGuid, page } = decoded;
// 2) show jobGUID/page
ui.showJobGUID(jobGuid, page);
// 3) fetch thumbnail
try {
const thumbSrc = await fetchThumbnail(API_BASE, jobGuid, page, MISKey);
ui.setThumbnail(thumbSrc);
} catch(e) {
console.warn('thumbnail failed', e);
}
// 4) fetch job details (XML)
const jobXmlText = await fetch(`${API_BASE}/job/${encodeURIComponent(jobGuid)}`, { headers: { 'Accept':'application/xml', 'MISKey': MISKey }});
const jobText = await jobXmlText.text();
const jobObj = parseJobXml(jobText);
ui.showJobCard(jobObj);
// 5) When user clicks "Send"
await sendJobToDevice(API_BASE, jobGuid, deviceGuid, MISKey);
ui.notify('Job sent');
}
Where to get MISKey & Site#
MISKey → Hub User Profile → Settings → External API.
Site# → Hub Account → Sites.
Printed QR format
Hub job bag prints URLs in the pfam.cc form; ensure your QR generator produces the 01{base64} format as above.
Microsoft GUID endianness
The first 4 + 2 + 2 bytes are stored little-endian — you must reverse those bytes to produce the canonical textual GUID. The provided decodeBase64GuidToHyphenated implements this.
Camera auto-stop
In the UI you probably want the camera to stop after the first successful decode to avoid duplicate scans. Stop html5-qrcode on success.
Implement configuration UI →GET /configuration/site/{site} → populate devices.
Implement QR decode function (base64 normalization + MS GUID swap).
ImplementGET /job/{GUID}/thumbnail/{page} handling binary & JSON-with-base64.
ImplementGET /job/{GUID} with XML parsing.
ImplementPOST /job/{GUID}/device/{DeviceGUID} to send job, with MISKey header.
PersistAPI_BASE, Site#, MISKey, DeviceGUID (localStorage).
Camera scanning (e.g.,html5-qrcode) with default back camera and always auto-stop.