Dear @lubos, how do I get the business details UUID from API2? It seems that the endopoint to get the list is missing (maybe because it’s just a list of one items?).
It appears that the POST action returns the UUID, but the subsequent GET call has no parameters for that specific resource. Consequently, the “Business” section seems untouchable via the APIs.
Furthermore, I’ve observed that the “Extension” module itself is not mapped/exposed by the APIs.
Is this an oversight, or is it an intentional design choice (by design)?
Post scriptum: It’s been a while since I was last active on this forum, although I’ve been using Manager.io for well over seven years.
I’ve noticed that interaction here has decreased significantly and that @lubos intervenes very rarely. I posted five different topics in the last few days and received no replies in any of them.
Is Manager.io still an actively developed, primary project?
Hello @Davide,
I will start with your last question first:
Yes, it is under active development, however, the past couple of years saw a lot of developments:
-
world governments going all nuts with their over the top e Invoicing requirements and Manager developing a framework to be able to manage all this chaos
-
Api2 & 3 being introduced
-
Introduction of Relay, deprecation of Relay and rework of Extension
-
Redesign of Inventory calculation process
-
Performance optimization to allow for ever increasing user database sizes
-
Complete rework of Themes
-
Change of desktop web app platform to electron
-
Development of electron print menus
-
Mobile responsive layout
-
Refreshing of design
-
Introduction of dark mode
Some of these projects are still in the pipelines in addition to many ideas being checked off and countless bug fixes related to side effects to adding or removing of features.
So I can say for sure, that the development of Manager hasn’t been this active since I joined, it’s just that there’s too many pipelines in parallel for @lubos to give the same level of attention to a single aspect like he used to, say 5 years ago.
Good observation, I too had to come to terms with this, however, it’s not a deal stopper simply because Business details is only but a tiny part of any projecy.
For example, you get the UUID once per implementation, you can also get ideas from what @Mabaega did for various e Invoicing Integration, most notably, the one for Saudi Arabia.
It could be both, in either case, APIs are not undergoing any developments at the moment.
Thanks for the kind reply. I was just referring to the forum iteration. Anyway, since we’re discussing…
Where can I find the documentation for API3? It seems to me that the official documentation is poorly maintained compared to the past.
Yet another project abandoned without having seen a flow from start to finish.
The interface is anything but responsive.
I’m looking for a place to save the JSON settings for the extensions I’m working on. Manager doesn’t support any of this. So I first considered using folders, since we don’t use them in the company, but then I thought about distributing the extension to everyone, and there was a risk that, if implemented in some workflow, someone could tamper with it and delete everything.
I then moved to the custom fields under the business area since it is the area most logically connected to common settings and is a little frequented area but the APIs are not implemented.
Without losing heart, I thought about using custom fields under the extension itself and writing the settings there. Custom fields aren’t implemented under extensions (just as they aren’t on the chart of accounts, a request I made under ideas a century ago and never implemented, but that’s another sad story).
I post on the forum requests for solutions that would benefit everyone, solutions that would take five minutes of work like unblocking the APIs under business, adding custom fields to extensions and no one responds… do you understand how frustrating it is?
I developed the API mapping framework, taking the risk that the extensions will be abandoned for other ideas, as has happened in the past.
I’m creating a development framework to implement extensions that will benefit everyone. I wanted to honor the forum with a first extension that allows flexible autonumbering, since it’s a request that’s been circulating on the forum for ages. But all this is putting me off.
Which extension are you working with? Settings → Extensions or Settings → Obsolete Features → ScriptExtensions?
I store the settings for each business inside Business Details using custom fields.
For the API, I use API 2 to retrieve array objects, and API 3 to process single objects.
You might find the Inline Extension useful as a reference. For API 3 endpoints, you can usually try the same endpoints from API 2 by simply replacing api2 with api3.
Inline Extension - Manager API Data Fetcher
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manager Data Fetcher</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 25px 70px rgba(0,0,0,0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 32px 40px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 20px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}
.header-title {
display: flex;
align-items: center;
gap: 16px;
font-size: 28px;
font-weight: 700;
letter-spacing: -0.5px;
}
.header-icon {
font-size: 36px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
}
.header-actions {
display: flex;
gap: 12px;
align-items: center;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: white;
color: #667eea;
font-weight: 600;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255,255,255,0.4);
}
.btn-success {
background: #10b981;
color: white;
}
.btn-success:hover:not(:disabled) {
background: #059669;
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(16,185,129,0.4);
}
.content {
padding: 40px;
}
.status-section {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 100%);
border-radius: 16px;
padding: 28px;
margin-bottom: 32px;
border: 1px solid #e0e7ff;
box-shadow: 0 2px 8px rgba(102,126,234,0.08);
}
.status-text {
font-size: 15px;
color: #4b5563;
margin-bottom: 18px;
min-height: 24px;
font-weight: 500;
}
.progress-container {
background: #e5e7eb;
height: 12px;
border-radius: 8px;
overflow: hidden;
position: relative;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 8px;
transition: width 0.4s ease;
width: 0%;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.5);
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
margin-bottom: 32px;
}
.stat-card {
background: white;
padding: 28px;
border-radius: 16px;
border: 1px solid #e5e7eb;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-6px);
box-shadow: 0 12px 28px rgba(102, 126, 234, 0.2);
}
.stat-label {
font-size: 13px;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 12px;
font-weight: 600;
}
.stat-value {
font-size: 36px;
font-weight: 800;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.json-container {
background: #1a1a2e;
color: #eaeaea;
border-radius: 16px;
padding: 32px;
max-height: 650px;
overflow: auto;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Consolas', monospace;
font-size: 14px;
line-height: 1.8;
box-shadow: inset 0 4px 12px rgba(0,0,0,0.4), 0 4px 20px rgba(0,0,0,0.1);
border: 1px solid #2d2d44;
}
.json-container::-webkit-scrollbar {
width: 12px;
height: 12px;
}
.json-container::-webkit-scrollbar-track {
background: #2d2d2d;
border-radius: 6px;
}
.json-container::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 6px;
}
.json-container::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #5568d3, #653a8b);
}
.error-box {
background: #fee2e2;
color: #991b1b;
padding: 18px 24px;
border-radius: 12px;
border-left: 4px solid #ef4444;
margin-bottom: 24px;
display: none;
font-weight: 500;
}
.error-box.show {
display: block;
}
.empty-state {
text-align: center;
padding: 80px 20px;
color: #999;
}
.empty-state-icon {
font-size: 72px;
margin-bottom: 20px;
opacity: 0.4;
}
.empty-state-text {
font-size: 18px;
margin-bottom: 10px;
font-weight: 600;
color: #6b7280;
}
.empty-state-subtext {
font-size: 14px;
color: #9ca3af;
}
.footer {
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 100%);
padding: 20px 40px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 13px;
color: #6b7280;
border-top: 1px solid #e5e7eb;
font-weight: 500;
}
.spinner {
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.header {
padding: 24px;
}
.content {
padding: 24px;
}
.header-title {
font-size: 22px;
}
.stats {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="header-title">
<span class="header-icon">📊</span>
<span>Manager Data Fetcher</span>
</div>
<div class="header-actions">
<button id="fetchBtn" class="btn btn-primary">
<span id="fetchBtnIcon">▶️</span>
<span id="fetchBtnText">Fetch All Data</span>
</button>
<button id="copyBtn" class="btn btn-success" disabled>
📋 Copy JSON
</button>
</div>
</div>
<div class="content">
<div class="status-section">
<div id="statusText" class="status-text">Ready to fetch data from Manager API</div>
<div class="progress-container">
<div id="progressBar" class="progress-bar"></div>
</div>
</div>
<div id="errorBox" class="error-box"></div>
<div id="stats" class="stats" style="display: none;">
<div class="stat-card">
<div class="stat-label">Total Endpoints</div>
<div id="totalEndpoints" class="stat-value">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Success</div>
<div id="successCount" class="stat-value">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Failed</div>
<div id="errorCount" class="stat-value">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Records</div>
<div id="totalRecords" class="stat-value">0</div>
</div>
</div>
<div id="emptyState" class="empty-state">
<div class="empty-state-icon">📦</div>
<div class="empty-state-text">No data yet</div>
<div class="empty-state-subtext">Click "Fetch All Data" to retrieve data from Manager</div>
</div>
<pre id="jsonOutput" class="json-container" style="display: none;"></pre>
</div>
<div class="footer">
<div id="footerStatus">Ready</div>
<div>Manager Data Fetcher v2.0</div>
</div>
</div>
<script>
// --------------------------
// ENDPOINTS CONFIGURATION
// --------------------------
const ENDPOINTS = [
{ url: '/api2/inventory-kits?pageSize=10000&fields=ItemCode&fields=ItemName', dataField: 'inventoryKits', isArray: true },
{ url: '/api2/non-inventory-items?pageSize=10000&fields=ItemCode&fields=ItemName', dataField: 'nonInventoryItems', isArray: true },
{ url: '/api2/classic-custom-fields?pageSize=10000&fields=Name&fields=Placement', dataField: 'classicCustomFields', isArray: true },
{ url: '/api2/image-custom-fields?pageSize=10000&fields=Name&fields=Placement', dataField: 'imageCustomFields', isArray: true },
{ url: '/api2/text-custom-fields?pageSize=10000&fields=Name&fields=Placement', dataField: 'textCustomFields', isArray: true },
{ url: '/api2/number-custom-fields?pageSize=10000&fields=Name&fields=Placement', dataField: 'numberCustomFields', isArray: true },
{ url: '/api2/date-custom-fields?pageSize=10000&fields=Name&fields=Placement', dataField: 'dateCustomFields', isArray: true },
{ url: '/api2/inventory-items?pageSize=10000&fields=ItemCode&fields=ItemName&fields=ControlAccount', dataField: 'inventoryItems', isArray: true },
{ url: '/api2/bank-and-cash-accounts?pageSize=10000&fields=Code&fields=Name&fields=ControlAccount', dataField: 'bankAndCashAccounts', isArray: true },
{ url: '/api2/fixed-assets?pageSize=10000&fields=Code&fields=Name&fields=ControlAccount', dataField: 'fixedAssets', isArray: true },
{ url: '/api2/intangible-assets?pageSize=10000&fields=Code&fields=Name&fields=ControlAccount', dataField: 'intangibleAssets', isArray: true },
{ url: '/api2/special-accounts?pageSize=10000&fields=Code&fields=Name&fields=ControlAccount', dataField: 'specialAccounts', isArray: true },
{ url: '/api2/employees?pageSize=10000&fields=Code&fields=Name&fields=ControlAccount', dataField: 'employees', isArray: true },
{ url: '/api2/capital-accounts?pageSize=10000&fields=Code&fields=Name&fields=ControlAccount', dataField: 'capitalAccounts', isArray: true },
{ url: '/api2/chart-of-accounts?pageSize=10000&fields=Key&fields=Code&fields=Name', dataField: 'chartOfAccounts', isArray: true },
{ url: '/api2/suppliers?pageSize=10000&fields=Code&fields=Name&fields=ControlAccount', dataField: 'suppliers', isArray: true },
{ url: '/api2/customers?pageSize=10000&fields=Code&fields=Name&fields=ControlAccount', dataField: 'customers', isArray: true },
{ url: '/api2/tax-codes?pageSize=10000&fields=Name', dataField: 'taxCodes', isArray: true },
{ url: '/api3/business-details-form', dataField: 'businessdetails', isArray: false }, // single object
{ url: '/api3/sales-invoice-form/f57bbda9-29dd-4da6-b460-7f55eb9c0172', dataField: 'salesInvoice', isArray: false }, // single object
];
// --------------------------
// DOM ELEMENTS
// --------------------------
const fetchBtn = document.getElementById('fetchBtn');
const fetchBtnIcon = document.getElementById('fetchBtnIcon');
const fetchBtnText = document.getElementById('fetchBtnText');
const copyBtn = document.getElementById('copyBtn');
const statusText = document.getElementById('statusText');
const progressBar = document.getElementById('progressBar');
const errorBox = document.getElementById('errorBox');
const stats = document.getElementById('stats');
const emptyState = document.getElementById('emptyState');
const jsonOutput = document.getElementById('jsonOutput');
const footerStatus = document.getElementById('footerStatus');
const totalEndpointsEl = document.getElementById('totalEndpoints');
const successCountEl = document.getElementById('successCount');
const errorCountEl = document.getElementById('errorCount');
const totalRecordsEl = document.getElementById('totalRecords');
let currentJsonData = null;
let isFetching = false;
// --------------------------
// API REQUEST via PostMessage
// --------------------------
function makeManagerApiRequest(path) {
return new Promise((resolve, reject) => {
const reqId = crypto.randomUUID();
const timeout = setTimeout(() => {
window.removeEventListener('message', handler);
reject(new Error('Request timeout'));
}, 30000);
function handler(event) {
if (event.source !== window.parent) return;
if (!event.data || event.data.requestId !== reqId) return;
clearTimeout(timeout);
window.removeEventListener('message', handler);
if (event.data.status >= 400) {
reject(new Error(`HTTP ${event.data.status}`));
} else {
resolve(event.data.body);
}
}
window.addEventListener('message', handler);
window.parent.postMessage({
type: 'api-request',
requestId: reqId,
path: path
}, '*');
});
}
// --------------------------
// UI HELPERS
// --------------------------
function showError(message) {
errorBox.textContent = '⚠️ ' + message;
errorBox.classList.add('show');
}
function hideError() {
errorBox.classList.remove('show');
}
function updateProgress(current, total) {
const percent = (current / total) * 100;
progressBar.style.width = percent + '%';
}
function setFetchingState(fetching) {
isFetching = fetching;
fetchBtn.disabled = fetching;
if (fetching) {
fetchBtnIcon.innerHTML = '<span class="spinner"></span>';
fetchBtnText.textContent = 'Fetching...';
} else {
fetchBtnIcon.textContent = '▶️';
fetchBtnText.textContent = 'Fetch All Data';
}
}
function displayResults(results) {
const consolidatedData = {};
// Simply copy all results to consolidatedData
for (const [key, value] of Object.entries(results)) {
consolidatedData[key] = value;
}
const jsonString = JSON.stringify(consolidatedData, null, 2);
currentJsonData = jsonString;
jsonOutput.textContent = jsonString;
jsonOutput.style.display = 'block';
emptyState.style.display = 'none';
copyBtn.disabled = false;
// Calculate stats
const successCount = Object.values(results).filter(r => !r?.error).length;
const errorCount = ENDPOINTS.length - successCount;
const totalRecords = Object.values(results)
.filter(r => !r?.error)
.reduce((sum, r) => {
if (Array.isArray(r)) {
return sum + r.length;
} else if (r && typeof r === 'object' && !r.error) {
// Single object counts as 1
return sum + 1;
}
return sum;
}, 0);
totalEndpointsEl.textContent = ENDPOINTS.length;
successCountEl.textContent = successCount;
errorCountEl.textContent = errorCount;
totalRecordsEl.textContent = totalRecords.toLocaleString();
stats.style.display = 'grid';
footerStatus.textContent = `✓ ${successCount} success | ✗ ${errorCount} failed`;
}
// --------------------------
// MAIN FETCH FLOW
// --------------------------
async function fetchAllData() {
if (isFetching) return;
setFetchingState(true);
hideError();
progressBar.style.width = '0%';
const results = {};
let completed = 0;
const total = ENDPOINTS.length;
try {
statusText.textContent = `⏳ Starting fetch of ${total} endpoints...`;
for (const endpoint of ENDPOINTS) {
statusText.textContent = `🔄 Fetching: ${endpoint.dataField}...`;
console.log(`[Fetcher] Fetching: ${endpoint.url}`);
try {
const data = await makeManagerApiRequest(endpoint.url);
if (endpoint.isArray) {
// For array endpoints, extract the array from the response
if (data && data[endpoint.dataField] && Array.isArray(data[endpoint.dataField])) {
results[endpoint.dataField] = data[endpoint.dataField];
console.log(`[Fetcher] ✓ ${endpoint.dataField}: ${data[endpoint.dataField].length} records`);
} else {
throw new Error(`Expected array in '${endpoint.dataField}'`);
}
} else {
// For single object (businessdetails), store the whole response
if (data && typeof data === 'object') {
results[endpoint.dataField] = data;
console.log(`[Fetcher] ✓ ${endpoint.dataField}: single object`);
} else {
throw new Error('Expected object for businessdetails');
}
}
} catch (err) {
results[endpoint.dataField] = {
url: endpoint.url,
error: err.message || 'Unknown error'
};
console.error(`[Fetcher] ✗ ${endpoint.dataField}:`, err);
}
completed++;
updateProgress(completed, total);
statusText.textContent = `⏳ Progress: ${completed}/${total} (${Math.round((completed/total)*100)}%)`;
}
statusText.textContent = '✅ All endpoints fetched successfully!';
displayResults(results);
} catch (err) {
console.error('[Fetcher] Fatal error:', err);
showError(err.message || 'Unknown error occurred');
statusText.textContent = '❌ Fetch failed';
footerStatus.textContent = 'Error occurred';
} finally {
setFetchingState(false);
}
}
// --------------------------
// COPY TO CLIPBOARD
// --------------------------
async function copyToClipboard() {
if (!currentJsonData) return;
try {
await navigator.clipboard.writeText(currentJsonData);
const originalText = copyBtn.textContent;
copyBtn.textContent = '✓ Copied!';
copyBtn.style.background = '#059669';
setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.style.background = '#10b981';
}, 2000);
console.log('[Fetcher] JSON copied to clipboard');
} catch (err) {
console.error('[Fetcher] Copy failed:', err);
showError('Failed to copy: ' + err.message);
}
}
// --------------------------
// EVENT LISTENERS
// --------------------------
fetchBtn.addEventListener('click', fetchAllData);
copyBtn.addEventListener('click', copyToClipboard);
// --------------------------
// INITIALIZATION
// --------------------------
console.log('[Fetcher] Manager Data Fetcher initialized');
console.log('[Fetcher] Total endpoints configured:', ENDPOINTS.length);
</script>
</body>
</html>
And as a reminder, we do not need an access token to access either API 2 or API 3 from within an extension. We only need to make a request to the appropriate endpoint, and the parent will provide the data to the extension.
Thanks for your help. I will try out you extension and come back with my feedback. Are you sure that you don’t need any token with api2 under the extensions? From my experience they don’t work without it. I mean not to just read but also to write data. Maybe under api3 is different.
When an extension is loaded, it does not communicate with the Manager server directly. Instead, the extension sends a request to the parent window using postMessage. The parent window receives that request, performs the actual API call internally, and then returns the result back to the extension.
In my example above, I only demonstrated the GET method, but I can confirm that all HTTP methods—GET, POST, PUT, PATCH, and DELETE—work correctly when processing single objects.
POST, PUT & PATCH Examples
const generateRequestId = () => crypto.randomUUID();
function makeManagerApiRequest(path, method = 'GET', body = null) {
return new Promise(function (resolve, reject) {
const reqId = generateRequestId();
function handler(event) {
if (event.source !== window.parent) {
return;
}
if (!event.data || event.data.requestId !== reqId) {
return;
}
window.removeEventListener("message", handler);
if (event.data.status && event.data.status >= 400) {
const errorMsg = `HTTP ${event.data.status}: ${event.data.statusText || 'Request failed'}`;
reject(new Error(errorMsg));
} else {
// Return the body directly as before
resolve(event.data.body);
}
}
window.addEventListener("message", handler);
const message = {
type: "api-request",
requestId: reqId,
path: path,
method: method
};
if (body) {
message.body = body;
}
window.parent.postMessage(message, "*");
});
}
// Function to generate QR code SVG from base64qrcode string
function generateInvoiceQrSvg(base64qrcode) {
try {
const qr = qrcode(0, 'L');
qr.addData(base64qrcode);
qr.make();
return qr.createSvgTag({ scalable: true });
} catch (error) {
console.error('Error generating QR code:', error);
throw new Error('Failed to generate QR code: ' + error.message);
}
}
// Update QR code in Manager (separate function)
async function updateInvoiceQrCode(invoiceKey, base64qrcode, isSales = true, skipThemeTogle = false, sKipQrField = false) {
try {
await makeManagerApiRequest(`/api3/image-custom-field-form/${"d2e9265a-460e-4a06-83f9-29a523a4d516"}`, 'PUT', {
name: "QR Code",
height: 160,
width: 160,
position: 200,
placement: [
"ad12b60b-23bf-4421-94df-8be79cef533e",
"245e5943-0092-409d-96ae-e2ee10eac75b"
],
displayOnView: true,
lockedForManualEditing: true,
excludeFromCopyingOrCloning: true
});
if (sKipQrField) {
await makeManagerApiRequest(`/api3/text-custom-field-form/${"a1b2c3d4-e5f6-4abc-8def-abcdef000018"}`, 'PUT', {
name: "Base64 QRCode",
displayOnView: false,
inactive: false,
placement: [
"ad12b60b-23bf-4421-94df-8be79cef533e",
"245e5943-0092-409d-96ae-e2ee10eac75b"
],
position: 118,
showAtTheTop: false,
size: 2,
type: 1,
excludeFromCopyingOrCloning: true,
lockedForManualEditing: true
});
}
const qrSvg = generateInvoiceQrSvg(base64qrcode);
const qrBase64 = btoa(qrSvg);
const blobResponse = await makeManagerApiRequest('/api3/blobs', 'POST', {
name: `${invoiceKey}`,
contentType: "image/svg+xml",
content: qrBase64
});
if (!blobResponse) {
throw new Error('Failed to upload QR code blob: No response received');
}
console.log("blobResponse", blobResponse);
const blobGuid = blobResponse;
console.log("blobGuid", blobGuid);
// update invoice with QR + optional theme toggle in one PATCH
const payload = {
customFields2: {
images: {
["d2e9265a-460e-4a06-83f9-29a523a4d516"]: blobGuid
}
}
};
if (skipThemeTogle) {
isSales ? payload.hasSalesInvoiceCustomTheme = false : payload.hasCreditNoteCustomTheme = false;
}
// comment: determine base API path using invoice type
const apiPath = isSales
? `/api3/sales-invoice-form/${invoiceKey}`
: `/api3/credit-note-form/${invoiceKey}`;
// comment: send patch request with prepared payload
await makeManagerApiRequest(apiPath, 'PATCH', payload);
console.log("QR code updated successfully for invoice:", invoiceKey);
return { success: true, message: 'QR code updated successfully' };
} catch (error) {
let detailedError = new Error(`Manager QR Code Update Failed: ${error.message}`);
detailedError.originalError = error;
detailedError.invoiceKey = invoiceKey;
throw detailedError;
}
}
This includes operations such as creating or updating custom fields using POST or PUT, updating invoice data using PATCH, and other similar actions. All of these methods function properly as long as the request targets a single-object endpoint.



