Step-by-step walkthrough using the Builder and Vibe Code at dev.boss.tech. Real example with QuickBooks.
Complete these quick setup steps before using the Developer Portal.
Create your account on app.boss.tech or download BOSS.Tech.
Set up a company workspace inside BOSS.Tech. This is where everything you build will live.
Only company owners (the person who created the company) can create the webpage.
Select your company, open the hamburger menu in the top right, choose Edit Company, then select Create a Website. We auto-fill your company name, but you can customize it. Hit Next and you're ready to build.
Sign in to dev.boss.tech, choose the correct company workspace in the top right, then go to Dashboard > Web Platform. Save or customize your web path, or connect a custom domain.
Use the same account to access dev.boss.tech and start building.
This section walks you through a real integration setup step by step. Each screenshot shows exactly what you see in the Builder and what to do next.
Go to dev.boss.tech and sign in. You land on the Builder home screen.
| Element | What it does |
|---|---|
| Top nav bar | Main menu. Builder is highlighted in cyan — you are in the right place. |
| "Select a integration" dropdown | Teal-bordered dropdown at top left. Click to pick an existing integration or use the sidebar to create one. |
| Center — "No files found" | File editor is empty until you select or create an integration. |
| Debug | Opens the debug panel for testing action executions. |
| Vibe Coding | AI-assisted code editor for writing Python action scripts. |
| New Integration | Creates a new integration from scratch. Start here. |
| Resources (right) | Quick links to docs: "What is an Integration?" and "What is a MiniApp?" |
Click Builder in the top nav. A dropdown shows all available modules.
| Module | Purpose |
|---|---|
| MiniApp | Build interactive UIs using BOSS Blocks and JSON schemas. |
| MiniApp Services | Backend services that normalize data across integrations. |
| BOSSi Integrations | Build third-party connectors. Select this one. |
| Flows | Automated workflows chaining actions across integrations. |
| Insights | Analytics and reporting. |
| Vibe | AI-assisted development environment. |
Click the "Select a integration" dropdown in the top left of the sidebar. A list of your existing integrations appears. Select quickbooks.
Once selected, the main panel loads the General Settings page for that integration.
| Element | What it does |
|---|---|
| Integration dropdown | Lists all integrations in your account. Here you can see newbt, operators, quickbooks, stripe, stripebt. Select the one you want to edit. |
| Friendly Name | The display name users see when connecting. Set to QuickBooks. |
| Integration Domain | The unique reverse-domain identifier: tech.boss.quickbooks. This cannot be
changed after creation. |
| Icon URL | The integration's icon. Points to an uploaded asset:
tech.boss.quickbooks.png. The QuickBooks logo appears as a preview.
|
| Public toggle | When enabled (cyan), the integration is visible and available for companies to connect. |
| Left sidebar | Navigation for this integration: General, User Types, OAuth, Schema, and the file tree for action code. |
Click User Types in the left sidebar. This screen defines which user roles can trigger actions in this integration.
| Element | What it does |
|---|---|
| Role list | Each row is a user role. Click the expand icon on the right to see and edit which actions that role can trigger. |
| owner | Company owner. Bypasses restrictions — always has full access regardless of what you configure here. |
| employee, standardNoAccess, etc. | QuickBooks-specific roles mapped from the service. Each one can be assigned a different set of allowed actions. |
| companyAdmin | Administrative role with broader access. |
| bt_customer-user-type-dropdown | Internal BOSS.Tech role for platform-level operations. |
| + Add new | Add a custom role if the defaults don't cover your needs. |
Click OAuth in the left sidebar. This is where you configure how companies authenticate with the external service. The Basic Configuration tab is selected by default.
| Field | What it does |
|---|---|
| Integration Name | Display name: quickbooks. Shown to users during the connection flow. |
| Base URL | The OAuth authorization URL: https://appcenter.intuit.com/connect/oauth2.
This is where users get redirected to grant access. |
| Client Id / Client Secret | Your OAuth credentials from the QuickBooks developer portal. These authenticate your app with Intuit's OAuth server. |
| OAuth Required toggle | When off, shows "Not an OAuth" — meaning API key auth. For QuickBooks this stays
off in the UI but the OAuth flow is configured via the fields above.
The toggle controls the isNonOauth flag.
|
| Parameters (JSON editor) | OAuth query parameters sent during the authorization redirect. Contains
client_id, response_type: "code",
scope: "com.intuit.quickbooks.accounting",
redirect_uri: "{{generic_callback}}", and
state: "{{company_id}}".
|
Scroll down on the same OAuth page to see the token configuration and webhook settings.
| Field | What it does |
|---|---|
| Required Query Parameters | Fields the user must provide when connecting. Empty for QuickBooks since OAuth handles everything automatically. |
| Token URL | https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer — the endpoint
BOSS.Tech calls to exchange the authorization code for access and refresh tokens. |
| Tokens (JSON editor) | Declares which fields to store from the OAuth response. QuickBooks stores:
realmId (the company identifier in QuickBooks),
x_refresh_token_expires_in, access_token, and
refresh_token. Each uses {{variable}} syntax to capture the
value from the OAuth response.
|
| Generate Webhook (toggle) | Enabled (cyan). Creates a unique inbound webhook URL for this integration instance. Shows "Webhook configuration generated" when active. |
realmId field is
QuickBooks-specific — it identifies which QuickBooks company the user connected. Your action code
accesses it as integration_variables["realmId"]. The Generate Webhook
toggle at the bottom creates a unique inbound URL for this integration instance.Click the Trigger Steps tab at the top of the OAuth Setup page. This defines what happens immediately after a user successfully connects.
| Element | What it does |
|---|---|
| Action dropdown | Set to quickbooks-sync-v1. This is the action that runs automatically right
after the user completes the OAuth flow. |
| Payload (JSON editor) | Contains {} — an empty object. The sync action doesn't need any input data
for the initial run; it pulls everything. |
| + Add Step | You can chain multiple actions to run after connection. For example, sync contacts first, then sync invoices. |
| Delete icon (trash) | Remove this trigger step. |
quickbooks-sync-v1 fetches all accounting data on first connect.
Click Schema in the left sidebar. The Basic Configuration tab shows the integration's identity, variables, and security settings.
| Field | What it does |
|---|---|
| Integration Name / Version | Quickbooks Generic Integration version 1.0.0. Metadata for
identification. |
| Description | Human-readable description of the integration. |
| Variables (JSON editor) | Static values available in all actions via variables["url"]. Here it
defines the QuickBooks API base URL:
https://quickbooks.api.intuit.com/v3/company/{{realmId}}/reports. Notice
{{realmId}} — this gets replaced at runtime with the value stored during
OAuth.
|
| Security (JSON editor) | Configures how BOSS.Tech authenticates API requests. Shows type: "bearer",
token: "{{access_token}}",
accessTokenVariable: "access_token",
expiryInVariable: "expires_in". BOSS.Tech uses this to automatically attach
auth headers and refresh tokens when they expire.
|
{{variable}}
placeholder in the URL gets resolved at runtime from stored tokens. Here, {{realmId}} is
the QuickBooks company ID that was captured during OAuth and stored in the tokens block.Click the Actions tab. This shows every operation the integration supports.
| Element | What it does |
|---|---|
| Search bar | Filter actions by name. Useful when you have many actions. |
| Action list | Each row is a declared action: fetch-books-profit-and-loss-v1,
fetch-books-cashflow-v1, fetch-books-balance-sheet-v1, etc.
Click the expand icon to configure.
|
| + Add Action | Create a new action. You name it, set its flags, and point it to a code step. |
Expand an action to see its configuration. Here is quickbooks-sync-v1 — the sync action that
runs after OAuth and orchestrates the initial data pull.
| Field | What it does |
|---|---|
| Action Name | quickbooks-sync-v1 — the action triggered by the OAuth trigger step. |
| Send data to backend (skipParser: true) | Enabled. This action does NOT send its own output through field
mapping. Instead, it calls other actions internally using send_to_trigger()
— those child actions handle their own data mapping. |
| Sends data to app (returnsData: false) | Disabled. This action doesn't return data to a synchronous caller. It's a background orchestrator. |
| Code Reference | quickbooks-sync-v1 — points to the Python file at
action/quickbooks-sync-v1/code/quickbooks-sync-v1.py.
|
| Continue on Failure | Disabled. If this step fails, execution stops. |
| Rate Limit | Period: 0, Limit: 0 — no rate limiting on this action. |
invoices-post-v1
using send_to_trigger(), and those child actions handle their own field mapping and data
storage.Here is how the sync action calls child actions internally:
| Line | What it does |
|---|---|
for invoice in invoices: |
Loops through each invoice fetched from QuickBooks. |
logging.info(f"[QB] Sending Invoice → {invoice.get('Id')}") |
Logs each invoice being processed. Visible in the Builder debug panel. |
send_to_trigger("invoices-post-v1", {"body": {"Invoice": invoice}}) |
Queues invoices-post-v1 as a child action for each invoice. Each runs
independently after the sync finishes. |
except Exception as ex: |
Error handling — logs failures without stopping the loop. |
Now look at invoices-post-v1 — one of the child actions called by the sync. Notice the
different flag configuration.
| Field | What it does |
|---|---|
| Action Name | invoices-post-v1 — receives individual invoice data from the sync
orchestrator. |
| Send data to backend (skipParser: false) | Disabled. This action's output DOES go through field mapping. The data gets mapped and stored in BOSS.Tech. |
| Sends data to app (returnsData: false) | Disabled. Data goes to storage, not returned to a caller. |
In the left sidebar, expand the quickbooks folder under the integration name. This shows the complete file structure of the integration.
| Item | What it is |
|---|---|
| action/ folder | Contains all action folders. Each action has its own subfolder with code and an optional schema.json for field mapping. |
| assets/ folder | Static assets like the integration icon (e.g., the QuickBooks logo). |
| oauth.json | Connection flow configuration (what we set up in Steps 5-7). |
| schema.json | Root schema with actions, variables, and security (Steps 8-9). |
| usertypes.json | Role-based permissions (Step 4). |
| webhook.json | Inbound event routing configuration. |
| Three-dot menu (...) | Options for the folder: Merge and Delete. |
| + button | Create a new file or folder inside. |
| Rocket icon | Deploy this file or folder to the current branch. |
Expand the action/ folder to see all action subfolders. Each one corresponds to an action declared in schema.json.
Each action folder (e.g., bills-post-v1, bulk-fetch-files-v1,
delete-integrations-v1) contains the code and configuration for that specific action.
Expand an individual action to see its internal structure:
| Item | What it is |
|---|---|
| code/ folder | Contains the Python script(s) for this action. The file name matches the
codeRef in schema.json.
|
| bills-post-v1.py | The Python script with the main(storage) function. This is where the action
logic lives. |
| schema.json | The action-level field mapping schema. Defines how output fields map to BOSS.Tech's data
model (the equivalentName JSONPath expressions). |
| + menu | "Add new file inside" or "Add new folder inside" — for adding additional code steps or resources. |
Once your code is ready, you have two operations available via the rocket icon and the three-dot menu:
Click the rocket icon next to any file or folder to deploy it to the current branch.
| Element | What it does |
|---|---|
| Files list | Shows exactly which files will be deployed. Here: bills-post-v1/schema.json
and bills-post-v1/code/bills-post-v1.py. |
| Branch | Deploys to the development branch. Changes become visible to everyone using the app on that branch. |
| Deploy button | Confirms and publishes the changes. |
Use Merge to promote changes from one branch to another (e.g., development to production).
| Element | What it does |
|---|---|
| Source Branch | Where the changes come from: development. |
| Target Branch | Where the changes go: production. The source fully overwrites matching files in the target. |
| Files list | Shows which files will be merged. Here:
quickbooks/action/bills-post-v1/code/bills-post-v1.py.
|
| Merge button | Confirms and executes the merge. |
Vibe Code is the AI-assisted development interface inside the Builder. You describe what you want in plain English, and the AI generates production-ready code by reading BOSSi's documentation and the external service's public API docs. No manual research needed.
Describe what you need in natural language. The AI reads BOSSi docs + the external API docs automatically.
The AI returns complete, ready-to-use configuration or Python code with explanations.
Paste the generated code into the corresponding file in the Builder file tree (oauth.json, action.py, etc.).
Hit Save, then click the rocket icon to deploy. Changes go live immediately.
Use the Debug panel to test your action, see real-time logs, and fix any issues.
If something fails, go back to Vibe Code, describe the fix, paste, deploy, debug again.
Click Vibe Coding in the left sidebar. The Vibe Code interface opens with a prompt field asking "What are we building?"
| Element | What it does |
|---|---|
| Prompt field | Type what you want to build in plain English. Example:
create an OAuth file for Stripe with a hardcoded API key
|
| "Not using your files" toggle | When off, the AI generates code from scratch. When on, it reads your existing integration files for context. |
| Send button (purple arrow) | Submits your prompt. The AI processes it and returns generated code. |
After submitting your prompt, the AI returns complete code with explanations.
In this example, the prompt was: "create an OAuth file for Stripe with a hardcoded API key"
The AI generated:
{
"name": "Stripe",
"services": ["payments"],
"isNonOauth": true,
"tokens": {
"apiKey": "sk_live_abc123..."
},
"genWebhook": true,
"url": "{{self_url}}",
"parameters": {
"self_url": "{{generic_callback}}"
},
"requiredQueryParams": [],
"triggerSteps": [
{
"action": "sync-payments-v1",
"payload": { "full_sync": true }
}
]
}
| What the AI decided | Why |
|---|---|
isNonOauth: true |
You asked for a hardcoded API key, so it skipped the OAuth flow entirely. |
tokens.apiKey hardcoded |
No {{placeholder}} — the key is baked in. No user input needed at
connection time. |
requiredQueryParams: [] |
Empty — since the key is hardcoded, no user prompt is shown. |
genWebhook: true |
Stripe needs a webhook URL to push payment events. |
triggerSteps calls sync-payments-v1 |
After connecting, immediately sync existing payment data. |
The AI also provided follow-up suggestions:
requiredQueryParams: ["apiKey"]payment_intent.succeeded): create a
webhook.json
oauth.json file in the Builder file
tree, replace the placeholder API key with your real one, save, and deploy.Click Debug in the left sidebar. This is where you test actions and see real-time execution logs.
| Element | What it does |
|---|---|
| Integration selector | Choose which connected integration to test. Here: tech.boss.stripebt[0] —
the first Stripe instance. |
| Action dropdown | Select the action to run. Here: stripe-sync-v1. |
| Connected status | Green "Connected" badge confirms the OAuth/API key connection is active. |
| Company ID / Person ID / Integration UUID | Context identifiers for this test run. The copy buttons let you grab these for debugging. |
| Payload Configuration | JSON editor for the action's input data. For a sync action, this is typically empty
({"passthrough": {}, "payload": {}}). For other actions like
post-products-v1, you'd include the required fields.
|
| Run button | Executes the selected action with the given payload. |
post-products-v1 requires specific fields:{
"payload": {
"name": "Hustle Plan",
"description": "Monthly subscription plan",
"sku": "HUSTLE-001",
"type": "subscription",
"isPublic": true,
"imageUrl": "https://example.com/hustle.png",
"productExternalId": "prod_stripe_123"
}
}
After clicking Run, the Log Events panel shows real-time execution results.
After clicking Run, the Log Events panel shows real-time execution results. See Step 19 below for detailed log examples.
| Column | What it shows |
|---|---|
| Action | The action that was executed. Click the expand arrow to see full details. |
| Time Stamp | When the action ran. |
| Message | Result summary. Success messages or error details. |
| Code | HTTP status code. 200 (green) = success. 500 (red) = server error with details. |
| Correlation Id | Unique ID linking all executions in this request chain. Useful for tracing multi-step actions. |
After clicking Run, the log events stream in real time. Here is a successful
stripe-sync-v1 execution:
| Log entry | What it means |
|---|---|
DEBUG: Starting execution of action: stripe-sync-v1 |
The action has been triggered and is starting. |
DEBUG: Starting step 1 of 1 |
Executing the first (and only) code step. |
DEBUG: Starting code execution step |
The Python sandbox is running your code. |
DEBUG: Sending to trigger service |
Your code called send_to_trigger() to queue a child action. |
INFO: *---STARTING STRIPE SYNC for CUSTOMERS---* |
A logging.info() call from your code. Custom messages you write appear
here. |
INFO: Fetched 100 customers, total so far: 100 |
Pagination progress — the sync is pulling data in batches. |
INFO: Total customers fetched: 386 |
Final count. The sync completed successfully. |
Click the arrow on any log row to see the full detail. There are different types of log entries:
Shows your custom log messages with full context about the execution:
| Field | What it shows |
|---|---|
result.code |
200 — the log was recorded successfully. |
result.message |
Your logging.info() message. |
data.action |
Which action produced this log. |
data.integration_id |
The full integration identifier (e.g., tech.boss.stripebt[0]). |
data.debug_type |
"log" — this is a logging statement from your code. |
data.level |
"INFO" — the log level. |
When your code calls send_to_trigger(), the debug panel shows the full payload being sent to
the child action:
This is useful for verifying that the data being passed to child actions is correct. You can see the complete payload including all fields from the external service.
When an action fails, the log shows a red 500 status code with the error details:
| Field | What it shows |
|---|---|
result.code: 500 |
The action failed. |
result.message |
The error message: "[ERROR] Invalid or missing name data". |
data.debug_type: "action_error" |
This is an action-level error, not just a log message. |
Add logging statements to your action code to track execution and debug issues:
# INFO — track progress and print variables
logging.info(f"*-----------STARTING STRIPE SYNC for {resource.upper()}-----------*")
logging.info(f"Fetched {len(customers)} customers, total so far: {total}")
# ERROR — log failures with response details
logging.error(f"[ERROR] Failed to create product: {response.data.decode('utf-8')}")
# DEBUG — verbose details for troubleshooting
logging.debug(f"Raw API response: {data}")
logging.info() for general progress tracking, logging.error() for failures,
and logging.debug() for detailed data inspection. You can print variables, response bodies,
and any data you need to verify.
Actions are the operations your integration performs. Each one is a Python script running in a managed sandbox — no packages to install, no servers to configure. Write, save, done.
Every script defines a main(storage) function. Return a dict to pass data to the next step
or back to the caller.
def main(storage):
api_key = integration_variables["apiKey"]
base_url = variables["baseUrl"]
page = payload.get("page", 1)
response = http.request(
"GET",
f"{base_url}/contacts?page={page}",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
)
if response.status == 429:
retry_after = int(response.headers.get("Retry-After", 60))
schedule_task(retry_after, retries=3)
return {"error": f"Rate limited. Retry in {retry_after}s"}
if response.status != 200:
return {"error": f"API error: {response.status}"}
data = json.loads(response.data.decode("utf-8"))
contacts = data.get("contacts", [])
if contacts:
send_to_parser("sync-contacts-v1", contacts)
if data.get("has_more"):
send_to_trigger("sync-contacts-v1", {"page": page + 1})
return {"synced": len(contacts)}
| Function | What it does |
|---|---|
http.request(method, url, headers, body) |
Make HTTP calls. Pre-loaded, no import needed. |
send_to_parser(action, data) |
Send records through field mapping into BOSS.Tech. |
send_to_trigger(action, payload) |
Queue the next execution (pagination, fan-out). |
schedule_task(seconds, retries) |
Retry the action after a delay (rate limits). |
modify_tokens(tokens) |
Persist state between runs. Overwrites all tokens — read first, update, write back. |
get_token() |
Get current OAuth access token. Auto-refreshes if expired. |
payload |
Dict with data sent by the trigger. |
integration_variables |
Dict with stored credentials (API keys, tokens). |
variables |
Dict with static values from schema.json. |
def main(storage):
api_key = integration_variables["apiKey"]
contact = {
"first_name": payload.get("firstName"),
"last_name": payload.get("lastName"),
"email": payload.get("email"),
}
response = http.request(
"POST", f"{variables['baseUrl']}/contacts",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
body=json.dumps(contact).encode("utf-8"),
)
if response.status not in (200, 201):
return {"error": f"Failed: {response.status}"}
created = json.loads(response.data.decode("utf-8"))
return {"id": created["id"]}
httpurllib3 PoolManager for HTTP requests
jsonJSON encode/decode
loggingLog output visible in Builder debug panel
datetime.datetimeDate and time utilities (accessed as datetime.datetime, not datetime
alone)
uuidUUID generation
urlparseURL parsing utilities
import
statements. Everything listed above is pre-loaded as globals.Detailed reference for each configuration file. Expand the sections you need.
Defines how a company connects to your integration: authentication method, credentials storage, and post-connection actions.
{
"name": "Northwind CRM",
"services": ["contacts"],
"isNonOauth": true,
"genWebhook": true,
"tokens": { "apiKey": "{{apiKey}}" },
"url": "{{self_url}}",
"parameters": { "self_url": "{{generic_callback}}" },
"requiredQueryParams": ["apiKey"],
"triggerSteps": [
{ "action": "sync-contacts-v1", "payload": { "full_sync": true } }
]
}
| Field | Description |
|---|---|
isNonOauth |
Set true for API key auth (no OAuth redirect) |
tokens |
Credentials stored on connect. Available as integration_variables |
requiredQueryParams |
Fields the user must fill in when connecting |
genWebhook |
Generate a unique inbound webhook URL |
triggerSteps |
Actions that run immediately after connecting |
services |
Data routing: contacts, calendar, messages,
payments, tickets, ai
|
Declares every operation your integration supports and how it authenticates with the external API.
{
"name": "Northwind CRM",
"version": "1.0.0",
"variables": { "baseUrl": "https://api.example.com/v1" },
"actions": {
"sync-contacts-v1": {
"skipParser": false,
"returnsData": true,
"steps": [{ "codeRef": "action" }]
}
},
"deleteAction": { "action": "delete-integrations-v1", "payload": {} }
}
| Field | Description |
|---|---|
skipParser |
false: output goes through field mapping. true: skip
mapping. |
returnsData |
true: return value sent to synchronous caller. |
rate_limit |
{"limit": 500, "period": 60} — per integration instance. |
deleteAction |
Runs before integration removal. Clean up webhooks here. |
Routes incoming push notifications from the external service to the right action.
{
"validations": { "body_required": ["event_type"], "headers_required": [] },
"triggerSteps": [{
"condition": "'{{event_type}}' == 'contact.updated'",
"memory": [{ "key": "cid", "value": "str(body['data']['id'])" }],
"action": "sync-contacts-v1",
"payload": { "contact_id": "{{cid}}" }
}]
}
| Field | Description |
|---|---|
condition |
Python expression. null = always run. |
memory |
Compute values from body/headers for use in payload. |
action |
Action to trigger. |
Maps fields from the external service's response to BOSS.Tech's data model. Lives at
action/{name}/schema.json.
{
"version": "1.0",
"data": {
"integrationPersonId": { "dataType": "string", "required": true, "equivalentName": "$.id" },
"firstName": { "dataType": "string", "required": false, "equivalentName": "$.first_name" },
"email": { "dataType": "string", "required": false, "equivalentName": "$.email" }
}
}
equivalentName is a JSONPath expression. $.id extracts the
id field from each record.
Restricts which actions each role can trigger. Optional — without it, all roles can trigger all actions.
{
"owner": ["sync-contacts-v1", "delete-integrations-v1"],
"admin": ["sync-contacts-v1"],
"agent": ["sync-contacts-v1"],
"contributor": []
}
Account owners bypass this file entirely.
| Global | Type | Description |
|---|---|---|
payload |
dict | Data sent with the trigger |
storage |
dict | Shared across steps in one action run |
variables |
dict | Static values from schema.json |
integration_variables |
dict | Stored credentials and tokens |
all_variables |
dict | All sources merged |
| Global | Description |
|---|---|
action |
Current action name |
entity_id / company_id |
Company's entity ID |
person_id |
User who triggered the action |
integration |
Full identifier, e.g. tech.boss.quickbooks[0] |
webhook_url |
Base inbound webhook URL |
| Function | Description |
|---|---|
get_token() |
Current access token, auto-refreshes |
schedule_task(sec, retries) |
Reschedule after delay |
send_to_trigger(action, payload) |
Queue new action execution |
send_to_parser(action, data) |
Send data through field mapping |
modify_tokens(dict) |
Replace all stored credentials |
create_cron_job(...) |
Create scheduled cron |
dispatch_command(action, payload, passthrough) |
Call another integration action |
b64(string) |
Base64 encode |
No. The sandbox blocks all imports. Everything you need is pre-loaded: http,
json, datetime.datetime, logging, uuid,
urlparse, b64().
Use modify_tokens(). Always read integration_variables first, update the copy,
then write back. It overwrites everything.
send_to_trigger() queues a new action run (pagination, fan-out).
send_to_parser() sends data through field mapping into BOSS.Tech storage.
Check response.status == 429, then call schedule_task(seconds, retries=3). You
can also set rate_limit on the action in schema.json.
Define refreshTokenUrl in schema.json's security block. In code, just call
get_token() — it refreshes automatically.
Use logging.info(), logging.error() in your code. Output shows in the Logs
panel at dev.boss.tech.
The action BOSS.Tech runs before removing an integration. Without it, webhooks you registered stay active forever after disconnect.