SDK Reference
The qbash SDK is available inside every task. It reads inputs, calls prompts and connected services, works with files and memory, and returns the task's result. There is no package to install or client to initialize.
This is the company-brief example from Intro and editor, with a log message added:
company = qbash.inputs.get("company")
qbash.log(f"Preparing a brief for {company}")
brief = qbash.ai.run_prompt(
"company-brief",
{"company": company, "notes": qbash.inputs.get("notes", "")},
)
qbash.output({"company": company, "brief": brief})
It requires a company task input and a prompt with the slug company-brief. The prompt uses company and notes placeholders; Prompts shows their configuration. The optional notes task input falls back to an empty string.
The examples below cover working with task files and runtime behavior. The method reference follows, grouped by SDK namespace.
Python scripts
Python scripts in the task are imported through aisle_scripts. For example, a file named company.py could contain:
def normalize_name(value):
return value.strip()
Your main.py can import it by its filename without the extension:
from aisle_scripts import company
name = company.normalize_name(qbash.inputs.get("company"))
brief = qbash.ai.run_prompt(
"company-brief",
{"company": name, "notes": qbash.inputs.get("notes", "")},
)
qbash.output({"company": name, "brief": brief})
Test files use the same imports. See Testing and debugging for a test of this helper.
Task documents
Markdown documents are available through the task's attached memory folder. For a document named briefing-guide, your code can read its content or search for relevant passages:
guide = qbash.memories.get("briefing-guide")
instructions = guide["content"]
matches = qbash.memories.search("company brief format")
The returned document contains its content and metadata; it is not just a text string. qbash.memories describes the returned fields and search options. You can also pass memory folders to a prompt from Python without reading each document first.
Prompt results
A Text prompt returns a string. A JSON prompt returns the value matching its output schema - for example, a dictionary with summary and questions fields. Those choices apply with or without Agent mode.
An Agent-mode prompt using Text returns its final answer as a string-like value. It also exposes .text, containing text written across its turns, and .files, containing generated files. To keep those files in the task's result, include them explicitly:
company = qbash.inputs.get("company")
brief = qbash.ai.run_prompt(
"company-brief",
{"company": company, "notes": qbash.inputs.get("notes", "")},
)
qbash.output({"company": company, "brief": brief, "files": brief.files})
This version of the example requires the prompt to have Agent mode enabled and Text output selected. With JSON output, read the returned fields instead. run_prompt(...) lists the full set of arguments and return types.
Retries and reused results
A task retry starts main.py again. qbash.checkpoint remembers work across attempts of that run, so a later attempt can reuse a completed result. For example:
company = qbash.inputs.get("company")
notes = qbash.inputs.get("notes", "")
brief = qbash.checkpoint.get_or_set(
"company-brief",
lambda: qbash.ai.run_prompt(
"company-brief",
{"company": company, "notes": notes},
),
)
qbash.output({"company": company, "brief": brief})
Once the brief has been stored, another attempt of the same run uses it. A separate run has its own checkpoints. Use this pattern for a text or JSON result; get_or_set(...) is not suitable for a function that returns None.
For an external write, a checkpoint alone does not guarantee it happens only once. The run could stop after posting a brief but before recording completion. Where supported, send the destination an idempotency key - a stable identifier it uses to recognize a repeated request - and design the operation so retrying does not duplicate the work.
qbash.cache reuses values across separate runs of the same task for a specified time. For example, a cached company lookup might remain useful for an hour. Choose the key and lifetime so a caller does not receive information prepared for different inputs.
qbash.run.last_run_at is the previous successful run time. It can help a scheduled task select recently changed records. If records can arrive late, query a small overlapping period and use record IDs to avoid processing the same update twice.
For several independent company briefs in one run, qbash.parallel(...) calls a function for each item with a limit on simultaneous requests. Its retries and checkpoints also need operations that are safe to repeat. Method options follow under parallel, checkpoint, cache, and run.
qbash
Top-level helpers called directly on the qbash object.
qbash.parallel()Run fn(item) for each item using a producer-consumer pool.
result = qbash.parallel(
fn=process,
items=items,
)
fnCallablerequireditemsIterablerequiredconcurrencyintdefault5max_per_minuteOptional[int]defaultNonecontinue_on_errorbooldefaultFalsecheckpointOptional[str]defaultNonecheckpoint_keyAnydefaultNonestore_resultbooldefaultFalseretryUnion[int, bool, RetryPolicy, None]defaultNone
ParallelResultretry: optional retry policy applied to fn(item) calls.
- retry=3 → up to 3 attempts (2 retries) on transient failures
- retry=1, retry=0, or retry=<negative int> → single call, no retry
- retry=False or None → no retry (single call)
- retry=True → default policy (3 attempts)
- retry=RetryPolicy(...) → full control
Atomicity precondition: fn(item) may be called more than once if retry is enabled OR if checkpoint resume kicks in across runs. Each call's observable side effects MUST be atomic - wrap DB writes in `with db: ...`, or use idempotent statements (INSERT ... ON CONFLICT, MERGE). The SDK does NOT roll back partial state from a failed call.
Returns ParallelResult(completed, skipped, errors, results). Results are in input order.
qbash.create_chat()Create a chat output message for this task run.
result = qbash.create_chat(
title="…",
content={"key": "value"},
)
titlestrrequiredChat thread name.
contentstr | dict | listrequiredAssistant message body (str, or dict/list which will be JSON-stringified).
skip_if_emptybooldefaultFalseIf True and content is falsy, returns None without creating the chat.
fileslist | NonedefaultNoneOptional list of QBash file-reference objects (from qbash.files.write, qbash.inputs, etc.) to attach.
read_onlybooldefaultTrueWhen True (default), the user cannot reply -- the chat renders as a system notification. When False, the user can follow up; requires either model= or a project_id whose project has a default_llm_model.
modelstr | NonedefaultNoneLLM model id to use for the user's follow-up replies. Only meaningful when read_only=False. Required when read_only=False and project_id is not set. Must NOT be passed when project_id is set -- the project's default_llm_model is used so follow-ups behave like any other chat inside that project.
project_idstr | NonedefaultNoneOptional project to associate the chat with. The chat owner must have access to this project. When set, the project's default_llm_model is used for follow-up replies and project tools (MCP, prompts, knowledge bases) are available.
user_idstr | NonedefaultNoneOptional override for the chat owner. Must be a user in the same company as the task with an active membership.
user_emailstr | NonedefaultNoneOptional override for the chat owner by email address - convenient when an external caller knows the user by their account email rather than their QBash user ID. Same validation envelope as user_id (must resolve to a user in the same company with an active membership). If both user_id and user_email are passed, user_id wins.
shared_withlist | NonedefaultNoneOptional list of additional org members to grant access to the new chat. Each entry is either an email string (defaults to read-only / "view") or a dict shaped like {"user_email": "...", "role": "view"} or {"user_id": "<uuid>", "role": "chat"}. Role aliases: "view" = read-only viewer, "chat" = full participant who can reply. Each recipient must be an active member of the task's company. All entries are validated up front - if any fails, no chat is created.
share_with_orgstr | NonedefaultNoneOptionally share the chat with EVERY active member of the task's company. "view" = whole org can read (read-only), "chat" = whole org can read and reply. None (default) = not shared org-wide. Unlike shared_with, this is a single org-level grant - membership is resolved live, so members added later automatically gain access and removed members lose it. Combine with shared_with to give specific people a different role than the org default.
memory_folder_idslist[str] | NonedefaultNoneOptional list of memory-folder UUIDs to expose as vector-search tools on follow-up replies (e.g. the folder the task summarized). Each folder must have AI search enabled and the chat owner must have a memory MCP connection for it. Ignored when read_only=True or project_id is set (a project already supplies its own memory and tool configuration).
system_messagestr | NonedefaultNoneOptional system message to seed follow-up replies with (sets the assistant's tone/role). Same precedence as memory_folder_ids -- ignored when read_only=True or project_id is set.
connectorslist[str] | NonedefaultNoneOptional connectors the chat's replies may use, each a provider ("hubspot"), an account's name or id, or the value of a credential input (qbash.inputs.get("crm")), resolved as qbash.ai.run_prompt(connectors=...) resolves them. Each must be an account the task has: one assigned to it, or the one a credential input carries for this run. A None item is skipped, so an optional credential input left blank passes nothing. Whoever can reply in the chat uses these accounts as the run does, without access of their own, and no other account through the task. Needs read_only=False, and is not allowed with project_id (the project's connectors apply).
messageslist[dict] | NonedefaultNoneOptional conversation to post before content, as a list of {"role": "user" | "assistant", "content": str} dicts in order, e.g. [{"role": "user", "content": question}] for the question the answer replies to. A user turn is posted as the chat owner's. The chat shows these turns, and replies send them to the model with content as its earlier answer. Up to 100 entries, each a non-empty string of at most 512 KB.
dict | NoneChat ownership: by default the chat is owned by whoever pressed Run (qbash.run.user). For runs without a user (schedule, webhook, system triggers) the chat falls back to the task author (qbash.run.creator). Pass user_id= to override and force a specific owner; the user must belong to the same company as the task and have an active membership.
qbash.output()Set this task run's output.
qbash.output(
value="…",
)
valueanyrequiredAny JSON-serializable value.
typestr | NonedefaultNoneOptional page rendering format: "html" or "markdown".
The value can be any JSON-serializable value: None, bool, int, float, str, list, or dict. qbash.output may only be called once per run.
TYPE AND PAGE RENDERING
When this task is bound as the generator for a Project or Site page, every successful run that calls qbash.output publishes the next page edition. The optional type controls how the page is rendered:
* No type: qbash automatically turns the value into a branded page. * type="markdown": renders a Markdown string in the standard page * type="html": publishes a complete HTML document string. Scripts and
- layout. Inline HTML is supported with the same security restrictions
- as HTML output. Rich entities may be authored with fenced blocks such
- as aisle-chart, mermaid, aisle-json, aisle-table, math, and ordinary
- language-tagged code fences.
- navigation metadata are removed, and a strict content-security policy
- blocks external requests. Inline CSS, SVG, and data: images are
- supported. Declarative aisle-chart, aisle-mermaid, aisle-json,
- aisle-table, aisle-math, and aisle-code elements are hydrated by the
- trusted application renderer without enabling page-authored scripts.
HTML and Markdown output require a string. qbash does not infer the type from the value's contents.
qbash.log()Append a message to the task run log.
qbash.log(
message="…",
)
messagestrrequired
qbash.sleep()Pause execution for the given number of seconds (max 900).
qbash.sleep(
seconds="…",
)
secondsanyrequired
qbash.ai
AI tools: raw, run_prompt, the model catalog, and provider-native web and search tools.
qbash.ai.raw()Call an LLM with the given instruction.
result = qbash.ai.raw(
instruction={"key": "value"},
)
instructionstr | dict | listrequiredThe user-facing instruction for the model. A dict or list is sent as JSON text.
fileslist | NonedefaultNoneOptional list of QBash file-reference objects to attach.
systemstr | NonedefaultNoneOptional system message. No system message is sent when omitted.
modelstr | NonedefaultNoneOptional model slug (e.g. "claude-haiku-4-5"). Defaults to "claude-sonnet-4-6" when omitted.
temperaturefloat | NonedefaultNoneOptional sampling temperature (0.0-2.0).
max_tokensint | NonedefaultNoneOptional cap on response tokens.
output_schemadict | NonedefaultNoneOptional JSON Schema dict. When present, the model is constrained to produce JSON matching the schema and this method returns a dict instead of a string. A Jev question set (see below) passed with any other model is refused with an error.
toolslist[dict] | NonedefaultNoneOptional list of native tool providers to attach. Each entry is a dict with: - provider (required, str): integration slug (e.g. "opensearch") - credential (optional, str): credential name or UUID; omit to use the task's default credential for that provider - allowed_tools (optional, list[str]): tool name filter; omit or pass [] for all tools from that provider
cachebooldefaultTrueEnable prompt caching (default True). Caches the system prompt and tool definitions so repeated calls pay ~10% input cost on cache hits (Anthropic). Other providers cache automatically. Pass False for a single LLM call in an infrequently-run task.
keystr | NonedefaultNone
str | dictJev (model="jev-latest"):
- TypeSafe's Jev is a decision model. It reads `instruction` as its
- state and answers typed questions. It never writes text.
- instruction can be a string, or a dict or list for structured
- state, e.g. {"customer": {...}, "transactions": [...]}. A dict
- or list is sent as JSON, and Jev receives it as structured
- state. A string that is exactly a JSON object or array is also
- sent as structured state.
- output_schema is required and holds the TypeSafe question set, - system, tools and files are refused with an error. Put - temperature, max_tokens and cache are ignored. - The return value is the answers dict keyed by question name:
- not a JSON Schema: a dict mapping each question name to a
- question. A JSON Schema is refused with an error.
- {"department": {"type": "choice",
- "instructions": "Which team handles this?",
- "criteria": {"billing": "Payments",
- "technical": "Bugs"}},
- "urgency": {"type": "score",
- "criteria": ["Can wait", "Today", "Blocked"]},
- "is_refund": {"type": "noul",
- "instructions": "Is this a refund request?"}}
- "instructions" is optional on every type. "choice" needs 1-255
- options, "score" needs 2-10 levels from lowest to highest, and
- "noul" (yes or no) takes optional {"true": ..., "false": ...}
- criteria.
- everything Jev should read in `instruction`.
- choice: {"type": "choice", "choice": "technical",
- "confidence": 0.78, "probabilities": {...}}
- score: {"type": "score", "score": 1.4, "confidence": 0.6,
- "legend": {...}, "probabilities": {...}}
- (score is probability-weighted, from 0 to the
- number of levels minus 1)
- noul: {"type": "noul", "noul": 0.93}
- (probability from 0 to 1 that the answer is yes,
- with no confidence field)
The model's text response as a string, or a dict if output_schema was provided.
qbash.ai.run_prompt()Run an inline prompt by slug (or a migrated prompt's old UUID).
result = qbash.ai.run_prompt(
prompt_ref="…",
variables={"key": "value"},
)
prompt_refstr | NonedefaultNoneThe inline prompt's slug, or the old UUID of a saved prompt that was migrated into this task.
variablesdict | NonedefaultNoneTemplate variables as a dict.
fileslist | NonedefaultNoneOptional list of QBash file-reference objects to attach.
slugstr | NonedefaultNoneAlternative keyword form for slug (same as passing a slug as prompt_ref).
cachebooldefaultTrueEnable prompt caching (default True). See ai.raw() for details.
connectorslist[str] | NonedefaultNoneThe connectors this call may use, each a provider ("hubspot", or the name the Connectors page shows, such as "PostgreSQL"), an account's name or id, or the value of a credential input (qbash.inputs.get("crm")), so a task that takes an account as an input hands it to the prompt. Each must be an account bound to the task (a fixed account or a credential input). A None item is skipped, so an optional credential input left blank passes nothing. Works in both modes: a plain prompt gets these on top of the connectors picked on it in the editor.
memorylist[str] | NonedefaultNoneThe memory folders this call may use, by name or id, each bound to the task under Memory. In Agent mode the prompt reads and writes them with its memory tools, at the task's access levels. A plain prompt gets their documents attached, as a folder attached to it in the editor is.
taskslist[str] | NonedefaultNoneAgent mode only. The linked tasks this call may run as tools, by name or id, each linked to the task under Tasks. A plain prompt runs no tasks, so passing tasks= to one fails the call.
modelstr | NonedefaultNoneRun this call on another model, e.g. model="gpt-5". The prompt's own model stays the default for every other call. It must be a model the organization has set up (the ones the prompt's model picker lists), or the call fails. Works for agent-mode prompts too. A template variable named "model" goes in the variables dict: run_prompt("reply", {"model": m}).
str | dict | PromptResultRead the run's inputs with qbash.inputs.get() and pass the accounts the prompt may use. Passing an item the task does not bind fails the call with a message naming where to bind it. The prompt inherits this run's write mode, so in a Test run with writes off its connector writes are not sent.
message = qbash.inputs.get("message") or ""
answer = qbash.ai.run_prompt(
"reply", {"message": message}, connectors=["postgres"]
)
qbash.output(answer)
A prompt set to agent mode in the editor uses tools and takes several steps before it answers: its abilities and subagents, within the turn and time limits it was given, plus only the connectors, memory folders and tasks this call passes. An agent prompt assigns none of its own.
answer = qbash.ai.run_prompt(
"agent",
qbash.inputs.all(),
connectors=["hubspot", qbash.inputs.get("crm")],
memory=["Support notes"],
tasks=["Send invoice"],
)
qbash.output(answer)
While a prompt runs, its text shows as it is written on the run's step wherever the run is watched (the run page, the editor's Test run, a chat's task card). That is progress: the run's output is what the script passes to qbash.output().
The prompt's answer as a str. With a structured_output (JSON) schema, the decoded value instead: a dict, or whatever value the schema describes. An agent-mode prompt without a schema returns a PromptResult, a str holding its final answer, with .text (everything it wrote across its turns) and .files (the files its abilities made). To keep those files in the run's output, pass them along: qbash.output({"text": answer, "files": answer.files}).
qbash.ai.anthropic_web_search()Search the web using Anthropic's built-in search tool.
result = qbash.ai.anthropic_web_search(
query="search terms",
)
querystrrequiredThe search query.
strqbash.ai.anthropic_web_fetch()Fetch and extract the text content of a URL using Anthropic.
result = qbash.ai.anthropic_web_fetch(
url="https://…",
)
urlstrrequiredThe URL to fetch.
strqbash.ai.openai_web_search()Search the web using OpenAI's built-in search tool.
result = qbash.ai.openai_web_search(
query="search terms",
)
querystrrequiredThe search query.
strqbash.ai.gemini_google_search()Search Google using Gemini's grounding tool.
result = qbash.ai.gemini_google_search(
query="search terms",
)
querystrrequiredThe search query.
strqbash.ai.gemini_url_context()Fetch and summarise one or more URLs using Gemini URL context.
result = qbash.ai.gemini_url_context(
urls=[...],
)
urlslist | strrequiredOne URL, or a list of URLs.
strqbash.ai.xai_x_search()Search X (Twitter) using xAI's live search tool.
result = qbash.ai.xai_x_search(
query="search terms",
)
querystrrequiredThe search query.
strqbash.ai.models()List models from Aisle's model catalog.
result = qbash.ai.models(
query="search terms",
provider="…",
)
querystr | NonedefaultNoneWords that must all appear in the id, name, provider or vendor, e.g. "sonnet 4.6".
providerstr | NonedefaultNoneWho serves the model: "anthropic", "openai", "gemini", "x-ai", "typesafe" or "openrouter".
vendorstr | NonedefaultNoneWho made the model, e.g. "anthropic", "google", "meta-llama". Finds that maker's models across providers.
familystr | NonedefaultNoneEvery row that runs one model (rows serving the same model share a family).
modestr | NonedefaultNone"chat", "image_generation" or "decision".
requireslist[str] | NonedefaultNoneCapabilities every model must have: "structured_output", "thinking", "image_input", "pdf_input", "citations", "agent_tasks".
min_contextint | NonedefaultNoneSmallest context window in tokens.
include_deprecatedbooldefaultFalseInclude deprecated models.
configuredbool | NonedefaultTrueTrue (the default) lists only models this company has configured and can run, False only the ones it has not, None the whole catalog.
sortstr | NonedefaultNone"catalog" (the default), "name" or "input_price" (cheapest first, unpriced last).
list[dict]List models from Aisle's model catalog, one summary dict per model. By default only models this company has configured and can run. Pass configured=None for the whole catalog or configured=False for the rest. query words must all appear in the id, name, provider or vendor. provider is who serves the model (anthropic, openai, gemini, x-ai, typesafe, openrouter) and vendor who made it (anthropic, google, meta-llama, ...). mode is "chat", "image_generation" or "decision". requires takes any of "structured_output", "thinking", "image_input", "pdf_input", "citations", "agent_tasks". sort is "catalog" (default), "name" or "input_price". Each dict has id (the value model= takes), name, provider, vendor, family, mode, status, retirement_date, replaced_by, limits {context_tokens, max_output_tokens}, modalities {input, output}, capabilities {structured_output, thinking}, pricing and availability {configured}. pricing is None for a model with no list price, otherwise {input_per_million, output_per_million, cache_read_per_million} in USD, with None for a rate the model doesn't have. Prices are catalog list prices.
qbash.ai.model()The full catalog record for one model id.
result = qbash.ai.model(
model_id="claude-haiku-4-5",
)
model_idstrrequiredCatalog model id, e.g. "claude-sonnet-5-5" or "anthropic/claude-3.5-haiku"
dict | NoneThe full catalog record for one model id, or None for an id the catalog does not have. Adds description, parameters (each with min, max, enum and default where the model declares them), capabilities (structured_output, thinking, thinking_always_on, built_in_tools, citations, image_types, image_max_bytes, document_types), pricing tiers (standard, batch, long_context with threshold_tokens, scheduled_change with effective_date, each a dict of USD rates, beside a currency key that is "USD", or None for an unpriced model), eligible {agent_tasks, trial} and availability {configured, configurations: [{slug, trial}]}.
qbash.chats
Chat history search: search, read, and search within threads.
qbash.chats.search()Search the executing user's chat threads.
result = qbash.chats.search(
query="search terms",
project_id="…",
)
querystrdefault''Search term (matches thread names and message content). Can be empty to browse with filters only.
project_idstr | NonedefaultNoneFilter to threads within a specific project (UUID).
task_idstr | NonedefaultNoneFilter to threads that ran a specific automated task (UUID).
prompt_idstr | NonedefaultNoneFilter to threads that used a specific prompt (UUID).
afterstr | NonedefaultNoneISO 8601 date or datetime. Only threads created after it.
beforestr | NonedefaultNoneISO 8601 date or datetime. Only threads created before it.
limitintdefault10Max results (default 10, max 25).
offsetintdefault0Number of results to skip (default 0). Use with limit for pagination when has_more is true.
dictFull-text search across thread names and message content. Supports optional filters that can be combined freely, and with an empty query to browse by filters alone.
dict with keys: threads (list), count, total, offset, has_more. Each thread has: id, name, project_id, project_name, updated_at, inserted_at, and optionally task_id.
qbash.chats.read()Read messages from a specific chat thread.
result = qbash.chats.read(
thread_id="…",
)
thread_idstrrequiredThe chat thread UUID (from search results).
limitintdefault50Max messages to return (default 50, max 100).
offsetintdefault0Number of messages to skip from the start (default 0). For page 2 of 50, pass offset=50, limit=50.
dictReturns messages in chronological order with pagination support. Only works for threads owned by the executing user.
dict with keys: thread_id, thread_name, project_id, total_messages, offset, limit, returned, has_more, messages. Each message has: id, role, content, model, sent_at.
qbash.chats.search_in_thread()Search for specific content within a single chat thread.
result = qbash.chats.search_in_thread(
thread_id="…",
query="search terms",
)
thread_idstrrequiredThe chat thread UUID.
querystrrequiredSearch term to find within the conversation.
limitintdefault10Max matching messages to return (default 10, max 30).
dictMore efficient than paginating through a long conversation when looking for specific messages. Only works for threads owned by the executing user.
dict with keys: thread_id, thread_name, query, matches, messages. Each message has: id, role, content, model, sent_at.
qbash.tasks
Call another task that this task has explicitly linked as a tool.
qbash.tasks.run()Run a linked task and return its result.
result = qbash.tasks.run(
id_or_slug="my-prompt",
)
id_or_slugstrrequiredThe linked task's UUID, or its exact name (tasks don't have a dedicated slug field - name is matched exactly and only among tasks this task has linked).
inputsdict | NonedefaultNoneInput values for the sub-task, matching its declared input schema. Values are coerced to their declared types (e.g. a numeric input still works if you pass a string).
anyThe sub-task's output value, whatever its ``qbash.output(...)`` call produced, unwrapped (a dict, list, string, number, bool, or None). The call waits for the sub-task to finish wherever it runs: a fast task, an agent, or a long task on Fargate. It waits up to the sub-task's own time limit, at most 29 minutes, and never past the end of this run.
Calls between tasks go one level deep. A task started this way, or from an agent prompt's ``tasks=``, cannot call tasks itself, so a task cannot call itself or the task that called it.
ValueError: if id_or_slug is missing, or inputs is not a dict. SDK error: if the task isn't linked, isn't accessible, or the run
- itself fails (missing required inputs, credential errors,
- script errors, etc.), or if it does not finish in time to
- answer. A sub-task that runs out the wait keeps running, and
- its result is in its run history.
qbash.tasks.list_runs()List a task's recent runs.
result = qbash.tasks.list_runs(
task_id="…",
limit=10,
)
task_idstr | NonedefaultNoneThe automated task UUID
limitintdefault10Max results (default 10, max 50)
dictList recent production runs for a task you may edit. Expired outputs are withheld.
qbash.documents
Document chunking - extraction_plan, extract_unit, split, contextualize_chunk.
qbash.documents.extraction_plan()Split a document into per-page extraction units.
result = qbash.documents.extraction_plan(
file="…",
)
fileanyrequiredThe source document - a QBash file-reference object or bare file_upload_id. PDF, Office, and plain-text formats.
strategystrdefault'hybrid_router'How to get text out of each page. ``"hybrid_router"`` (default) keeps a clean native text layer and falls back to the LLM per page; ``"llm_per_page"`` always uses the LLM; ``"native_text"`` always uses the native layer.
dictConverts the file to PDF if needed, splits it into one file per page, and reads each page's native text layer. Pages whose text layer is already clean come back with ``content`` pre-filled and cost nothing further; pages that need an LLM come back with ``content: None`` - pass those to :meth:`extract_unit`.
Not idempotent: it calls the conversion service and creates a file record per page, so a re-run duplicates them. Checkpoint the result.
dict with keys: units, unit_count, needs_llm. Each unit is {seq, page, content} - ``page`` is a file blob for :meth:`extract_unit` and ``content`` is the extracted text, or None when the page still needs an LLM pass. ``needs_llm`` is how many units have ``content: None``.
qbash.documents.extract_unit()Extract one page's text with an LLM.
result = qbash.documents.extract_unit(
page="…",
)
pageanyrequiredThe page file reference from a plan unit's ``page`` field.
dictOne LLM call - a natural ``qbash.parallel`` unit. Only worth calling for units :meth:`extraction_plan` returned with ``content: None``.
dict with key: text.
qbash.documents.split()Split text into chunks and group them into contextualization windows.
result = qbash.documents.split(
text="…",
ref="…",
)
textstr | NonedefaultNoneThe document text to split. Exactly one of ``text`` or ``ref`` is required.
refanydefaultNoneA file reference to read the text from instead of passing it inline. Exactly one of ``text`` or ``ref`` is required.
doc_typestrdefault'text'``"text"`` (default) splits on paragraph and heading boundaries; ``"csv"`` converts to a markdown table, keeps rows intact, and repeats the header block in every chunk.
dictDeterministic and cheap - re-run it instead of checkpointing it.
The chunks are persisted as an artifact and the response carries only a handle to it: **no chunk text and no window text come back**. Pass ``chunks_ref`` plus a chunk's ``seq`` to :meth:`contextualize_chunk` and the server resolves that chunk's window itself.
Chunk size and window size are platform settings, tuned server-side and not exposed here.
dict with keys: chunks_ref, chunks, chunk_count, window_count. ``chunks`` is [{seq, window}] - sequence numbers and window indexes only, no text.
ValueError: neither ``text`` nor ``ref`` supplied, or both.
qbash.documents.contextualize_chunk()Prepend window context to one chunk with an LLM.
result = qbash.documents.contextualize_chunk(
chunks_ref="…",
seq=0,
)
chunks_refanyrequiredThe ``chunks_ref`` handle from :meth:`split`.
seqintrequiredWhich chunk to contextualize - a ``seq`` from ``split``'s ``chunks`` list.
dictOne LLM call - a natural ``qbash.parallel`` unit. The window text is never sent over the wire: the server loads the chunks artifact and rebuilds the window this ``seq`` belongs to.
**Warm each window's cache before fanning out over it.** The window is prompt-cached, but the cache entry only becomes readable once a first response starts streaming, and it expires 5 minutes later. So per window: call this once, sequentially, for that window's first chunk, and only then ``qbash.parallel`` over the rest of that window. Warming every window up front instead means the early windows expire before the fan-out reaches them and every call pays full price.
dict with keys: seq, text, cached. ``text`` is the generated context followed by the chunk itself. ``cached`` is whether this call read the window from the prompt cache.
qbash.memories
Memory operations - search, store, get, update, query, list, propositions.
qbash.memories.search()Case-insensitive keyword search over memory records without using
result = qbash.memories.search(
query="search terms",
)
querystrrequiredlimitintdefault5folderstr | NonedefaultNonefolder_idstr | NonedefaultNone
listembeddings. Searches record names, attached filenames and metadata, plus body content for Markdown documents. Uploaded files are not searched by extracted body text. Omitting the folder searches this task's inline Markdown files.
Returns whole records with id, name, content, metadata, and files, without similarity scores. Only accepted memory content is searched; pending pull requests are not included. Use vector_search() for semantic matching and similarity scores.
qbash.memories.get()Fetch a memory record by name, or an inline file by stable slug
result = qbash.memories.get(
name="…",
)
namestrrequiredfolderstr | NonedefaultNonefolder_idstr | NonedefaultNone
dictwhen the folder is omitted.
Returns a dict with keys id, name, content, metadata, files. ``files`` is a list of file-reference blobs for uploaded documents (empty for Markdown records); pass one to ``qbash.files.read(blob, encoding="bytes")`` to get the original bytes (e.g. a PDF form template). Reads return the last accepted version while a PR is pending.
qbash.memories.store()Create a memory record.
result = qbash.memories.store(
name="…",
)
namestrrequiredcontentstr | NonedefaultNonemetadatadict | NonedefaultNonefiledict | NonedefaultNoneawait_embeddingbooldefaultFalseembedding_timeoutint | NonedefaultNonefolderstr | NonedefaultNonefolder_idstr | NonedefaultNone
dictText creation follows the folder's review setting and any document override. Both are set in the memory UI and apply to every writer, not only agents. They are not store() arguments. See Reviewing memory changes.
A protected text create returns {"id": None, "status": "pending_review", "pull_request_id": "..."}. No live document exists until acceptance, so do not immediately fetch/update it or claim it was published. Pending writes return immediately even with await_embedding=True. File writes to review-required folders fail explicitly: attachment staging is not supported. Text writes open proposals automatically when review is required.
Exactly one of ``content`` or ``file`` must be provided.
- - content: inline text (the doc's ``content`` field is set directly).
- - file: a QBash file-reference object (as returned by
- ``qbash.files.write`` or a Drive ``download_files`` call).
- The memories pipeline extracts text from the file and
- stores it as content.
When ``await_embedding=True``, blocks until the doc's embedding is generated (or fails), polling every 15s. ``embedding_timeout`` seconds caps the wait; ``None`` polls indefinitely (Lambda runtime is the cap). Raises :class:`EmbeddingFailedError` if the doc reaches a terminal failed state; raises :class:`TimeoutError` if the timeout elapses without reaching a terminal status.
qbash.memories.archive()Mark a memory as archived. Idempotent - archiving an
result = qbash.memories.archive(
memory_id="…",
)
memory_idstrrequiredfolderstr | NonedefaultNonefolder_idstr | NonedefaultNone
dictalready-archived memory is a no-op success. Archived memories are excluded by default from vector_search, find_memory, query_memories, list_records, and get_propositions on the agent-facing MCP path. Review-required writes return pending_review and pull_request_id instead of archiving immediately; reads keep serving the accepted status until acceptance. Already-archived memories are a no-op success. There is no retry idempotency for pending proposals; repeated calls may open more PRs.
qbash.memories.unarchive()Restore an archived memory to active status. Idempotent.
result = qbash.memories.unarchive(
memory_id="…",
)
memory_idstrrequiredfolderstr | NonedefaultNonefolder_idstr | NonedefaultNone
dictEmbeddings are preserved across archive/unarchive - restored memories become searchable after the change is applied. Review-required writes return pending_review and pull_request_id; the accepted archived status remains in effect until approval. Repeated pending submissions are not deduplicated.
qbash.memories.await_embedding()Block until the memory's embeddings finish (or fail).
result = qbash.memories.await_embedding(
memory_id="…",
)
memory_idstrrequiredtimeoutint | NonedefaultNonefolderstr | NonedefaultNonefolder_idstr | NonedefaultNone
dictPolls every 15 seconds - background-task cadence, no need for tight polling. Returns the doc's status fields when ``embedding_status`` reaches ``"completed"``. Raises :class:`EmbeddingFailedError` when status is ``"failed"``. Treats ``None``/``"pending"``/``"processing"`` as "keep waiting". ``timeout=None`` polls indefinitely.
qbash.memories.vector_search()Semantic search returning [{id, name, content, similarity}].
result = qbash.memories.vector_search(
query="search terms",
)
querystrrequiredlimitintdefault5thresholdfloatdefault0.3folderstr | NonedefaultNonefolder_idstr | NonedefaultNone
listOmitting the folder searches this task's inline Markdown files.
qbash.memories.update()Update an existing memory record by ID.
result = qbash.memories.update(
memory_id="…",
content="…",
)
memory_idstrrequiredcontentstrrequiredmetadatadict | NonedefaultNonemergebooldefaultTruefolderstr | NonedefaultNonefolder_idstr | NonedefaultNone
dictThe folder's review setting and any document override decide whether this text write requires review. See Reviewing memory changes for policy settings. A protected update returns status="pending_review" and pull_request_id; it preserves the live ID and last accepted content/metadata. Reads and search continue using that accepted version until approval. Check status before reporting success as published. Review follows the write policy automatically. Omitting metadata preserves it. With merge=False, an explicit metadata dict replaces it (use {} to clear it).
qbash.memories.query()Filter records by name or metadata key-value pairs. Omitting the
result = qbash.memories.query(
name="…",
metadata={"key": "value"},
)
namestr | NonedefaultNonemetadatadict | NonedefaultNonefolderstr | NonedefaultNonefolder_idstr | NonedefaultNone
listfolder queries this task's inline Markdown files. Filters match exact JSON values and types: numbers, booleans, strings, objects, arrays, and null. A numeric 2 differs from the string "2". Only published versions participate; pending proposals are excluded.
qbash.memories.list_all()List memory records (lightweight: id, name, created_at, metadata,
result = qbash.memories.list_all(
folder="my-folder",
folder_id="my-folder",
)
folderstr | NonedefaultNonefolder_idstr | NonedefaultNone
listfiles). ``files`` is always present - a list of file-reference blobs for uploaded documents, empty for Markdown records. Omitting the folder lists this task's inline Markdown files.
qbash.memories.get_propositions()Get semantic proposition chunks for a record. Omitting the folder
result = qbash.memories.get_propositions(
memory_id="…",
)
memory_idstrrequiredfolderstr | NonedefaultNonefolder_idstr | NonedefaultNone
listtargets this task's inline Markdown files.
qbash.http
HTTP tool - outbound requests proxied through Elixir for URL validation.
qbash.http.request()Make an HTTP request.
result = qbash.http.request(
method="…",
url="https://…",
)
methodstrrequiredHTTP method, e.g. "GET" or "POST".
urlstrrequiredAbsolute http(s) URL.
headersdict | list | NonedefaultNoneOptional request headers: a dict, or a list of name and value pairs.
bodydict | list | str | NonedefaultNoneOptional request body. A dict is sent as JSON.
authdict | NonedefaultNoneOptional auth dict built with qbash.http.auth.*.
follow_redirectsbooldefaultTrueFollow 3xx redirects. Pass False to receive the redirect response itself (status, Location header).
timeoutint | NonedefaultNoneRequest timeout in milliseconds (default: 30000).
dictMake an HTTP request. Returns {status, headers, body}. Redirects (301/302/303/307/308) are followed by default, up to 5 hops, each validated like the first request. A public URL that redirects (e.g. a Google Sheets `/export?format=csv` link) works with a plain GET; no integration credential is needed. 301/302/307/308 keep the method and body; only 303 becomes a GET. On a redirect to a different origin, auth headers are dropped and a body containing a secret ref is not replayed (the call errors). Pass `follow_redirects=False` to get the 3xx response itself. `timeout` is in milliseconds (default 30000). Responses over 50 MB are refused. Authentication: never paste tokens into code. Bind a secret to the task and pass `auth=`: `qbash.http.request("GET", url, auth=qbash.http.auth.bearer(qbash.secrets["API_KEY"]))`. A secret ref also works anywhere a string goes: `headers={"Authorization": f"Bearer {qbash.secrets['API_KEY']}"}`. The ref is an opaque per-run token that the server resolves when it sends the request. Obtain it from `qbash.secrets[...]` and pass it through verbatim. Encoding, hashing or slicing it produces text the server cannot resolve. Unknown `auth` shapes are rejected.
qbash.http.auth
HTTP auth helpers. Build the `auth=` argument for qbash.http.request and qbash.files.download.
qbash.http.auth.bearer()Authorization: Bearer <token>.
result = qbash.http.auth.bearer(
token="…",
)
tokenstrrequired
dictqbash.http.auth.basic()Authorization: Basic base64(username:password). Resolved before encoding.
result = qbash.http.auth.basic(
username="…",
password="…",
)
usernamestrrequiredpasswordstrrequired
dictqbash.http.auth.header()Send `value` in the named header, e.g. header("X-Api-Key", qbash.secrets["KEY"]).
result = qbash.http.auth.header(
name="…",
value="…",
)
namestrrequiredvaluestrrequired
dictqbash.http.auth.query()Append `name=value` to the URL query string, URL-encoded.
result = qbash.http.auth.query(
name="…",
value="…",
)
namestrrequiredvaluestrrequired
dictqbash.files
Files tool - write, read, metadata, split, and convert operations.
qbash.files.open()Open a file as a streaming file-like object.
result = qbash.files.open(
ref="…",
)
refanyrequiredmodeanydefault'rt'encodinganydefault'utf-8'errorsanydefault'strict'
anymode='rb' yields a binary stream backed by a presigned S3 GET. mode='rt' yields a text stream (utf-8 decoded by default).
qbash.files.write()Write content as a named file. Returns a QBash file-reference object.
result = qbash.files.write(
content="…",
name="…",
)
contentanyrequirednamestrrequiredcontent_typestrdefault'text/plain'
dictcontent: str, bytes, or file-like object with .read() name: filename (e.g. "report.csv") content_type: MIME type (e.g. "text/csv", "application/pdf")
Files ≤ 10 MB are embedded in the RPC. Larger files use a two-phase presigned S3 upload automatically. The returned blob can be passed to `qbash.ai.raw(files=[ref])` or `qbash.create_chat(files=[ref])`.
qbash.files.begin_upload()Request a presigned S3 upload URL for a large file.
result = qbash.files.begin_upload(
name="…",
)
namestrrequiredcontent_typestrdefault'application/octet-stream'byte_sizeint | NonedefaultNone
dictReturns {"upload_url": str, "file_upload_id": str}. After uploading to the URL, call complete_upload(file_upload_id). Most callers should prefer write(), which routes to begin/complete automatically.
qbash.files.complete_upload()Confirm a large file upload and get the QBash file-reference object.
result = qbash.files.complete_upload(
file_upload_id="…",
)
file_upload_idstrrequired
dictCall this after a successful PUT to the upload_url from begin_upload().
qbash.files.read()Read a file's contents by reference.
result = qbash.files.read(
ref="…",
)
refanyrequiredencodingstrdefault'text'
anyencoding="text" -> str (UTF-8 decoded). Raises UnicodeDecodeError on encoding="bytes" -> bytes. encoding="base64" -> str (base64-encoded content).
- invalid UTF-8; use "bytes" for binary files.
Raises ValueError if the file is larger than 10 MB. For larger files, use qbash.files.download_url(ref) and stream from S3 directly.
qbash.files.download_url()Return a presigned S3 URL for the file (8 hour expiry).
result = qbash.files.download_url(
ref="…",
)
refanyrequired
strUseful for streaming large files (>10 MB) or passing a URL to another service. Blocks until the file's malware scan has cleared so the URL is immediately usable.
qbash.files.metadata()Return file metadata.
result = qbash.files.metadata(
ref={"key": "value"},
)
refdict | strrequiredA QBash file-reference object, or a file_upload_id.
include_urlbooldefaultFalseAlso return a presigned download_url, waiting for the file's scan to clear.
dictWith include_url=True, also includes a presigned download_url and waits for the file's scan to clear. Returns {file_upload_id, filename, content_type, byte_size, scan_status, processing_status} when include_url=False (fast peek - does not block on scan). Returns the same fields plus download_url when include_url=True (blocks on scan completion so the URL is immediately usable).
qbash.files.split_pdf()Split a PDF into individual pages.
result = qbash.files.split_pdf(
ref={"key": "value"},
)
refdict | strrequiredA QBash file-reference object, or a file_upload_id.
listReturns a list of QBash file-reference objects, one per page. Each object can be passed directly to qbash.ai.raw(files=[page]).
qbash.files.convert()Convert a file to a simpler format.
result = qbash.files.convert(
ref={"key": "value"},
)
refdict | strrequiredA QBash file-reference object, or a file_upload_id.
dict.doc/.docx/.rtf/.odt -> txt; .xlsx/.ods -> csv; .pptx/.ppt/.odp -> pdf. Returns {file_upload_id, filename, content_type, download_url}. Note: to pass a converted file to qbash.ai.raw, use the ORIGINAL ref - the LLM automatically handles conversion for supported formats.
qbash.files.zip()Create a zip archive from a list of (filename, content_bytes) tuples.
result = qbash.files.zip(
entries="…",
)
entriesanyrequired
anyqbash.files.zip([("a.pptx", bytes1), ("b.csv", bytes2)]) -> bytes
Filenames are sanitized (path components stripped). Duplicate names after sanitization get a _1, _2, etc. suffix.
qbash.files.writer()Open a streaming file writer for incremental writes.
result = qbash.files.writer(
name="…",
)
namestrrequiredcontent_typestrdefault'text/plain'
StreamingWriterReturns a StreamingWriter. Call .write(data) to append, .close() to finalize and get the QBash file-reference object.
qbash.files.download()Download a file from a URL and upload it to S3 server-side.
result = qbash.files.download(
url="https://…",
filename="…",
)
urlstrrequiredURL to download from (http or https).
filenamestrrequiredName for the stored file (e.g. "report.pdf").
content_typestr | NonedefaultNoneOverride content type. Auto-detected from the response Content-Type header if not provided.
headersdict | list | NonedefaultNoneOptional request headers dict.
authdict | NonedefaultNoneOptional auth dict (same format as qbash.http.request).
timeoutint | NonedefaultNoneHow long to wait for the next piece of the response, in milliseconds (default: 60000). It is not a limit on the whole download.
dictReturns a QBash file-reference object (same as qbash.files.write). The server streams the file into storage, so binary data never transits the RPC channel. Files up to qbash's 5 GB file limit are stored. A multi-GB download needs a run whose expected runtime is over 15 minutes.
qbash.inputs
Inputs accessor - read-only view over merged trigger + user inputs.
qbash.inputs.get()Return the value for input param `name`, or `default` if absent.
result = qbash.inputs.get(
name="…",
)
namestrrequireddefaultAnydefaultNone
Anyqbash.inputs.all()Return all input params as a dict.
result = qbash.inputs.all()
dictqbash.secrets
Secrets accessor: qbash.secrets["NAME"] returns a placeholder for a bound org secret, and the value never enters the sandbox.
qbash.secrets.get()Return the ref for bound secret `name`, or `default` when it is not bound.
result = qbash.secrets.get(
name="…",
)
namestrrequireddefaultstr | NonedefaultNone
SecretRef | str | NoneSame as `qbash.secrets[name]` for a bound name. Use it for optional secrets: `tok = qbash.secrets.get("OPTIONAL_TOKEN")` then `if tok:`. An unbound name never raises, so check the result before sending it.
qbash.secrets.names()Sorted names of the secrets bound to this task.
result = qbash.secrets.names()
list[str]qbash.checkpoint
qbash.checkpoint - run-scoped key/value store (DynamoDB-backed, no RPC).
qbash.checkpoint.done()Return True if this key has been marked or set.
result = qbash.checkpoint.done(
key="my-key",
)
keystrrequired
boolqbash.checkpoint.mark()Record completion with no stored value.
result = qbash.checkpoint.mark(
key="my-key",
)
keystrrequired
anyqbash.checkpoint.get()Return stored value, or None if absent or mark-only.
result = qbash.checkpoint.get(
key="my-key",
)
keystrrequired
Optional[Any]qbash.checkpoint.set()Store a JSON-serializable value (also marks as done).
result = qbash.checkpoint.set(
key="my-key",
value="…",
)
keystrrequiredvalueAnyrequired
anyqbash.checkpoint.get_or_set()Return cached value if present; otherwise call fn(), store, and return.
result = qbash.checkpoint.get_or_set(
key="my-key",
fn=process,
)
keystrrequiredfnCallablerequired
AnyNot appropriate when fn() can return None - use explicit get/set instead.
qbash.checkpoint.query_prefix()Return all key-value pairs where key starts with prefix.
result = qbash.checkpoint.query_prefix(
prefix="…",
)
prefixstrrequired
dict[str, Any]Uses DynamoDB Query with begins_with on the sort key. All keys share the same partition key (exec:{scope}). Handles pagination for result sets exceeding 1 MB.
qbash.cache
qbash.cache - task-scoped key/value store with required TTL.
qbash.cache.get()Return stored value if present and not expired, else None.
result = qbash.cache.get(
key="my-key",
)
keystrrequired
Optional[Any]qbash.cache.set()Store value with TTL. ttl: int seconds or '1h'/'1d' string.
result = qbash.cache.set(
key="my-key",
value="…",
ttl="…",
)
keystrrequiredvalueAnyrequiredttlanyrequired
anyqbash.cache.get_or_set()Return cached value if present; otherwise call fn(), store, and return.
result = qbash.cache.get_or_set(
key="my-key",
fn=process,
ttl="…",
)
keystrrequiredfnCallablerequiredttlanyrequired
AnyNot appropriate when fn() can return None - use explicit get/set instead.
qbash.cache.delete()Explicitly evict a cached value.
result = qbash.cache.delete(
key="my-key",
)
keystrrequired
anyqbash.run
qbash.run - runtime metadata for the current task invocation.
qbash.run.execution_idstrUnique id for this run; correlate against logs and execution records.
qbash.run.task_idstr | NoneThe AutomatedTask id this run belongs to; None for unsaved test runs.
qbash.run.is_testboolTrue when invoked as an unsaved draft from the task editor.
qbash.run.started_atdatetimeUTC timestamp of when this run was enqueued (tz-aware).
qbash.run.last_run_atdatetime | NoneUTC timestamp of the previous successful run; None on first run.
qbash.run.userdict | None{"id": uuid} of the human who pressed Run; None for schedule/webhook/system.
qbash.run.creatordict | None{"id": uuid} of the task author; the authorization subject for credentials and memory.
qbash.run.companydict | None{"id": uuid} of the company this task runs under; None when unscoped.
qbash.run.projectdict | None{"id": uuid} of the project bound to this task; None when standalone.
qbash.dates
qbash.dates - date and datetime utilities for task scripts.
qbash.dates.today()Return today's UTC date.
result = qbash.dates.today()
dateUse this instead of date.today(), which returns the lambda container's local date - undefined in practice.
qbash.dates.now()Return the current UTC datetime (tz-aware).
result = qbash.dates.now()
datetimeqbash.dates.parse()Parse a value into a tz-aware UTC datetime.
result = qbash.dates.parse(
value="…",
)
valueanyrequired
datetime- datetime -> returned as-is if tz-aware; if naive, assumed UTC - date -> midnight UTC of that date - str -> parsed via dateutil; assumed UTC if no tz - None -> raises ValueError
- ValueError if value is None, empty, or unparseable - TypeError if value is not str/date/datetime
qbash.powerpoint
PowerPoint utilities - exposed as qbash.powerpoint in user scripts.
qbash.powerpoint.fill()Fill {{placeholders}} in a PPTX template.
result = qbash.powerpoint.fill(
template_bytes="…",
data={"key": "value"},
)
template_bytesbytesrequireddatadictrequired
bytesqbash.powerpoint.fill(template_bytes, {"key": "val"}) -> bytes
Read the template with qbash.files.read(ref, encoding="bytes") first. Keys must be word characters only (a-z, 0-9, underscore). Replacement text inherits the first run's formatting when a placeholder spans multiple XML runs.
qbash.pdf
PDF form utilities - exposed as qbash.pdf in user scripts.
qbash.pdf.fields()List the fillable form fields in an AcroForm PDF.
result = qbash.pdf.fields(
pdf_bytes="…",
)
pdf_bytesbytesrequiredthe PDF document as bytes.
listqbash.pdf.fields(pdf_bytes) -> [{"name", "label", "type", "options", "value",
- "page", "readonly", "multiselect", "editable"}, ...]
Read the PDF with ``with qbash.files.open(ref, mode="rb") as f: pdf_bytes = f.read()`` first. ``name`` is the fully qualified field name (use it as the key for fill); ``label`` is the form author's tooltip, which government forms usually set to the printed caption. ``type`` is one of text, checkbox, radio, dropdown, listbox, signature. ``options`` lists a checkbox's on-state, a radio group's choices, or a dropdown's entries. ``page`` is 1-based. ``readonly`` fields cannot be filled; ``multiselect`` list boxes take a list of options; ``editable`` dropdowns also accept free text that is not one of their options. A signature field's ``value`` is "signed" or None. Results are sorted by page, then name.
TypeError: pdf_bytes is not bytes. ValueError: the PDF has no fillable fields (no AcroForm, or an empty
- one left behind by flattening), uses an XFA form (Adobe
- proprietary; cannot be filled programmatically), or is encrypted
- with a non-empty password.
qbash.pdf.fill()Fill an AcroForm PDF and return the filled document as bytes.
result = qbash.pdf.fill(
pdf_bytes="…",
data={"key": "value"},
)
pdf_bytesbytesrequiredthe template PDF as bytes.
datadictrequiredmapping of field name (or label) to value.
flattenbooldefaultFalseremove form fields after filling. Default False keeps the output editable.
bytesqbash.pdf.fill(pdf_bytes, {"field": "value", ...}, flatten=False) -> bytes
Keys are field names from fields() (or an unambiguous label). Values: text fields take any value (stringified; None clears); checkboxes take True/False; radio groups and dropdowns take one of their ``options`` (an ``editable`` dropdown also takes free text); multi-select list boxes take a list of options. Unknown or read-only keys raise KeyError listing the offenders and close matches, so a typo never silently produces a blank form. ``flatten=True`` bakes the values into the page and removes the form widgets (non-editable output). Flattening is refused for forms with radio groups (pypdf renders them incorrectly). Filling an already signed form is refused entirely, because rewriting the file invalidates the existing signature; fill an unsigned copy instead.
TypeError: data is not a dict, or pdf_bytes is not bytes. KeyError: a key matches no field name or label, a label is shared by ValueError: a checkbox/radio/dropdown value is not one of its options,
- several fields, or the field is read-only.
- a list was given for a single-select field, the PDF is not a
- fillable AcroForm (see fields), the form is already signed, or
- flatten=True was requested for a form with radio groups.
qbash.parsers
qbash.parsers - format helpers for any file-like input.
qbash.parsers.csv_chunks()Yield CSV rows in batches of `chunk_size` from a text file-like.
result = qbash.parsers.csv_chunks(
f="…",
)
fTextIOrequiredchunk_sizeintdefault1000has_headerbooldefaultTruedialectstrdefault'excel'
Iterator[list[dict]]With has_header=True the first row becomes the keys for every dict. With has_header=False keys are "col_0", "col_1", ...
The file-like MUST be opened in text mode (mode='rt' on client.open(), or csv-friendly settings on a local open()). For binary inputs, wrap with io.TextIOWrapper first.
qbash.parsers.xlsx_chunks()Yield XLSX rows in batches of `chunk_size` from a binary file-like.
result = qbash.parsers.xlsx_chunks(
f="…",
)
fBinaryIOrequiredchunk_sizeintdefault1000has_headerbooldefaultTruesheetslist[str | int] | NonedefaultNone
Iterator[list[dict]]f must be a binary file-like object (BytesIO). String paths and raw bytes are rejected - use BytesIO(sftp.read(path, mode='rb')).
qbash.parsers.line_chunks()Yield lines in batches of `chunk_size` from a text file-like.
result = qbash.parsers.line_chunks(
f="…",
)
fTextIOrequiredchunk_sizeintdefault1000
Iterator[list[str]]Lines retain their trailing newline (matches stdlib `for line in f:` shape). Strip with .rstrip('\n') in user code if undesired.
qbash.integrations
Each connected service is called as qbash.integrations.<provider>.<method>(), using the account the Task has assigned. Open a method for its parameters and result.
list_campaigns()List campaigns with their engagement stats (opens, clicks, bounces, unsubscribes).
result = qbash.integrations.active_campaign.list_campaigns(
limit=10,
offset=0,
)
limitint | NonedefaultNoneMaximum number of campaigns to return (max 100, default 20).
offsetint | NonedefaultNonePagination offset.
statusstr | NonedefaultNoneFilter by status code (0=draft, 1=scheduled, 2=sending, 3=paused, 5=completed).
orders_sdatestr | NonedefaultNoneSort by send date, either ASC or DESC.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
{"status": "success", "campaigns": list[dict], "count": int, "meta": dict, "has_more": bool, "next_cursor": str | None}Each campaign map is reshaped here rather than forwarded. It carries id, name, type, status, sdate, ldate, mdate and cdate, the engagement counters send_amt, total_amt, opens, uniqueopens, linkclicks, uniquelinkclicks, subscriberclicks, hardbounces, softbounces, unsubscribes, unsubreasons, forwards, uniqueforwards, replies, uniquereplies and socialshares parsed from ActiveCampaign strings into ints, and raw, which holds the campaign object exactly as ActiveCampaign sent it. Keys whose value is null are dropped, so read them with .get() rather than by index, and fall back to raw for fields not lifted to the top level. "count" is the number of campaigns in this response, not the account total. "meta" is ActiveCampaign's own meta object forwarded unchanged, and it is an empty dict when the response carries none, so read "total" from it with .get(). One page is returned per call, so step through the rest with offset. Continue with cursor=next_cursor until None, or use offset; do not supply both. A full page without a total requires a final empty-page check.
get_campaign()Get a single campaign by ID with full engagement stats.
result = qbash.integrations.active_campaign.get_campaign(
campaign_id="…",
)
campaign_idstrrequiredCampaign ID.
{"status": "success", "campaign": dict}The campaign map is built by the same formatter list_campaigns uses, so it carries the same lifted fields plus raw, the campaign object exactly as ActiveCampaign sent it. Keys whose value is null are dropped, so read them with .get() rather than by index. A campaign ID that does not exist comes back as an error rather than an empty result.
list_campaign_links()List the trackable links in a campaign (URL, name, ref, tracking flags).
result = qbash.integrations.active_campaign.list_campaign_links(
campaign_id="…",
)
campaign_idstrrequiredCampaign ID.
{"status": "success", "links": list[dict], "count": int, "meta": dict, "related": dict, "has_more": bool, "next_cursor": str | None}Returns the complete collection of trackable links without pagination. The link records are forwarded without reshaping, so read their vendor fields with .get(). The result key is always links, including on accounts whose response uses the older campaignLinks key. count is the number of links returned. meta can be an empty dict, and related preserves sibling collections. has_more is always False and next_cursor is always None. This endpoint does not support limit, offset or cursor.
get_campaign_revenue()Get aggregate revenue attributed to a campaign.
result = qbash.integrations.active_campaign.get_campaign_revenue(
campaign_id="…",
)
campaign_idstrrequiredCampaign ID.
{"status": "success", "revenue": dict}"revenue" is the body of ActiveCampaign's aggregateRevenues endpoint forwarded unchanged. Nothing here reads or reshapes it, so the keys inside are the vendor's, they vary with what the account has attributed, and they should be read with .get() against ActiveCampaign's own documentation. A campaign with no attributed revenue still returns a success envelope.
list_contacts()List or search contacts with optional filters.
result = qbash.integrations.active_campaign.list_contacts(
limit=10,
offset=0,
)
limitint | NonedefaultNoneMaximum number of contacts to return (max 100, default 20).
offsetint | NonedefaultNonePagination offset.
emailstr | NonedefaultNoneExact email match filter.
list_idstr | NonedefaultNoneFilter by list ID.
tag_idstr | NonedefaultNoneFilter by tag ID.
statusstr | NonedefaultNoneList-membership status (-1=any, 0=unconfirmed, 1=active, 2=unsubscribed, 3=bounced). Combine with list_id for one list.
searchstr | NonedefaultNoneFree-text search across name and email.
idsstr | NonedefaultNoneComma-separated known contact IDs, fetched in one request.
id_greaterstr | NonedefaultNoneOnly IDs above this value; combine with orders_id=ASC to walk large audiences.
segment_idstr | NonedefaultNoneExisting segment ID. When set, most other filters are ignored by ActiveCampaign; status and search still apply.
automation_idstr | NonedefaultNoneOnly contacts associated with this automation (seriesid).
form_idstr | NonedefaultNoneOnly contacts associated with this signup form.
created_afterstr | NonedefaultNoneOnly contacts created after this date.
created_beforestr | NonedefaultNoneOnly contacts created before this date.
updated_afterstr | NonedefaultNoneOnly contacts modified after this date; this is not the time of unsubscribe.
updated_beforestr | NonedefaultNoneOnly contacts modified before this date.
tag_namestr | NonedefaultNoneFilter by tag name instead of tag_id; do not combine them.
orders_idstr | NonedefaultNoneASC or DESC by contact ID.
orders_udatestr | NonedefaultNoneASC or DESC by contact modification date.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
{"status": "success", "contacts": list[dict], "count": int, "meta": dict, "has_more": bool, "next_cursor": str | None}Each contact map is reshaped here rather than forwarded. It carries id, email, first_name, last_name, phone, cdate, udate, adate, edate, sdate, ip, ua, hash, socialdata_lastcheck, email_local, email_domain, orgid, orgname, bounced_hard, bounced_soft, bounced_date, and raw, which holds the contact object exactly as ActiveCampaign sent it. first_name and last_name are renamed from ActiveCampaign's firstName and lastName. Keys whose value is null are dropped, so read them with .get() rather than by index, and use raw for fields not lifted to the top level. For actual custom-field values, call list_contact_field_values and join field IDs to list_fields. "count" is the number of contacts in this response, not the account total. "meta" is ActiveCampaign's own meta object forwarded unchanged and is an empty dict when the response carries none. One page is returned per call, so step through the rest with offset. Continue with cursor=next_cursor until None, or use offset; do not supply both. A full page without a total requires a final empty-page check.
get_contact()Get a single contact by ID with bounce + activity counters.
result = qbash.integrations.active_campaign.get_contact(
contact_id="…",
)
contact_idstrrequiredContact ID.
{"status": "success", "contact": dict}The contact map is built by the same formatter list_contacts uses, so it carries the same lifted fields plus raw, the contact object exactly as ActiveCampaign sent it. Keys whose value is null are dropped, so read them with .get() rather than by index. contact.related preserves sibling vendor collections such as fieldValues, contactLists and deals; raw contains only the contact object. A contact ID that does not exist comes back as an error rather than an empty result.
list_contact_activities()List engagement activities for a contact (opens, clicks, automation events).
result = qbash.integrations.active_campaign.list_contact_activities(
contact_id="…",
)
contact_idstrrequiredContact ID.
afterstr | NonedefaultNoneISO 8601 timestamp. Only activities after this point are returned.
limitint | NonedefaultNoneMaximum number of activities to return.
offsetint | NonedefaultNonePagination offset.
includestr | NonedefaultNoneComma-separated documented include paths, e.g. reference.log.campaign,reference.link,notes.
typestr | NonedefaultNoneActivity types, comma-separated, e.g. log,link-data,reply,tracking-log.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
{"status": "success", "activities": list[dict], "count": int, "meta": dict, "related": dict, "has_more": bool, "next_cursor": str | None}related preserves sibling vendor records requested with include. The activity records are forwarded without reshaping, and two different ActiveCampaign payloads land under the same "activities" key: the activities array, and the trackingLogs array on accounts that answer with those instead. The fields differ between the two, so read them with .get() rather than by index. "count" is the number of records in this response and "meta" is ActiveCampaign's own meta object, an empty dict when the response carries none. One page is returned per call, so step through the rest with offset. Continue with cursor=next_cursor until None, or use offset; do not supply both. A full page without a total requires a final empty-page check.
list_contact_tags()List the contact-tag association records for a contact.
result = qbash.integrations.active_campaign.list_contact_tags(
contact_id="…",
)
contact_idstrrequiredContact ID.
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
{"status": "success", "contact_tags": list[dict], "count": int, "meta": dict, "related": dict, "has_more": bool, "next_cursor": str | None}One page is returned per call. contact_tags contains vendor records without reshaping; count is the page length, not a total. meta can be an empty dict. related preserves sibling collections. Contact tags support cursor/offset continuation. Contact-tag id is an association ID, while tag is the tag ID; use list_tags/get_tag to resolve names.
list_lists()List all lists in the account.
result = qbash.integrations.active_campaign.list_lists(
limit=10,
offset=0,
)
limitint | NonedefaultNoneMaximum number of lists to return.
offsetint | NonedefaultNonePagination offset.
namestr | NonedefaultNoneFilter lists by name (server-side substring match).
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
{"status": "success", "lists": list[dict], "count": int, "meta": dict, "has_more": bool, "next_cursor": str | None}The list records are ActiveCampaign's own and are forwarded without reshaping, so read their fields with .get() against ActiveCampaign's documentation. "count" is the number of lists in this response, not the account total, and "meta" is ActiveCampaign's own meta object, an empty dict when the response carries none. One page is returned per call despite the mode name, so step through the rest with offset. Continue with cursor=next_cursor until None, or use offset; do not supply both. A full page without a total requires a final empty-page check.
list_tags()List all tags in the account.
result = qbash.integrations.active_campaign.list_tags(
limit=10,
offset=0,
)
limitint | NonedefaultNoneMaximum number of tags to return.
offsetint | NonedefaultNonePagination offset.
searchstr | NonedefaultNoneFilter tags by name (server-side substring match).
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
{"status": "success", "tags": list[dict], "count": int, "meta": dict, "has_more": bool, "next_cursor": str | None}The tag records are ActiveCampaign's own and are forwarded without reshaping, so read their fields with .get() against ActiveCampaign's documentation. "count" is the number of tags in this response, not the account total, and "meta" is ActiveCampaign's own meta object, an empty dict when the response carries none. One page is returned per call despite the mode name, so step through the rest with offset. Continue with cursor=next_cursor until None, or use offset; do not supply both. A full page without a total requires a final empty-page check.
add_tag_to_contact()Apply a tag to a contact.
result = qbash.integrations.active_campaign.add_tag_to_contact(
contact_id="…",
tag_id="…",
)
contact_idstrrequiredContact ID.
tag_idstrrequiredTag ID (numeric).
{"status": "success", "contact_tag": dict}"contact_tag" is the association record ActiveCampaign created, forwarded without reshaping. Its "id" is the association ID rather than the tag ID, and remove_tag_from_contact looks that ID up again rather than taking it, so there is nothing to keep from this result for a later removal. contact_id and tag_id are parsed to ints before the request when they look numeric, and are sent as given when they do not.
remove_tag_from_contact()Remove a tag from a contact. Looks up the contactTag association and deletes it.
result = qbash.integrations.active_campaign.remove_tag_from_contact(
contact_id="…",
tag_id="…",
)
contact_idstrrequiredContact ID.
tag_idstrrequiredTag ID to remove.
{"status": "success", "result": {"deleted": True, "contact_tag_id": str}} or {"status": "success", "message": str}Two shapes, and "result" is the discriminator. When the contact carries the tag, the association is deleted and "result" holds deleted and contact_tag_id, the association ID as a string. When the contact does not carry the tag, nothing is deleted and the envelope holds "message" instead of "result", which keeps repeat removal harmless. That message is not proof the tag is gone. The lookup asks for the contact's associations without a limit or an offset, so it only sees the first page ActiveCampaign returns, and a contact with more associations than fit on it can still carry the tag and get the message shape. Confirm with list_contact_tags when it matters. Read "result" with .get() rather than indexing it. The removal costs two requests, one to list the contact's associations and one to delete the match.
track_event()Send a custom event to ActiveCampaign site tracking. Uses event_key + actid credentials, not the API token.
result = qbash.integrations.active_campaign.track_event(
event="…",
)
eventstrrequiredEvent name as configured in Settings → Tracking.
eventdatastr | NonedefaultNoneOptional payload string for the event.
emailstr | NonedefaultNoneContact email to attribute the event to.
{"status": "success", "result": dict | list | str | int | float | bool | None}"result" is the tracking endpoint's body parsed as JSON and forwarded without reshaping, so check it is a dict before indexing it. Any JSON value the endpoint returns arrives here, including a list, a string, a number, a boolean or None, because the service passes a decoded body straight through. A body that is not valid JSON is replaced with a map holding "success" set to 1 and "raw" set to the response text. A body whose "success" is 0 becomes an error before the script sees it, so reaching this envelope means the endpoint accepted the event. This mode needs the event_key and actid credential fields, and it calls a separate tracking host rather than the account API URL, so it works even when api_token and api_url are unset. Passing email attaches the event to that contact, and leaving it out records an anonymous event.
list_campaign_messages()List campaign messages. Lifetime per-message totals for regular and split campaigns; these counters cannot be constrained to a date range.
result = qbash.integrations.active_campaign.list_campaign_messages(
campaign_id="…",
)
campaign_idstrrequiredcampaign id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "campaign_messages": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Lifetime per-message totals for regular and split campaigns; these counters cannot be constrained to a date range. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/retrieve-campaign-messages-associated-campaign
list_messages()List messages. Email content: id, subject, preheader_text, fromname, fromemail, text and html when returned.
result = qbash.integrations.active_campaign.list_messages(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "messages": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Email content: id, subject, preheader_text, fromname, fromemail, text and html when returned. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-messages
get_message()Get message. An email message including subject, sender and available HTML/text content.
result = qbash.integrations.active_campaign.get_message(
message_id="…",
)
message_idstrrequiredmessage id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "message": dict, "related": dict}An email message including subject, sender and available HTML/text content. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-message
list_templates()List templates. Email template IDs, names and available content. Access depends on template permissions.
result = qbash.integrations.active_campaign.list_templates(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "templates": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Email template IDs, names and available content. Access depends on template permissions. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-templates
get_template()Get template. An email template and available HTML/text content.
result = qbash.integrations.active_campaign.get_template(
template_id="…",
)
template_idstrrequiredtemplate id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "template": dict, "related": dict}An email template and available HTML/text content. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-template
get_list()Get list. List name, sender information and subscription configuration.
result = qbash.integrations.active_campaign.get_list(
list_id="…",
)
list_idstrrequiredlist id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "list": dict, "related": dict}List name, sender information and subscription configuration. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-list
get_tag()Get tag. Tag id, tag (name), tagType and description.
result = qbash.integrations.active_campaign.get_tag(
tag_id="…",
)
tag_idstrrequiredtag id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "tag": dict, "related": dict}Tag id, tag (name), tagType and description. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-tag
list_fields()List fields. Contact custom-field definitions: id, title, type, perstag and default value. Values are separate records.
result = qbash.integrations.active_campaign.list_fields(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "fields": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Contact custom-field definitions: id, title, type, perstag and default value. Values are separate records. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/retrieve-fields
get_field()Get field. A contact custom-field definition. Options and list relationships are in related when returned.
result = qbash.integrations.active_campaign.get_field(
field_id="…",
)
field_idstrrequiredfield id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "field": dict, "related": dict}A contact custom-field definition. Options and list relationships are in related when returned. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-custom-field-contact
list_field_options()List field options. Allowed option labels and values for a contact custom field.
result = qbash.integrations.active_campaign.list_field_options(
field_id="…",
)
field_idstrrequiredfield id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "field_options": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Allowed option labels and values for a contact custom field. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/retrieve-a-custom-field-contact
list_contact_field_values()List contact field values. Actual custom-field values: id, contact, field, value, cdate and udate. Join field with list_fields.
result = qbash.integrations.active_campaign.list_contact_field_values(
contact_id="…",
)
contact_idstrrequiredcontact id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "field_values": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Actual custom-field values: id, contact, field, value, cdate and udate. Join field with list_fields. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/retrieve-contact-field-values
list_contact_lists()List contact lists. List memberships: list, status, sdate, udate and unsubreason. Status 0 unconfirmed, 1 active, 2 unsubscribed, 3 bounced. udate is membership-specific.
result = qbash.integrations.active_campaign.list_contact_lists(
contact_id="…",
)
contact_idstrrequiredcontact id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "contact_lists": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}List memberships: list, status, sdate, udate and unsubreason. Status 0 unconfirmed, 1 active, 2 unsubscribed, 3 bounced. udate is membership-specific. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/retrieve-contact-list-memberships
list_contact_automations()List contact automations. Automation memberships with seriesid/automation, status, adddate, remdate and completion fields when returned.
result = qbash.integrations.active_campaign.list_contact_automations(
contact_id="…",
)
contact_idstrrequiredcontact id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "contact_automations": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Automation memberships with seriesid/automation, status, adddate, remdate and completion fields when returned. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/get-contact
list_contact_deals()List contact deals. Deals associated with this contact. Permission-limited deals may contain only id, title and isDisabled.
result = qbash.integrations.active_campaign.list_contact_deals(
contact_id="…",
)
contact_idstrrequiredcontact id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "deals": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Deals associated with this contact. Permission-limited deals may contain only id, title and isDisabled. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/retrieve-contact-deals
list_contact_notes()List contact notes. Notes with id, note text, owner/user references and timestamps.
result = qbash.integrations.active_campaign.list_contact_notes(
contact_id="…",
)
contact_idstrrequiredcontact id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "notes": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Notes with id, note text, owner/user references and timestamps. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/retrieve-contact-notes
list_contact_scores()List contact scores. Contact scoreValues: score identifies the definition and scoreValue is the actual score; join score with list_scores.
result = qbash.integrations.active_campaign.list_contact_scores(
contact_id="…",
)
contact_idstrrequiredcontact id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "score_values": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Contact scoreValues: score identifies the definition and scoreValue is the actual score; join score with list_scores. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/retrieve-a-contacts-score-value
list_contact_geo_ips()List contact geo ips. Tracked IP/geographic references, campaign/message IDs and timestamps. Included geoAddresses are preserved in related.
result = qbash.integrations.active_campaign.list_contact_geo_ips(
contact_id="…",
)
contact_idstrrequiredcontact id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "geo_ips": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Tracked IP/geographic references, campaign/message IDs and timestamps. Included geoAddresses are preserved in related. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/retrieve-contact-geo-ips
list_automations()List automations. Automation id, name, status, entered/exited counters and related resource links. This does not edit the automation builder.
result = qbash.integrations.active_campaign.list_automations(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "automations": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Automation id, name, status, entered/exited counters and related resource links. This does not edit the automation builder. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-automations
get_automation()Get automation. Automation metadata and available entry/exit counters.
result = qbash.integrations.active_campaign.get_automation(
automation_id="…",
)
automation_idstrrequiredautomation id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "automation": dict, "related": dict}Automation metadata and available entry/exit counters. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-an-automation
list_automation_contacts()List automation contacts. Contact enrollment/completion records for an automation; contact and seriesid identify the relationship.
result = qbash.integrations.active_campaign.list_automation_contacts(
automation_id="…",
)
automation_idstrrequiredautomation id. Use the ID returned by the corresponding list operation.
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "contact_automations": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Contact enrollment/completion records for an automation; contact and seriesid identify the relationship. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-contactautomations-for-automation
list_automation_campaigns()List automation campaigns. Campaigns associated with an automation, including their vendor counters.
result = qbash.integrations.active_campaign.list_automation_campaigns(
automation_id="…",
)
automation_idstrrequiredautomation id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "campaigns": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Campaigns associated with an automation, including their vendor counters. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. This relationship endpoint returns the complete collection; limit/offset are ignored by ActiveCampaign. has_more is false and next_cursor is None. Reference: https://developers.activecampaign.com/reference/retrieve-an-automation
list_segments()List segments. Saved segment IDs, names and metadata only; conditions are not included. filters, orders and include have no effect. Use list_contacts(segment_id=...) for membership.
result = qbash.integrations.active_campaign.list_segments(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "segments": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Saved segment IDs, names and metadata only; conditions are not included. filters, orders and include have no effect. Use list_contacts(segment_id=...) for membership. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-segments
get_segment()Get segment. Saved segment metadata only; segment conditions are not included. This does not evaluate membership.
result = qbash.integrations.active_campaign.get_segment(
segment_id="…",
)
segment_idstrrequiredsegment id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "segment": dict, "related": dict}Saved segment metadata only; segment conditions are not included. This does not evaluate membership. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-segment
list_deals()List deals. Deals: id, title, value, currency, status (0 open, 1 won, 2 lost), group (pipeline), stage, owner and contact. value is in minor currency units. Requires Deals and pipeline access. Limited records have isDisabled.
result = qbash.integrations.active_campaign.list_deals(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "deals": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Deals: id, title, value, currency, status (0 open, 1 won, 2 lost), group (pipeline), stage, owner and contact. value is in minor currency units. Requires Deals and pipeline access. Limited records have isDisabled. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-deals
get_deal()Get deal. A deal and related records when returned. Requires Deals and pipeline access; isDisabled identifies restricted records.
result = qbash.integrations.active_campaign.get_deal(
deal_id="…",
)
deal_idstrrequireddeal id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "deal": dict, "related": dict}A deal and related records when returned. Requires Deals and pipeline access; isDisabled identifies restricted records. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-deal
list_pipelines()List pipelines. Pipeline id, title, currency, assignment settings and stage references. Requires pipeline access.
result = qbash.integrations.active_campaign.list_pipelines(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "pipelines": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Pipeline id, title, currency, assignment settings and stage references. Requires pipeline access. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-pipelines
get_pipeline()Get pipeline. Pipeline metadata and stage references. Requires pipeline access.
result = qbash.integrations.active_campaign.get_pipeline(
pipeline_id="…",
)
pipeline_idstrrequiredpipeline id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "pipeline": dict, "related": dict}Pipeline metadata and stage references. Requires pipeline access. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-pipeline
list_stages()List stages. Stage id, title, group (pipeline), order and deal display settings. Requires pipeline access.
result = qbash.integrations.active_campaign.list_stages(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "stages": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Stage id, title, group (pipeline), order and deal display settings. Requires pipeline access. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-deal-stages
get_stage()Get stage. A pipeline stage with title, group and ordering.
result = qbash.integrations.active_campaign.get_stage(
stage_id="…",
)
stage_idstrrequiredstage id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "stage": dict, "related": dict}A pipeline stage with title, group and ordering. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-deal-stage
list_tasks()List tasks. CRM tasks: id, title/note, duedate, status (0 incomplete, 1 complete), reltype, relid, assignee and dealTasktype. These are ActiveCampaign CRM tasks, not qbash tasks.
result = qbash.integrations.active_campaign.list_tasks(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "tasks": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}CRM tasks: id, title/note, duedate, status (0 incomplete, 1 complete), reltype, relid, assignee and dealTasktype. These are ActiveCampaign CRM tasks, not qbash tasks. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-tasks
get_task()Get task. An ActiveCampaign CRM task and its owner, assignee, due date and completion status.
result = qbash.integrations.active_campaign.get_task(
task_id="…",
)
task_idstrrequiredtask id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "task": dict, "related": dict}An ActiveCampaign CRM task and its owner, assignee, due date and completion status. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/get-task
list_task_types()List task types. CRM task type IDs and labels used by dealTasktype on tasks.
result = qbash.integrations.active_campaign.list_task_types(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "task_types": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}CRM task type IDs and labels used by dealTasktype on tasks. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-deal-task-types
list_deal_notes()List deal notes. Deal note text, user references and timestamps. Requires access to the deal pipeline.
result = qbash.integrations.active_campaign.list_deal_notes(
deal_id="…",
)
deal_idstrrequireddeal id. Use the ID returned by the corresponding list operation.
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "notes": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Deal note text, user references and timestamps. Requires access to the deal pipeline. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/retrieve-a-deal
list_deal_contacts()List deal contacts. Secondary-contact association records: id, contact and deal.
result = qbash.integrations.active_campaign.list_deal_contacts(
deal_id="…",
)
deal_idstrrequireddeal id. Use the ID returned by the corresponding list operation.
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "contact_deals": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Secondary-contact association records: id, contact and deal. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-secondary-contacts
list_deal_fields()List deal fields. Deal custom-field definitions and option metadata when returned.
result = qbash.integrations.active_campaign.list_deal_fields(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "deal_fields": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Deal custom-field definitions and option metadata when returned. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/retrieve-all-custom-deal-field-meta
list_deal_field_values()List deal field values. Deal custom-field values: dealId, customFieldId and fieldValue. Filter with filters={"dealId": id}.
result = qbash.integrations.active_campaign.list_deal_field_values(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "field_values": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Deal custom-field values: dealId, customFieldId and fieldValue. Filter with filters={"dealId": id}. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-custom-fielddata-field-values
list_accounts()List accounts. Company account id, name, accountUrl, contactCount, dealCount and timestamps. Account-contact associations replace deprecated organizations.
result = qbash.integrations.active_campaign.list_accounts(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "accounts": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Company account id, name, accountUrl, contactCount, dealCount and timestamps. Account-contact associations replace deprecated organizations. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-accounts
get_account()Get account. Company account metadata with related collections preserved when returned.
result = qbash.integrations.active_campaign.get_account(
account_id="…",
)
account_idstrrequiredaccount id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "account": dict, "related": dict}Company account metadata with related collections preserved when returned. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-an-account
list_account_contacts()List account contacts. Company-contact association IDs, account and contact references.
result = qbash.integrations.active_campaign.list_account_contacts(
account_id="…",
)
account_idstrrequiredaccount id. Use the ID returned by the corresponding list operation.
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "account_contacts": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Company-contact association IDs, account and contact references. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/retrieve-an-account
list_account_notes()List account notes. Company account notes with text, owners and timestamps.
result = qbash.integrations.active_campaign.list_account_notes(
account_id="…",
)
account_idstrrequiredaccount id. Use the ID returned by the corresponding list operation.
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "notes": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Company account notes with text, owners and timestamps. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/retrieve-an-account
list_account_fields()List account fields. Company account custom-field definitions and options when returned.
result = qbash.integrations.active_campaign.list_account_fields(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "account_fields": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Company account custom-field definitions and options when returned. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-custom-fields-meta
list_account_field_values()List account field values. Company custom-field values: accountId, customFieldId and fieldValue. Filter with filters={"customerAccountId": id}.
result = qbash.integrations.active_campaign.list_account_field_values(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "field_values": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Company custom-field values: accountId, customFieldId and fieldValue. Filter with filters={"customerAccountId": id}. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-custom-field-values-2
list_notes()List notes. Notes across available contacts/deals/accounts. reltype and relid identify the owning resource.
result = qbash.integrations.active_campaign.list_notes(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "notes": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Notes across available contacts/deals/accounts. reltype and relid identify the owning resource. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-notes
get_note()Get note. A note with text, resource relationship, user reference and timestamps.
result = qbash.integrations.active_campaign.get_note(
note_id="…",
)
note_idstrrequirednote id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "note": dict, "related": dict}A note with text, resource relationship, user reference and timestamps. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-note
list_scores()List scores. Lead/deal score definitions: id, name, reltype, status and description. Definitions are not contact score values.
result = qbash.integrations.active_campaign.list_scores(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "scores": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Lead/deal score definitions: id, name, reltype, status and description. Definitions are not contact score values. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-scores
get_score()Get score. A contact/deal score definition. Use list_contact_scores for actual values.
result = qbash.integrations.active_campaign.get_score(
score_id="…",
)
score_idstrrequiredscore id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "score": dict, "related": dict}A contact/deal score definition. Use list_contact_scores for actual values. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-score
list_webhooks()List webhooks. Configured webhook IDs, URLs, events and sources. Reading these does not create subscriptions or task triggers.
result = qbash.integrations.active_campaign.list_webhooks(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "webhooks": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Configured webhook IDs, URLs, events and sources. Reading these does not create subscriptions or task triggers. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-webhooks
get_webhook()Get webhook. A configured webhook URL, events and sources.
result = qbash.integrations.active_campaign.get_webhook(
webhook_id="…",
)
webhook_idstrrequiredwebhook id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "webhook": dict, "related": dict}A configured webhook URL, events and sources. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-webhook
list_forms()List forms. Signup form IDs, names and available configuration.
result = qbash.integrations.active_campaign.list_forms(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "forms": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Signup form IDs, names and available configuration. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-forms
get_form()Get form. Signup form metadata and available configuration.
result = qbash.integrations.active_campaign.get_form(
form_id="…",
)
form_idstrrequiredform id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "form": dict, "related": dict}Signup form metadata and available configuration. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-form
list_users()List users. Account user IDs, names, email and group references; availability depends on user permissions.
result = qbash.integrations.active_campaign.list_users(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "users": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Account user IDs, names, email and group references; availability depends on user permissions. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-users
get_user()Get user. An account user and group references. Does not retrieve or reset API credentials.
result = qbash.integrations.active_campaign.get_user(
user_id="…",
)
user_idstrrequireduser id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "user": dict, "related": dict}An account user and group references. Does not retrieve or reset API credentials. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-user
list_groups()List groups. User group IDs, titles and permission settings.
result = qbash.integrations.active_campaign.list_groups(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "groups": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}User group IDs, titles and permission settings. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-groups
get_group()Get group. A user group and its permission settings.
result = qbash.integrations.active_campaign.get_group(
group_id="…",
)
group_idstrrequiredgroup id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "group": dict, "related": dict}A user group and its permission settings. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-group
list_ecommerce_connections()List ecommerce connections. External commerce connection id, service, externalid, status, syncStatus and lastSync.
result = qbash.integrations.active_campaign.list_ecommerce_connections(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "connections": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}External commerce connection id, service, externalid, status, syncStatus and lastSync. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-connections
get_ecommerce_connection()Get ecommerce connection. An external commerce connection and its synchronization metadata.
result = qbash.integrations.active_campaign.get_ecommerce_connection(
connection_id="…",
)
connection_idstrrequiredconnection id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "connection": dict, "related": dict}An external commerce connection and its synchronization metadata. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-connection
list_ecommerce_customers()List ecommerce customers. Commerce customer id, connection reference, externalid and contact/email references.
result = qbash.integrations.active_campaign.list_ecommerce_customers(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "customers": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Commerce customer id, connection reference, externalid and contact/email references. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-customers
get_ecommerce_customer()Get ecommerce customer. A commerce customer and vendor-provided purchase totals when returned.
result = qbash.integrations.active_campaign.get_ecommerce_customer(
customer_id="…",
)
customer_idstrrequiredcustomer id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "customer": dict, "related": dict}A commerce customer and vendor-provided purchase totals when returned. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-customer
list_ecommerce_orders()List ecommerce orders. Commerce orders: id, externalid, customerid, totalPrice, currency, orderDate and order products when returned. Monetary fields use vendor units; do not assume floats are major currency values.
result = qbash.integrations.active_campaign.list_ecommerce_orders(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "orders": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Commerce orders: id, externalid, customerid, totalPrice, currency, orderDate and order products when returned. Monetary fields use vendor units; do not assume floats are major currency values. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-orders
get_ecommerce_order()Get ecommerce order. A commerce order with available products and customer references. Related collections are preserved.
result = qbash.integrations.active_campaign.get_ecommerce_order(
order_id="…",
)
order_idstrrequiredorder id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "order": dict, "related": dict}A commerce order with available products and customer references. Related collections are preserved. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-an-order
list_custom_object_schemas()List custom object schemas. Custom-object schema IDs, fields and relationship definitions. Availability depends on account entitlements.
result = qbash.integrations.active_campaign.list_custom_object_schemas(
limit=10,
offset=0,
)
limitint | NonedefaultNonePage size, 1–100; default 20.
offsetint | NonedefaultNoneNon-negative vendor offset. Use either offset or cursor.
cursorstr | NonedefaultNoneOpaque continuation returned as next_cursor; use instead of offset.
filtersdict | NonedefaultNoneDocumented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].
ordersdict | NonedefaultNoneDocumented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "schemas": list[dict], "count": int, "meta": dict, "related": dict, "next_cursor": str | None, "has_more": bool}Custom-object schema IDs, fields and relationship definitions. Availability depends on account entitlements. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. One page per call. count is the page length, not the total; meta may be an empty dict. Continue with cursor=next_cursor until it is None; a full page without meta.total requires a final empty-page check. filters and orders use the vendor's documented field spellings, not SDK aliases. Unsupported vendor filters may be silently ignored. Reference: https://developers.activecampaign.com/reference/list-all-schemas
get_custom_object_schema()Get custom object schema. A custom-object schema with field and relationship definitions. Availability depends on account entitlements.
result = qbash.integrations.active_campaign.get_custom_object_schema(
schema_id="…",
)
schema_idstrrequiredschema id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "schema": dict, "related": dict}A custom-object schema with field and relationship definitions. Availability depends on account entitlements. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-schema
get_custom_object_record()Get custom object record. A custom-object record with fields, relationships and externalId. Requires schema and record IDs, which may be UUIDs.
result = qbash.integrations.active_campaign.get_custom_object_record(
schema_id="…",
record_id="…",
)
schema_idstrrequiredschema id. Use the ID returned by the corresponding list operation.
record_idstrrequiredrecord id. Use the ID returned by the corresponding list operation.
includestr | NonedefaultNoneDocumented related-record include paths, comma-separated. Returned sibling collections are preserved in related.
{"status": "success", "record": dict, "related": dict}A custom-object record with fields, relationships and externalId. Requires schema and record IDs, which may be UUIDs. Records are forwarded without renaming their fields; IDs, counters and dates can be strings, and fields can be absent. Read optional fields with .get(). related preserves other top-level vendor collections. A missing or inaccessible resource returns an error. Reference: https://developers.activecampaign.com/reference/retrieve-a-record
list_campaign_opens()Read campaign report. Contacts who opened: subscriberid, email, tstamp and times. Opens may include privacy-proxy activity; this API exposes no Apple Privacy filter.
result = qbash.integrations.active_campaign.list_campaign_opens(
campaign_id="…",
)
campaign_idstrrequiredcampaign id. Use the ID returned by the corresponding list operation.
message_idstr | NonedefaultNoneMessage ID for a split campaign; omitted means the vendor default.
pageintdefault1Legacy page number, starting at 1. The documented page size is 20, but some accounts return larger pages; use next_page and allow a final empty-page check.
sort_directionstr | NonedefaultNoneASC or DESC by open timestamp; default DESC.
{"status": "success", "opens": list[dict], "count": int, "next_page": int | None, "has_more": bool}Contacts who opened: subscriberid, email, tstamp and times. Opens may include privacy-proxy activity; this API exposes no Apple Privacy filter. Uses the existing API URL/token against the supported v1 /admin/api.php report API; no separate secret is needed. Fields remain vendor-named. Continue with page=next_page until None; an exactly full last page may require an empty-page request. count is the response length, not a total. Empty reports return an empty collection. The exact legacy "Failed: Nothing is returned" response is normalized to empty; other result_code=0 responses remain errors. Reference: https://www.activecampaign.com/api/example.php?call=campaign_report_open_list
list_campaign_link_clicks()Read campaign report. Each link has id, link URL, a_unique and a_total; info contains subscriberid, email, tstamp and times for its clickers. Numeric-keyed legacy collections are converted to lists. This endpoint has no documented page parameter.
result = qbash.integrations.active_campaign.list_campaign_link_clicks(
campaign_id="…",
)
campaign_idstrrequiredcampaign id. Use the ID returned by the corresponding list operation.
message_idstr | NonedefaultNoneMessage ID for a split campaign; omitted means the vendor default.
{"status": "success", "links": list[dict], "count": int}Each link has id, link URL, a_unique and a_total; info contains subscriberid, email, tstamp and times for its clickers. Numeric-keyed legacy collections are converted to lists. This endpoint has no documented page parameter. Uses the existing API URL/token against the supported v1 /admin/api.php report API; no separate secret is needed. Fields remain vendor-named. count is the response length, not a total. Empty reports return an empty collection. The exact legacy "Failed: Nothing is returned" response is normalized to empty; other result_code=0 responses remain errors. Reference: https://www.activecampaign.com/api/example.php?call=campaign_report_link_list
list_campaign_non_openers()Read campaign report. Contacts recorded as not opening this campaign, identified by subscriberid/email where returned.
result = qbash.integrations.active_campaign.list_campaign_non_openers(
campaign_id="…",
)
campaign_idstrrequiredcampaign id. Use the ID returned by the corresponding list operation.
message_idstr | NonedefaultNoneMessage ID for a split campaign; omitted means the vendor default.
pageintdefault1Legacy page number, starting at 1. The documented page size is 20, but some accounts return larger pages; use next_page and allow a final empty-page check.
{"status": "success", "non_openers": list[dict], "count": int, "next_page": int | None, "has_more": bool}Contacts recorded as not opening this campaign, identified by subscriberid/email where returned. Uses the existing API URL/token against the supported v1 /admin/api.php report API; no separate secret is needed. Fields remain vendor-named. Continue with page=next_page until None; an exactly full last page may require an empty-page request. count is the response length, not a total. Empty reports return an empty collection. The exact legacy "Failed: Nothing is returned" response is normalized to empty; other result_code=0 responses remain errors. Reference: https://www.activecampaign.com/api/example.php?call=campaign_report_unopen_list
list_campaign_bounces()Read campaign report. Bounced contacts and vendor-provided bounce details. Read fields with .get(); payload fields vary.
result = qbash.integrations.active_campaign.list_campaign_bounces(
campaign_id="…",
)
campaign_idstrrequiredcampaign id. Use the ID returned by the corresponding list operation.
message_idstr | NonedefaultNoneMessage ID for a split campaign; omitted means the vendor default.
pageintdefault1Legacy page number, starting at 1. The documented page size is 20, but some accounts return larger pages; use next_page and allow a final empty-page check.
{"status": "success", "bounces": list[dict], "count": int, "next_page": int | None, "has_more": bool}Bounced contacts and vendor-provided bounce details. Read fields with .get(); payload fields vary. Uses the existing API URL/token against the supported v1 /admin/api.php report API; no separate secret is needed. Fields remain vendor-named. Continue with page=next_page until None; an exactly full last page may require an empty-page request. count is the response length, not a total. Empty reports return an empty collection. The exact legacy "Failed: Nothing is returned" response is normalized to empty; other result_code=0 responses remain errors. Reference: https://www.activecampaign.com/api/example.php?call=campaign_report_bounce_list
list_campaign_unsubscribes()Read campaign report. Campaign-specific unsubscribed contacts with email, subscriberid, tstamp and reasons when returned.
result = qbash.integrations.active_campaign.list_campaign_unsubscribes(
campaign_id="…",
)
campaign_idstrrequiredcampaign id. Use the ID returned by the corresponding list operation.
message_idstr | NonedefaultNoneMessage ID for a split campaign; omitted means the vendor default.
pageintdefault1Legacy page number, starting at 1. The documented page size is 20, but some accounts return larger pages; use next_page and allow a final empty-page check.
{"status": "success", "unsubscribes": list[dict], "count": int, "next_page": int | None, "has_more": bool}Campaign-specific unsubscribed contacts with email, subscriberid, tstamp and reasons when returned. Uses the existing API URL/token against the supported v1 /admin/api.php report API; no separate secret is needed. Fields remain vendor-named. Continue with page=next_page until None; an exactly full last page may require an empty-page request. count is the response length, not a total. Empty reports return an empty collection. The exact legacy "Failed: Nothing is returned" response is normalized to empty; other result_code=0 responses remain errors. Reference: https://www.activecampaign.com/api/example.php?call=campaign_report_unsubscription_list
list_campaign_forwards()Read campaign report. Forward records with subscriberid, messageid, email_from, email_to, tstamp and a_times when returned. This endpoint has no documented page parameter.
result = qbash.integrations.active_campaign.list_campaign_forwards(
campaign_id="…",
)
campaign_idstrrequiredcampaign id. Use the ID returned by the corresponding list operation.
message_idstr | NonedefaultNoneMessage ID for a split campaign; omitted means the vendor default.
{"status": "success", "forwards": list[dict], "count": int}Forward records with subscriberid, messageid, email_from, email_to, tstamp and a_times when returned. This endpoint has no documented page parameter. Uses the existing API URL/token against the supported v1 /admin/api.php report API; no separate secret is needed. Fields remain vendor-named. count is the response length, not a total. Empty reports return an empty collection. The exact legacy "Failed: Nothing is returned" response is normalized to empty; other result_code=0 responses remain errors. Reference: https://www.activecampaign.com/api/example.php?call=campaign_report_forward_list
get_campaign_report_totals()Read campaign report. Aggregate campaign/message totals. uniqueopens counts openers; uniquelinkclicks and subscriberclicks are distinct metrics. Supply both start_date and end_date for a time window.
result = qbash.integrations.active_campaign.get_campaign_report_totals(
campaign_id="…",
)
campaign_idstrrequiredcampaign id. Use the ID returned by the corresponding list operation.
message_idstr | NonedefaultNoneMessage ID for a split campaign; omitted means the vendor default.
start_datestr | NonedefaultNoneISO-8601 start of reporting window; requires end_date.
end_datestr | NonedefaultNoneISO-8601 end of reporting window; requires start_date.
{"status": "success", "totals": dict}Aggregate campaign/message totals. uniqueopens counts openers; uniquelinkclicks and subscriberclicks are distinct metrics. Supply both start_date and end_date for a time window. Uses the existing API URL/token against the supported v1 /admin/api.php report API; no separate secret is needed. Fields remain vendor-named. totals is a vendor-named map, with numeric counters often returned as strings. Vendor result_code=0 remains an error. Reference: https://www.activecampaign.com/api/example.php?call=campaign_report_totals
search_persons()Search for persons in Affinity by name or email.
result = qbash.integrations.affinity.search_persons(
term="…",
page_size=10,
)
termstr | NonedefaultNoneSearch term (name or email) to filter persons.
page_sizeint | NonedefaultNoneNumber of results per page.
page_tokenstr | NonedefaultNonePagination token from a previous response.
with_interaction_datesbooldefaultFalseInclude first and last interaction dates in results.
with_opportunitiesbooldefaultFalseInclude associated opportunities in results.
with_current_organizationsbooldefaultFalseInclude current organization associations in results.
{"status": "success", "persons": list[dict], "person_count": int, "next_page_token": str | None}Returns one page of persons. person_count is the size of that page rather than the number of matches in Affinity, and next_page_token is None on the last page. Pass the token back as page_token to read the next page. Each person carries id, first_name, last_name, primary_email, emails, organization_ids and type, and any of those Affinity returns as null is left out of the record, so read them with .get(). The with_interaction_dates, with_opportunities and with_current_organizations flags request interaction_dates, opportunities and current_organizations, which are kept when supplied. Embedded list_entries are also kept.
get_person()Get detailed information for a specific Affinity person by ID.
result = qbash.integrations.affinity.get_person(
person_id=0,
)
person_idintrequiredAffinity person ID.
with_interaction_datesbooldefaultFalseInclude first and last interaction dates.
with_opportunitiesbooldefaultFalseInclude associated opportunities.
with_current_organizationsbooldefaultFalseInclude current organization associations.
{"status": "success", "person": dict, "next_page_token": None}next_page_token is always None here because the single person endpoint does not paginate. The person record carries id, first_name, last_name, primary_email, emails, organization_ids and type, and any of those Affinity returns as null is left out, so read them with .get(). The with_interaction_dates, with_opportunities and with_current_organizations flags request interaction_dates, opportunities and current_organizations, which are kept when supplied. Embedded list_entries are also kept.
search_organizations()Search for organizations in Affinity by domain or name.
result = qbash.integrations.affinity.search_organizations(
term="…",
exact_match=True,
)
termstr | NonedefaultNoneSearch term (name or domain) to filter organizations.
exact_matchbooldefaultFalseMatch the term exactly instead of a partial/fuzzy match.
page_sizeint | NonedefaultNoneNumber of results per page.
page_tokenstr | NonedefaultNonePagination token from a previous response.
with_interaction_datesbooldefaultFalseInclude first and last interaction dates.
with_interaction_personsbooldefaultFalseInclude interaction persons in results.
{"status": "success", "organizations": list[dict], "organization_count": int, "next_page_token": str | None}Returns one page of organizations. next_page_token is None on the last page and is passed back as page_token to read the next page. exact_match filters the page after it arrives, so organization_count can be smaller than the page Affinity sent, and an exact match that sits on a later page is only found by following the token. Each organization carries id, name, domain, domains, person_ids and type, and any of those Affinity returns as null is left out, so read them with .get(). The with_interaction_dates and with_interaction_persons flags request interaction_dates and interaction_persons, which are kept when supplied. Embedded list_entries are also kept.
get_organization()Get detailed information for a specific Affinity organization by ID.
result = qbash.integrations.affinity.get_organization(
organization_id=0,
)
organization_idintrequiredAffinity organization ID.
with_interaction_datesbooldefaultFalseInclude first and last interaction dates.
with_interaction_personsbooldefaultFalseInclude interaction persons in results.
{"status": "success", "organization": dict, "next_page_token": None}next_page_token is always None here because the single organization endpoint does not paginate. The organization record carries id, name, domain, domains, person_ids and type, and any of those Affinity returns as null is left out, so read them with .get(). The with_interaction_dates and with_interaction_persons flags request interaction_dates and interaction_persons, which are kept when supplied. Embedded list_entries are also kept.
get_field_values()Get field values for an entity
result = qbash.integrations.affinity.get_field_values(
organization_id=0,
person_id=0,
)
organization_idintOrganization ID
person_idintPerson ID
list_entry_idintList entry ID
opportunity_idintOpportunity ID
field_idslist[int] | NonedefaultNoneKeep values with these field IDs.
field_nameslist[str] | NonedefaultNoneResolve and filter exact field names. May cost an extra definitions request.
{"status": "success", "field_values": list[dict]}Each field value carries id, field_id, field_name, value and list_entry_id, and all five keys are always present. value is None when the field is unset in Affinity, which is why these records keep their nulls where other Affinity modes drop them. The response covers every field value for the entity and carries no pagination key. Specify exactly one entity ID. field_ids filters locally. field_names resolves missing names via GET /fields with case-sensitive matching at the cost of one extra request. Name filtering takes precedence.
get_list_entries()Get entries for a list
result = qbash.integrations.affinity.get_list_entries(
list_id=0,
)
list_idintrequiredList ID
with_field_valuesbooldefaultFalseMerge each entry's field values onto the entry.
field_nameslist[str] | NonedefaultNoneKeep only merged field values with these names. The match is case sensitive.
page_sizeintResults per page
page_tokenstr | NonePagination token
{"status": "success", "list_entries": list[dict], "next_page_token": str | None}Returns one page of entries. next_page_token is None on the last page and is passed back as page_token to read the next page. Each entry carries id, list_id, entity_type, entity_id, entity and created_at, and any of those Affinity returns as null is left out, so read them with .get(). Field values are merged onto each entry as field_values only when with_field_values is True, which costs one extra call per entry and stops after 50 entries, and the entries past that cap carry the string not_fetched instead. Failed field reads carry not_fetched and field_values_error. Without the flag no field values come back, call get_field_values with list_entry_id for those.
get_organization_list_entries()Get all list entries for an organization
result = qbash.integrations.affinity.get_organization_list_entries(
organization_id=0,
)
organization_idintrequiredOrganization ID
with_field_valuesbooldefaultFalseMerge each entry's field values onto the entry, one extra call per entry.
field_nameslist[str] | NonedefaultNoneKeep only merged field values with these names. The match is case sensitive.
{"status": "success", "list_entries": list[dict], "truncated": True | None}The truncated key is present only when it is True, so read it with .get() and treat a missing key as a complete result. It is set on the fallback path, which runs when Affinity does not embed list entries in the organization record. That path scans at most 20 organization lists and one page of 500 entries per list, and it sets truncated when a list is past the cap, a continuation page remains unread or a per-list read fails. Entries have the same fields as get_list_entries. With with_field_values set, field_values is merged onto each entry at the cost of one extra call per entry and stops after 50 entries, with the entries past that cap carrying the string not_fetched instead. Failed field reads also carry not_fetched and field_values_error. No pagination key is returned, so the entries in one response are all this mode can give.
get_fields()Read global and list field definitions, optionally filtered by list or field type
result = qbash.integrations.affinity.get_fields(
entity_type=0,
value_type=0,
)
entity_typeint | NonedefaultNone0 person, 1 organization, 8 opportunity.
value_typeint | NonedefaultNoneVendor value type, 0–7.
with_modified_namesbooldefaultFalsePrefix field names with their list name.
exclude_dropdown_optionsbooldefaultFalseOmit dropdown options.
list_idint | NonedefaultNoneOptional list ID. Omit to return global and list fields.
{"status": "success", "fields": list[dict]}Each field carries id, name, list_id, value_type, allows_multiple, dropdown_options, enrichment_source and track_changes, and any of those Affinity returns as null is left out, so read them with .get(). dropdown_options is there only for fields that have options. The whole list of definitions comes back in one call with no pagination key.
create_field_value()Create a field value on an entity
result = qbash.integrations.affinity.create_field_value(
field_id=0,
entity_id=0,
value=0.7,
)
field_idintrequiredField ID
entity_idintrequiredEntity ID (organization, person, or opportunity)
valuestr | int | float | dict | list | NonerequiredField value (type depends on field)
list_entry_idintList entry ID (required for list-specific fields)
{"status": "success", "field_value": dict}field_value carries id, field_id, field_name, value and list_entry_id, and all five keys are always present. value is what Affinity stored and can be None. Keep id if you plan to update or delete the value later.
update_field_value()Update an existing field value
result = qbash.integrations.affinity.update_field_value(
field_value_id=0,
value=0.7,
)
field_value_idintrequiredField value ID to update
valuestr | int | float | dict | list | NonerequiredNew value
{"status": "success", "field_value": dict}field_value carries id, field_id, field_name, value and list_entry_id, and all five keys are always present. value is what Affinity stored and can be None.
delete_field_value()Delete a field value
result = qbash.integrations.affinity.delete_field_value(
field_value_id=0,
)
field_value_idintrequiredField value ID to delete
{"status": "success", "deleted": True}deleted is always True on success and nothing about the removed value comes back, so read anything you need from the record before deleting it.
create_list_entry()Add an entity to a list
result = qbash.integrations.affinity.create_list_entry(
list_id=0,
entity_id=0,
)
list_idintrequiredList ID
entity_idintrequiredEntity ID to add
{"status": "success", "list_entry": dict}list_entry carries id, list_id, entity_type, entity_id, entity and created_at, and any of those Affinity returns as null is left out, so read them with .get(). Keep id to set list specific field values on the new entry.
delete_list_entry()Remove an entity from a list
result = qbash.integrations.affinity.delete_list_entry(
list_entry_id=0,
list_id=0,
)
list_entry_idintrequiredList entry ID to remove
list_idintrequiredList ID
{"status": "success", "deleted": True}deleted is always True on success and nothing about the removed entry comes back. Removing a list entry does not delete the underlying person or organization.
list_lists()List the Affinity lists this API key can reach.
result = qbash.integrations.affinity.list_lists()
{"status": "success", "lists": list[dict]}Every list comes back in one response with no pagination key. Each list carries id, name, type and public, and any of those Affinity returns as null is left out, so read them with .get(). type is Affinity's numeric entity type: 0 person, 1 organization, 8 opportunity. It tells you which kind of entity the list entries point at. Field definitions are not part of this shape, call get_list or get_fields with the list id for those.
get_list()Get one Affinity list with its field definitions.
result = qbash.integrations.affinity.get_list(
list_id=0,
)
list_idintrequiredList ID
{"status": "success", "list": dict}list carries id, name, type, public and fields. Any of the first four that Affinity returns as null is left out, so read them with .get(). fields holds one entry per column with id, name, value_type and list_id, again with nulls dropped. Field definitions retain dropdown_options, allows_multiple and track_changes when provided.
list_opportunities()List Affinity opportunities.
result = qbash.integrations.affinity.list_opportunities(
term="…",
page_size=10,
)
termstr | NonedefaultNoneSearch opportunity names.
page_sizeint | NonedefaultNoneNumber of results per page.
page_tokenstr | NonedefaultNonePagination token from a previous response.
{"status": "success", "opportunities": list[dict], "next_page_token": str | None}Returns one page of opportunities. next_page_token is None on the last page. Pass the token back as page_token to read the next page. Each opportunity carries id, name, person_ids, organization_ids and list_entries, and any of those Affinity returns as null is left out, so read them with .get(). term searches opportunity names. No list filter is available. V1 membership is returned in list_entries rather than a top-level list_id.
get_opportunity()Get one Affinity opportunity by ID.
result = qbash.integrations.affinity.get_opportunity(
opportunity_id=0,
)
opportunity_idintrequiredOpportunity ID
{"status": "success", "opportunity": dict}opportunity carries id, name, person_ids, organization_ids and list_entries, and any of those Affinity returns as null is left out, so read them with .get(). The single opportunity endpoint does not paginate, so no token comes back. Field values live apart from the record, call get_field_values with opportunity_id for those.
whoami()Show which Affinity account this API key belongs to.
result = qbash.integrations.affinity.whoami()
{"status": "success", "account": dict}Reads GET /v2/auth/whoami with V2 version 2026-09-17. account carries tenant_name, user_id, user_email (from user.emailAddress) and grant_type, and any of those Affinity returns as null is left out, so read them with .get(). The call takes no parameters and reads one endpoint, which makes it a cheap way to check that the stored key still works before a longer run.
get_organization_notes()Get the notes attached to an organization.
result = qbash.integrations.affinity.get_organization_notes(
organization_id=0,
)
organization_idintrequiredOrganization ID
page_sizeint | NonedefaultNoneNumber of results per page.
page_tokenstr | NonedefaultNonePagination token from a previous response.
{"status": "success", "notes": list[dict], "next_page_token": str | None}Returns one page of notes in the order Affinity sends them. next_page_token is None on the last page. Pass the token back as page_token to read the next page. Each note carries id, type, content, creator_id, created_at, person_ids, organization_ids and opportunity_ids, and any of those Affinity returns as null is left out, so read them with .get(). content is the stored note body, which is HTML for notes written in the Affinity editor.
get_person_notes()Get the notes attached to a person.
result = qbash.integrations.affinity.get_person_notes(
person_id=0,
)
person_idintrequiredPerson ID
page_sizeint | NonedefaultNoneNumber of results per page.
page_tokenstr | NonedefaultNonePagination token from a previous response.
{"status": "success", "notes": list[dict], "next_page_token": str | None}Returns one page of notes in the order Affinity sends them. next_page_token is None on the last page. Pass the token back as page_token to read the next page. Each note carries id, type, content, creator_id, created_at, person_ids, organization_ids and opportunity_ids, and any of those Affinity returns as null is left out, so read them with .get(). content is the stored note body, which is HTML for notes written in the Affinity editor.
get_organization_interactions()Read one interaction type for an organization within an explicit time range of at most one year. Preserve type and range while paginating.
result = qbash.integrations.affinity.get_organization_interactions(
organization_id=0,
type=0,
)
organization_idintrequiredOrganization ID
typestr | intrequiredmeeting/0, call/1, chat/2 or email/3.
start_timestr | NonedefaultNoneISO 8601 lower time bound. Must precede end_time, at most one year apart.
end_timestr | NonedefaultNoneISO 8601 upper time bound, at most one year after start_time.
page_sizeint | NonedefaultNoneResults per page, 1–100.
page_tokenstr | NonedefaultNonePrevious next_page_token with the same entity, type and range.
internal_person_idint | NonedefaultNoneFilter to interactions involving this internal person.
logging_typeint | NonedefaultNone0 automatically logged, 1 manually logged.
directionint | NonedefaultNone0 incoming, 1 outgoing for chat/email.
min_interaction_datestr | NonedefaultNoneLegacy alias for start_time. ISO 8601 timestamp.
max_interaction_datestr | NonedefaultNoneLegacy alias for end_time. ISO 8601 timestamp.
{"status": "success", "interactions": list[dict], "next_page_token": str | None}Meeting=0, call=1, chat=2, email=3. Returns email recipients, meeting attendees and note IDs where available. Null attributes are omitted, so use .get(). Dates are ISO 8601 timestamps. Pass next_page_token as page_token with unchanged filters. Existing min_interaction_date and max_interaction_date remain aliases for start_time/end_time.
get_organization_field_values()Get the global field values on an organization.
result = qbash.integrations.affinity.get_organization_field_values(
organization_id=0,
)
organization_idintrequiredOrganization ID
field_nameslist[str] | NonedefaultNoneKeep only field values with these names. The match is case sensitive.
{"status": "success", "field_values": list[dict]}Each field value carries id, field_id, field_name, value and list_entry_id, and all five keys are always present. value is None when the field is unset in Affinity. Most Affinity fields belong to a list rather than to the organization itself, so this covers the global fields only and get_organization_list_entries returns the per list values. field_names resolves missing names through GET /fields and filters locally with case-sensitive matches. The response carries no pagination key.
get_person_field_values()Get the global field values on a person.
result = qbash.integrations.affinity.get_person_field_values(
person_id=0,
)
person_idintrequiredPerson ID
field_nameslist[str] | NonedefaultNoneKeep only field values with these names. The match is case sensitive.
{"status": "success", "field_values": list[dict]}Each field value carries id, field_id, field_name, value and list_entry_id, and all five keys are always present. value is None when the field is unset in Affinity. Most Affinity fields belong to a list rather than to the person, so this covers the global fields only and get_person_list_entries returns the per list values. field_names resolves missing names through GET /fields and filters locally with case-sensitive matches. The response carries no pagination key.
get_person_list_entries()Get all list entries for a person, with the field values on each entry.
result = qbash.integrations.affinity.get_person_list_entries(
person_id=0,
)
person_idintrequiredPerson ID
field_nameslist[str] | NonedefaultNoneKeep only merged field values with these names. The match is case sensitive.
{"status": "success", "list_entries": list[dict], "truncated": True | None}The truncated key is present only when it is True, so read it with .get() and treat a missing key as a complete result. It is set on the fallback path, which runs when Affinity does not embed list entries in the person record. That path scans at most 20 person lists and one page of 500 entries per list, and it sets truncated when a list is past the cap, a continuation page remains unread or a per list read fails. Each entry carries id, list_id, entity_type, entity_id, entity, created_at and field_values, and any of those Affinity returns as null is left out, so read them with .get(). The field values are merged onto each entry at the cost of one extra call per entry and stop after 50 entries, and entries past that cap carry the string not_fetched instead. field_names keeps only the merged values with those names, case sensitive. No pagination key is returned.
get_rate_limit()Read Affinity account monthly and user minute API quota usage.
result = qbash.integrations.affinity.get_rate_limit()
{"status": "success", "rate": dict}Returns org_monthly and api_key_per_minute buckets with limit, used, remaining and reset. reset is seconds until reset, not a timestamp. V1 and V2 share account quotas. This endpoint is exempt from the monthly quota.
get_note()Read one Affinity note by ID, including related records.
result = qbash.integrations.affinity.get_note(
note_id=0,
)
note_idintrequiredAffinity note_id.
{"status": "success", "note": dict}Returns one note, including content, type, related entity IDs, parent_id and interaction metadata where available. Content may be plain text, HTML or Markdown. Null fields are omitted, so use .get(). Dates are ISO 8601 strings. Does not paginate.
get_list_entry()Read one Affinity list entry by list and entry IDs.
result = qbash.integrations.affinity.get_list_entry(
list_id=0,
list_entry_id=0,
)
list_idintrequiredAffinity list_id.
list_entry_idintrequiredAffinity list_entry_id.
{"status": "success", "list_entry": dict}Returns entry metadata and the embedded entity. Null fields are omitted, so use .get(). Field values require get_field_values(list_entry_id=...). Does not paginate.
get_opportunity_notes()Read one page of notes attached to an Affinity opportunity.
result = qbash.integrations.affinity.get_opportunity_notes(
opportunity_id=0,
)
opportunity_idintrequiredAffinity opportunity_id.
page_sizeint | NonedefaultNoneResults per page, 1–500.
page_tokenstr | NonedefaultNonePrevious next_page_token, keeping other filters unchanged.
{"status": "success", "notes": list[dict], "next_page_token": str | None}Content may be HTML, Markdown or plain text. Null note attributes are omitted. Pass next_page_token as page_token with the same opportunity_id. A bare-array vendor response is complete and has no token.
list_entity_files()Read Affinity file metadata, optionally filtered by an entity. Does not download files.
result = qbash.integrations.affinity.list_entity_files(
person_id=0,
organization_id=0,
)
person_idint | NonedefaultNoneOptional entity filter.
organization_idint | NonedefaultNoneOptional entity filter.
opportunity_idint | NonedefaultNoneOptional entity filter.
page_sizeint | NonedefaultNoneResults per page, 1–500.
page_tokenstr | NonedefaultNonePrevious next_page_token, keeping other filters unchanged.
{"status": "success", "entity_files": list[dict], "next_page_token": str | None}Raw V1 file metadata includes size in bytes, IDs, name, uploader_id and created_at as an ISO 8601 timestamp. Attributes may be null. Follow next_page_token with the same filters even if a page is empty.
list_reminders()Read Affinity reminders with entity, owner, status and due-date filters.
result = qbash.integrations.affinity.list_reminders(
person_id=0,
organization_id=0,
)
person_idint | NonedefaultNoneOptional entity filter.
organization_idint | NonedefaultNoneOptional entity filter.
opportunity_idint | NonedefaultNoneOptional entity filter.
creator_idint | NonedefaultNoneInternal person ID.
owner_idint | NonedefaultNoneInternal person ID.
completer_idint | NonedefaultNoneInternal person ID.
typeint | NonedefaultNone0 one-time, 1 recurring.
reset_typeint | NonedefaultNone0 email/meeting, 1 email, 2 meeting. Required for recurring type.
statusint | NonedefaultNone0 completed, 1 active, 2 overdue.
due_beforestr | NonedefaultNoneISO 8601 upper due-date bound.
due_afterstr | NonedefaultNoneISO 8601 lower due-date bound.
page_sizeint | NonedefaultNoneResults per page, 1–500.
page_tokenstr | NonedefaultNonePrevious next_page_token, keeping other filters unchanged.
{"status": "success", "reminders": list[dict], "next_page_token": str | None}Raw V1 reminder records include creator, owner, completer and related person/organization/opportunity objects, some of which may be null. Dates are ISO 8601. Pass next_page_token as page_token, preserving filters. type=0 is one-time, type=1 recurring. Recurring type requires reset_type.
get_field_value_changes()Read Affinity field history. Forward continuation requires changed_after and order_by=asc.
result = qbash.integrations.affinity.get_field_value_changes(
field_id=0,
)
field_idintrequiredAffinity field_id.
person_idint | NonedefaultNoneOptional entity filter.
organization_idint | NonedefaultNoneOptional entity filter.
opportunity_idint | NonedefaultNoneOptional entity filter.
list_entry_idint | NonedefaultNoneOptional list entry filter.
action_typeint | NonedefaultNone0 create, 1 delete, 2 update.
changed_afterstr | NonedefaultNoneInclusive ISO 8601 timestamp.
order_bystrdefault"desc"asc or desc.
after_idint | NonedefaultNoneLast change ID. Requires changed_after and order_by=asc.
limitintdefault100Maximum returned changes, 1–500.
{"status": "success", "changes": list[dict], "next_after_id": int | None}Only fields with track_changes=True support history. Returns raw V1 records including changed_at (ISO 8601), action_type (0 create, 1 delete, 2 update), changer and value. Deleted changes hold the old value. Optional entity filters are mutually exclusive. For bounded forward pagination use changed_after, order_by='asc' and limit; pass next_after_id as after_id with the same filters. A full page may require a final empty request. Descending reads do not produce a cursor.
get_person_interactions()Read one interaction type for an Affinity entity within an explicit time range of at most one year.
result = qbash.integrations.affinity.get_person_interactions(
person_id=0,
type=0,
start_time="…",
end_time="…",
)
person_idintrequiredAffinity person_id.
typestr | intrequiredmeeting/0, call/1, chat/2 or email/3.
start_timestrrequiredISO 8601 lower time bound. Must precede end_time, at most one year apart.
end_timestrrequiredISO 8601 upper time bound, at most one year after start_time.
page_sizeint | NonedefaultNoneResults per page, 1–100.
page_tokenstr | NonedefaultNonePrevious next_page_token with the same entity, type and range.
internal_person_idint | NonedefaultNoneFilter to interactions involving this internal person.
logging_typeint | NonedefaultNone0 automatically logged, 1 manually logged.
directionint | NonedefaultNone0 incoming, 1 outgoing for chat/email.
{"status": "success", "interactions": list[dict], "next_page_token": str | None}Meeting=0, call=1, chat=2, email=3. The person endpoint requires an external person. Preserve entity, type and time bounds while following next_page_token. Email recipients and meeting attendees are retained. Null attributes are omitted. Date/time values are ISO 8601.
get_opportunity_interactions()Read one interaction type for an Affinity entity within an explicit time range of at most one year.
result = qbash.integrations.affinity.get_opportunity_interactions(
opportunity_id=0,
type=0,
start_time="…",
end_time="…",
)
opportunity_idintrequiredAffinity opportunity_id.
typestr | intrequiredmeeting/0, call/1, chat/2 or email/3.
start_timestrrequiredISO 8601 lower time bound. Must precede end_time, at most one year apart.
end_timestrrequiredISO 8601 upper time bound, at most one year after start_time.
page_sizeint | NonedefaultNoneResults per page, 1–100.
page_tokenstr | NonedefaultNonePrevious next_page_token with the same entity, type and range.
internal_person_idint | NonedefaultNoneFilter to interactions involving this internal person.
logging_typeint | NonedefaultNone0 automatically logged, 1 manually logged.
directionint | NonedefaultNone0 incoming, 1 outgoing for chat/email.
{"status": "success", "interactions": list[dict], "next_page_token": str | None}Meeting=0, call=1, chat=2, email=3. The person endpoint requires an external person. Preserve entity, type and time bounds while following next_page_token. Email recipients and meeting attendees are retained. Null attributes are omitted. Date/time values are ISO 8601.
list_saved_views()Read metadata on accessible Affinity saved views for a list.
result = qbash.integrations.affinity.list_saved_views(
list_id=0,
)
list_idintrequiredAffinity list_id.
limitint | NonedefaultNoneResults per page, 1–100.
cursorstr | NonedefaultNonePrevious next_cursor. The connector never follows nextUrl directly.
{"status": "success", "saved_views": list[dict], "next_cursor": str | None}Returns name, type and createdAt. Only accessible views appear. Uses V2 version 2026-09-17 and the existing API key. Raw vendor camelCase attributes and nulls are preserved. Pass next_cursor as cursor with unchanged filters. Requires API access on the customer's plan and resource permissions.
get_saved_view_entries()Read Affinity sheet saved-view entries with inline field data. Requires Export data from Lists permission.
result = qbash.integrations.affinity.get_saved_view_entries(
list_id=0,
view_id=0,
)
list_idintrequiredAffinity list_id.
view_idintrequiredAffinity view_id.
limitint | NonedefaultNoneResults per page, 1–100.
cursorstr | NonedefaultNonePrevious next_cursor. The connector never follows nextUrl directly.
{"status": "success", "list_entries": list[dict], "next_cursor": str | None}Respects saved-view filters and selected columns, but does not preserve view sort order. Sheet views only. Includes typed field values inline without per-entry requests. Uses V2 version 2026-09-17 and the existing API key. Raw vendor camelCase attributes and nulls are preserved. Pass next_cursor as cursor with unchanged filters. Requires API access on the customer's plan and resource permissions.
get_person_enriched_fields()Read Affinity V2 enriched, global and relationship-intelligence fields on a person. Vendor beta endpoint.
result = qbash.integrations.affinity.get_person_enriched_fields(
person_id=0,
)
person_idintrequiredAffinity person_id.
idslist[str] | NonedefaultNoneV2 field IDs, mutually exclusive with types.
typeslist[str] | NonedefaultNoneenriched, global or relationship-intelligence. Mutually exclusive with ids.
limitint | NonedefaultNoneResults per page, 1–100.
cursorstr | NonedefaultNonePrevious next_cursor. The connector never follows nextUrl directly.
{"status": "success", "fields": list[dict], "next_cursor": str | None}Includes field IDs, names and typed value objects. List fields are excluded. This vendor endpoint is in beta. Uses V2 version 2026-09-17 and the existing API key. Raw vendor camelCase attributes and nulls are preserved. Pass next_cursor as cursor with unchanged filters. Requires API access on the customer's plan and resource permissions.
get_organization_enriched_fields()Read Affinity V2 enriched, global and relationship-intelligence fields on a company. Vendor beta endpoint.
result = qbash.integrations.affinity.get_organization_enriched_fields(
organization_id=0,
)
organization_idintrequiredAffinity organization_id.
idslist[str] | NonedefaultNoneV2 field IDs, mutually exclusive with types.
typeslist[str] | NonedefaultNoneenriched, global or relationship-intelligence. Mutually exclusive with ids.
limitint | NonedefaultNoneResults per page, 1–100.
cursorstr | NonedefaultNonePrevious next_cursor. The connector never follows nextUrl directly.
{"status": "success", "fields": list[dict], "next_cursor": str | None}organization_id is the V2 companyId. List fields are excluded. Values are typed objects. This vendor endpoint is in beta. Uses V2 version 2026-09-17 and the existing API key. Raw vendor camelCase attributes and nulls are preserved. Pass next_cursor as cursor with unchanged filters. Requires API access on the customer's plan and resource permissions.
get_person_relationships()Read Affinity relationship scores and LinkedIn connections for a person.
result = qbash.integrations.affinity.get_person_relationships(
person_id=0,
)
person_idintrequiredAffinity person_id.
filterstr | NonedefaultNoneVendor V2 filter expression.
order_bylist[str] | NonedefaultNoneinteractionScore or -interactionScore.
limitint | NonedefaultNoneResults per page, 1–100.
cursorstr | NonedefaultNonePrevious next_cursor. The connector never follows nextUrl directly.
{"status": "success", "relationships": list[dict], "next_cursor": str | None}Includes person1, person2, interactionScore and linkedIn. Scores range from 0 to 1, may be null on LinkedIn-only connections and estimate communication strength. Uses V2 version 2026-09-17 and the existing API key. Raw vendor camelCase attributes and nulls are preserved. Pass next_cursor as cursor with unchanged filters. Requires API access on the customer's plan and resource permissions.
get_organization_relationships()Read Affinity relationship scores and LinkedIn connections for a company.
result = qbash.integrations.affinity.get_organization_relationships(
organization_id=0,
)
organization_idintrequiredAffinity organization_id.
filterstr | NonedefaultNoneVendor V2 filter expression.
order_bylist[str] | NonedefaultNoneinteractionScore or -interactionScore.
limitint | NonedefaultNoneResults per page, 1–100.
cursorstr | NonedefaultNonePrevious next_cursor. The connector never follows nextUrl directly.
{"status": "success", "relationships": list[dict], "next_cursor": str | None}organization_id is the V2 companyId. Includes person1, person2, interactionScore and linkedIn. Scores range from 0 to 1, may be null and estimate communication strength. Uses V2 version 2026-09-17 and the existing API key. Raw vendor camelCase attributes and nulls are preserved. Pass next_cursor as cursor with unchanged filters. Requires API access on the customer's plan and resource permissions.
domain_rating()Get Domain Rating and Ahrefs Rank for a domain.
result = qbash.integrations.ahrefs.domain_rating(
target="…",
)
targetstrrequiredDomain to analyse (e.g. example.com). Protocol prefix is stripped automatically.
datestr | NonedefaultNoneHistorical date in YYYY-MM-DD format.
{"status": "success", "target": str, "result": dict}"target" echoes the domain after the protocol prefix and any trailing slash have been stripped. "result" is Ahrefs' domain-rating body with every null valued key removed. The only key the service checks for is "domain_rating", and nothing here reads its value, so read the body with .get() rather than assuming a number or a nested map.
site_metrics()Get overall SEO metrics: traffic, keywords, backlinks.
result = qbash.integrations.ahrefs.site_metrics(
target="…",
)
targetstrrequiredDomain to analyse.
datestr | NonedefaultNoneHistorical date in YYYY-MM-DD format.
countrystr | NonedefaultNoneTwo-letter country code (e.g. us).
target_modestr | NonedefaultNoneTarget Mode (domain / prefix / exact / subdomains).
{"status": "success", "target": str, "metrics": dict | list | str | int | float | bool | None}"metrics" is the "metrics" value from Ahrefs with every null valued key removed when it is an object. Ahrefs decides what that value is and nothing here requires an object, so a null, a list or a scalar arrives unchanged and the type has to be checked before indexing. When it is an object, read its keys with .get(). The service asks for a fixed field list and no other field is reachable through this mode: org_keywords, org_keywords_1_3, org_traffic, paid_keywords, paid_traffic, domain_rating_avg, backlinks and referring_domains.
backlinks()List backlinks pointing to a domain.
result = qbash.integrations.ahrefs.backlinks(
target="…",
)
targetstrrequiredDomain to analyse.
limitintdefault100Maximum number of backlinks to return.
target_modestr | NonedefaultNoneTarget Mode (domain / prefix / exact / subdomains).
order_bystr | NonedefaultNoneOrder By field (e.g. domain_rating:desc).
{"status": "success", "target": str, "backlinks": list[dict], "count": int}"count" is the length of "backlinks" in this response, not how many backlinks Ahrefs holds for the target. Ahrefs removed offset pagination from API v3, so this mode can only return the first page and has no continuation token. Each record carries the fixed field list the service requests: url_from, url_to, domain_rating_source, ahrefs_rank_source, links_internal, links_external, anchor, nofollow, first_seen and last_seen. Any of those keys is dropped when its value is null, so read records with .get(). Client.backlinks/3 accepts a country filter that this mode does not set, so backlinks come back for every country.
referring_domains()Get referring domains with their metrics.
result = qbash.integrations.ahrefs.referring_domains(
target="…",
)
targetstrrequiredDomain to analyse.
limitintdefault100Maximum number of referring domains to return.
order_bystr | NonedefaultNoneOrder By field.
{"status": "success", "target": str, "referring_domains": list[dict], "count": int}"count" is the length of "referring_domains" in this response, not the target's total. Ahrefs removed offset pagination from API v3, so this mode can only return the first page and has no continuation token. Each record carries the fixed field list the service requests: domain_from, domain_rating, ahrefs_rank, backlinks, linked_domains, dofollow_linked_domains and dofollow_referring_pages. Any of those keys is dropped when its value is null, so read records with .get(). Client.referring_domains/3 accepts a country filter that this mode does not set, so referring domains come back for every country.
organic_keywords()Get organic keywords a domain ranks for.
result = qbash.integrations.ahrefs.organic_keywords(
target="…",
)
targetstrrequiredDomain to analyse.
countrystr | Nonedefault"us"Two-letter country code (e.g. us).
limitintdefault100Maximum number of keywords to return.
order_bystr | NonedefaultNoneOrder By field.
{"status": "success", "target": str, "keywords": list[dict], "count": int}"count" is the length of "keywords" in this response, not every keyword the target ranks for. Ahrefs removed offset pagination from API v3, so this mode can only return the first page and has no continuation token. Each record carries the fixed field list the service requests: keyword, position, volume, traffic, cpc, keyword_difficulty, ctr and sum_traffic. Any of those keys is dropped when its value is null, so read records with .get().
top_pages()Get top pages by organic traffic.
result = qbash.integrations.ahrefs.top_pages(
target="…",
)
targetstrrequiredDomain to analyse.
countrystr | Nonedefault"us"Two-letter country code (e.g. us).
limitintdefault100Maximum number of pages to return.
{"status": "success", "target": str, "pages": list[dict], "count": int}"count" is the length of "pages" in this response, not the target's page total. Ahrefs removed offset pagination from API v3, so this mode returns only the first page with no continuation token. Ordering is left to Ahrefs because this mode does not expose order_by. Each record carries the fixed field list the service requests: url, sum_traffic, top_keyword, top_keyword_volume, top_keyword_position and traffic_value. Any of those keys is dropped when its value is null, so read records with .get().
organic_competitors()Get organic competitors in SERPs.
result = qbash.integrations.ahrefs.organic_competitors(
target="…",
)
targetstrrequiredDomain to analyse.
countrystr | Nonedefault"us"Two-letter country code (e.g. us).
limitintdefault100Maximum number of competitors to return.
{"status": "success", "target": str, "competitors": list[dict], "count": int}"count" is the length of "competitors" in this response. This mode exposes no offset, so only the first "limit" competitors Ahrefs returns are reachable and raising "limit" is the only way to see more. Each record carries the fixed field list the service requests: domain, common_keywords, organic_keywords, organic_traffic and avg_position. Any of those keys is dropped when its value is null, so read records with .get().
keyword_overview()Get keyword metrics: volume, difficulty, CPC.
result = qbash.integrations.ahrefs.keyword_overview(
keywords=[...],
)
keywordslistrequiredKeywords to look up, as a list of strings or a comma-separated string.
countrystr | Nonedefault"us"Two-letter country code (e.g. us).
{"status": "success", "keywords": list[dict], "count": int}This is the one Ahrefs mode with no "target" key, since it queries Keywords Explorer rather than a domain. "count" is the length of "keywords" in this response. Nothing here matches rows back to the keywords that were passed, so match on each record's "keyword" field rather than on position. Each record carries the fixed field list the service requests: keyword, difficulty, volume, cpc, cps, return_rate, clicks, clicks_paid, clicks_organic and competing_pages. Any of those keys is dropped when its value is null, so read records with .get().
broken_backlinks()List broken backlinks pointing to a domain or URL.
result = qbash.integrations.ahrefs.broken_backlinks(
target="…",
)
targetstrrequiredDomain or URL to analyse.
limitintdefault100Maximum rows to return.
target_modestr | NonedefaultNoneTarget scope: exact, prefix, domain, or subdomains.
aggregationstr | NonedefaultNoneBacklink grouping: similar_links, 1_per_domain, or all.
order_bystr | NonedefaultNoneAhrefs result field and optional direction.
{"status": "success", "target": str, "backlinks": list[dict], "count": int}The length of `backlinks` is the number of rows in this response. Returns at most `limit` records; API v3 has no continuation token for this report. Null fields are removed from each record; use `.get()` for optional values. `http_code_target` is the status Ahrefs observed for the target page, and may be null. API-unit cost depends on returned rows and requested fields.
anchors()Summarize backlink anchor text for a domain or URL.
result = qbash.integrations.ahrefs.anchors(
target="…",
)
targetstrrequiredDomain or URL to analyse.
limitintdefault100Maximum rows to return.
target_modestr | NonedefaultNoneTarget scope: exact, prefix, domain, or subdomains.
historystr | NonedefaultNoneBacklink period: live, since:YYYY-MM-DD, or all_time.
order_bystr | NonedefaultNoneAhrefs result field and optional direction.
{"status": "success", "target": str, "anchors": list[dict], "count": int}The length of `anchors` is the number of rows in this response. Returns up to `limit` anchor summaries; API v3 has no continuation token for this report. `history` controls the backlink period (`live`, `since:YYYY-MM-DD`, or `all_time`). null valued fields are removed; use `.get()` for optional values. API-unit cost depends on returned rows and requested fields.
matching_terms()Find keyword ideas that contain one or more terms.
result = qbash.integrations.ahrefs.matching_terms(
keywords=[...],
)
keywordslistrequiredSeed terms as a list of strings or a comma-separated string.
countrystrdefault"us"Two-letter ISO country code.
limitintdefault100Maximum ideas to return.
termsstr | Nonedefault"all"Use `all` or `questions`.
match_modestr | Nonedefault"terms"Use `terms` for words in any order or `phrase` for exact order.
order_bystr | NonedefaultNoneAhrefs result field and optional direction.
{"status": "success", "keywords": list[dict], "count": int}Pass one or more seed terms. Matching terms include the supplied words in any order by default; use `match_mode=phrase` to keep their order. `terms=questions` limits ideas to question phrasing. The required country is a two-letter ISO country code. Returns up to `limit` results with optional keyword metrics omitted when Ahrefs returns null; use `.get()` for fields that may be absent. This endpoint consumes API units on eligible plans.
list_bases()List all accessible Airtable bases.
result = qbash.integrations.airtable.list_bases(
offset="…",
)
offsetstr | NonedefaultNonePagination cursor from a previous response's offset.
{"status": "success", "bases": list[dict], "base_count": int, "offset": str | None}Each entry in bases has id and name keys. name falls back to the base ID when Airtable does not return a name. This mode ignores any base_id. Airtable returns the bases one page at a time. offset is only present when Airtable returned a cursor for another page, so use it as the loop condition and pass it back as the offset param to fetch the next page. base_count counts the bases on the current page, not every base the credential can reach.
list_tables()List tables in an Airtable base.
result = qbash.integrations.airtable.list_tables(
base_id="…",
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
detail_levelstrdefault"full"Level of schema detail to return. One of full or identifiers_only. Anything other than full returns the reduced shape.
{"status": "success", "tables": list[dict], "table_count": int}Each table has id, name, description and primaryFieldId keys, and description is null when the table does not have one. At detail_level full each table also carries fields and views lists, where every view has id, name and type. Any other detail_level omits fields and views entirely. A field normally has id, name and type plus description and options, but this integration drops every field key whose value is null, including id, name and type, so read field keys with .get() rather than indexing.
describe_table()Get schema and metadata for a specific Airtable table.
result = qbash.integrations.airtable.describe_table(
base_id="…",
tableId="…",
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name to describe.
detail_levelstrdefault"full"Level of schema detail to return. One of full or identifiers_only. Anything other than full returns the reduced shape.
{"status": "success", "table": dict}table has id, name, description and primaryFieldId keys, and description is null when the table does not have one. At detail_level full it also carries fields and views lists, where every view has id, name and type. Any other detail_level omits fields and views entirely. A field normally has id, name and type plus description and options, but this integration drops every field key whose value is null, including id, name and type, so read field keys with .get() rather than indexing. The table is matched on either its ID or its name, and a name that matches nothing returns an error rather than an empty table.
list_records()List records from an Airtable table. Returns up to 100 records per page. Use the returned offset to fetch subsequent pages.
result = qbash.integrations.airtable.list_records(
base_id="…",
tableId="…",
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name to list records from.
filterByFormulastr | NonedefaultNoneAirtable formula to filter records, for example NOT to skip empty names.
fieldslist | NonedefaultNoneField names to return on each record. Omit to return every field.
maxRecordsint | NonedefaultNoneMaximum total number of records to return. Omit to return all records, paginated at 100 per page via offset.
sortlist[dict] | NonedefaultNoneSort specs applied by Airtable, in priority order.
fieldstrrequiredField name to sort by.
directionstrrequiredSort direction. One of asc or desc.
viewstr | NonedefaultNoneView name or ID to filter records by.
offsetstr | NonedefaultNonePagination cursor from a previous list_records response. Pass it to fetch the next page.
pageSizeint | NonedefaultNoneNumber of records per page. Maximum 100. Omit to use the Airtable default of 100.
{"status": "success", "records": list[dict], "record_count": int, "offset": str | None}Each record has id, fields and created_time keys. created_time is Airtable createdTime renamed by this integration. fields is keyed by Airtable field name, not field ID, and Airtable leaves empty cells out of it, so read field values with .get() rather than indexing. offset is only present when Airtable returned a cursor, so use it as the loop condition and pass it back as the offset param to fetch the next page. A cursor means more rows remain to be scanned rather than more results, so with filterByFormula a page can carry an offset and still hold few records or none. record_count counts the records on the current page, not the whole table.
search_records()Search records in an Airtable table by text. When fieldIds is omitted, automatically searches all text fields.
result = qbash.integrations.airtable.search_records(
base_id="…",
tableId="…",
searchTerm="…",
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name to search.
searchTermstrrequiredText to match. Matching is case-insensitive substring, not whole word or exact.
fieldIdslist | NonedefaultNoneField names to search in. Omit to search every text field, which costs one extra API call to read the table schema.
maxRecordsint | NonedefaultNoneMaximum total number of matching records to return. Omit to return all matches, paginated at 100 per page via offset.
offsetstr | NonedefaultNonePagination cursor from a previous search_records response. Pass it to fetch the next page of matches.
pageSizeint | NonedefaultNoneNumber of records per page. Maximum 100. Omit to use the Airtable default of 100.
viewstr | NonedefaultNoneView ID or name to scope the search to a specific view.
{"status": "success", "records": list[dict], "record_count": int, "offset": str | None}Each record has id, fields and created_time keys, the same shape list_records returns, and Airtable leaves empty cells out of fields, so read field values with .get() rather than indexing. This is not a dedicated search endpoint. It builds a filterByFormula that ORs a case-insensitive substring match across the chosen fields, so there is no relevance ranking and no sort param. Omitting fieldIds costs one extra API call to read the table schema and then searches only the text field types, which are singleLineText, multilineText, richText, email, url and phoneNumber. Numbers, dates and select fields are never searched automatically, so name them in fieldIds to match on them. A table with no text fields returns an error asking for explicit fieldIds. offset is only present when Airtable returned a cursor, and a cursor means more rows remain to be scanned rather than more matches, so a page can carry an offset and still hold no matching records at all. Use offset as the loop condition rather than record_count, and pass it back as the offset param.
get_record()Get a specific Airtable record by ID.
result = qbash.integrations.airtable.get_record(
base_id="…",
tableId="…",
recordId="…",
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name containing the record.
recordIdstrrequiredAirtable record ID in recXXX format.
{"status": "success", "record": dict}record has id, fields and created_time keys. created_time is Airtable createdTime renamed by this integration. fields is keyed by Airtable field name, not field ID, and Airtable leaves empty cells out of it, so read field values with .get() rather than indexing.
create_record()Create a new record in an Airtable table.
result = qbash.integrations.airtable.create_record(
base_id="…",
tableId="…",
fields={"key": "value"},
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name to create the record in.
fieldsdictrequiredMap of field names to values for the new record, keyed by Airtable field name. An empty map is accepted and creates a record with no values set.
{"status": "success", "record": dict}record has id, fields and created_time keys and is the row as Airtable stored it, so it carries computed and default values that were not in the request. Airtable leaves empty cells out of fields, so read field values with .get() rather than indexing. Creates one record per call. Use batch_create_records to create up to 10 in a single request.
batch_create_records()Create up to 10 records in an Airtable table.
result = qbash.integrations.airtable.batch_create_records(
base_id="…",
tableId="…",
records={"key": "value"},
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name to create records in.
recordslist[dict]requiredRecords to create. Maximum 10. Each entry must be an object with a fields key.
fieldsdictrequiredMap of field names to values for that new record.
{"status": "success", "records": list[dict], "created_count": int}Airtable accepts at most 10 records per call and a longer list is rejected before any request is made, so chunk larger sets and call repeatedly. Created rows come back in the order Airtable returned them, each with id, fields and created_time, and each fields map is the row as Airtable stored it including computed and default values. Airtable leaves empty cells out of fields, so read field values with .get() rather than indexing.
batch_update_records()Update up to 10 existing records in an Airtable table.
result = qbash.integrations.airtable.batch_update_records(
base_id="…",
tableId="…",
records={"key": "value"},
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name containing the records to update.
recordslist[dict]requiredRecords to update. Maximum 10. Each entry must have both id and fields.
idstrrequiredAirtable record ID in recXXX format.
fieldsdictrequiredMap of field names to new values for that record.
{"status": "success", "records": list[dict], "updated_count": int}Airtable accepts at most 10 records per call and a longer list is rejected before any request is made, so chunk larger sets and call repeatedly. Updated rows come back with id, fields and created_time. Only the field names present in each entry are changed and the rest of the row is left alone, and every returned row carries the whole stored row rather than just the changed keys, minus any cell Airtable treats as empty, so read field values with .get() rather than indexing. There is no upsert. An unknown record ID fails the call.
batch_delete_records()Delete up to 10 records from an Airtable table by ID.
result = qbash.integrations.airtable.batch_delete_records(
base_id="…",
tableId="…",
recordIds=[...],
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name containing the records to delete.
recordIdslist[str]requiredRecord IDs in recXXX format to delete. Maximum 10.
{"status": "success", "deleted_ids": list[str], "deleted_count": int}Airtable accepts at most 10 record IDs per call. An oversized list fails the whole call rather than deleting the first 10. deleted_ids is built from the id of each entry in the Airtable response and this integration does not inspect the deleted flag on those entries, so read it as what Airtable echoed back rather than as a separate confirmation. deleted_count is the length of that list. Deletion is permanent and there is no undo through this API.
get_base_schema()Get the full schema for an Airtable base, covering all tables and fields.
result = qbash.integrations.airtable.get_base_schema(
base_id="…",
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
{"status": "success", "schema": dict}schema has base, tables and table_count keys. base has id and name, and it is resolved by scanning the first page of list_bases, which is the only page that call reads, so a base that sits beyond that page falls back to the requested base ID with the name Unknown even though its tables still load correctly. tables is always at full detail, so every table carries its fields and views, and every field key whose value is null is dropped, so read field keys with .get() rather than indexing. This mode makes two API calls, one to list bases and one to list tables, so prefer list_tables when the base identity is already known.
list_field_types()Reference list of all Airtable field types organised by category.
result = qbash.integrations.airtable.list_field_types()
{"status": "success", "field_types": list[dict], "count": int}Static reference data. This makes no API call and needs no base, so it works before any base or table is known. Each entry has type, description and category keys. The singleSelect and multipleSelects entries also carry an options_note describing the options payload those types require. Use the type values here for create_field and for create_table tableFields.
get_table_views()List views configured on an Airtable table.
result = qbash.integrations.airtable.get_table_views(
base_id="…",
tableId="…",
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name whose views to list.
{"status": "success", "views": list[dict], "view_count": int}Each view has id, name and type keys and nothing else, so the filters, sorts and visible fields that make up a view configuration are not returned. This is read from the full table schema, so it costs the same call as describe_table and returns nothing describe_table does not already include. Either the name or the ID of a view can then be passed as the view param of list_records or search_records.
create_table()Create a new table in an Airtable base.
result = qbash.integrations.airtable.create_table(
base_id="…",
name="…",
tableFields={"key": "value"},
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
namestrrequiredName for the new table.
tableFieldslist[dict]requiredField definitions for the new table. Each entry needs a name and a type.
namestrrequiredField name.
typestrrequiredField type, one of the values list_field_types returns.
optionsdict | NonedefaultNoneType-specific options. Required for singleSelect and multipleSelects, which need a choices list.
descriptionstr | NonedefaultNoneDescription for the table. An empty string is treated the same as omitting it.
{"status": "success", "table": dict}The created table comes back at full detail, and its fields and views keys are empty lists when Airtable returns none. Every field key whose value is null is dropped, so read field keys with .get() rather than indexing. Airtable does not officially document this endpoint, so it can fail on plans or bases where table creation is not permitted. Every entry in tableFields needs a name and a type, and the type must be one of the values list_field_types returns. Select types additionally need an options payload with choices.
create_field()Create a new field in an existing Airtable table.
result = qbash.integrations.airtable.create_field(
base_id="…",
tableId="…",
fieldConfig={"key": "value"},
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name to add the field to.
fieldConfigdictrequiredField configuration object. Required keys: name (string), type (string). For singleSelect/multipleSelects, include options: {choices: [{name: "Choice A"}, {name: "Choice B"}]}. Do not include choice ids on create.
namestrrequiredField name.
typestrrequiredField type, one of the values list_field_types returns.
descriptionstr | NonedefaultNoneDescription for the field.
optionsdict | NonedefaultNoneType-specific options. Required for singleSelect and multipleSelects, which need a choices list.
{"status": "success", "field": dict}field normally has id, name, type, description and options keys, but every key whose value is null is dropped, including id, name and type, so read the field with .get() rather than indexing. A field type cannot be changed after creation, so pick it deliberately. Use list_field_types for the valid type values and for which types need an options payload.
update_field()Update an existing field name, description, or select options. Fields cannot be deleted via the Airtable API. Rename or clear values instead.
result = qbash.integrations.airtable.update_field(
base_id="…",
tableId="…",
fieldId="…",
updates={"key": "value"},
)
base_idstrrequiredAirtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.
tableIdstrrequiredTable ID or name containing the field.
fieldIdstrrequiredField ID or name to update.
updatesdictrequiredUpdates to apply. Accepts name, description, and options. Type changes are generally not supported. For select fields the options.choices array replaces all existing choices. Call describe_table first to get current choices with their ids, then include those ids to preserve existing choices. Omitting a choice removes it. Omitting its id may cause a 422 conflict.
namestr | NonedefaultNoneNew field name.
descriptionstr | NonedefaultNoneNew field description.
optionsdict | NonedefaultNoneReplacement type-specific options. For select fields the choices list replaces every existing choice.
{"status": "success", "field": dict}field is the whole field after the update, not just the changed keys, and every key whose value is null is dropped, including id, name and type, so read the field with .get() rather than indexing. Airtable has no delete-field endpoint, so rename the field or clear its values instead. Changing the type of an existing field is generally rejected. Read the current choices with describe_table before editing a select field, because the choices list sent here replaces the existing one.
list_indices()List Algolia indices visible to the configured API key. Requires the listIndexes ACL.
result = qbash.integrations.algolia.list_indices(
hits_per_page=0,
page=0,
)
hits_per_pageintdefault100Maximum indices per page.
pageintdefault0Zero-based result page.
{"status": "success", "result": dict}"result" is Algolia's own /1/indexes body forwarded unchanged. Nothing here reads, renames or projects it, so its keys are the vendor's and none of them can be guaranteed from this side. The only reshaping applied is that every key whose value is null is dropped from the body before it is returned, so read "result" with .get() rather than indexing. hitsPerPage defaults to 100 and page to 0 and both are always sent, so one page comes back per call and the next page is fetched by incrementing the page param. Nothing loops here, so this is not necessarily every index the key can see. Which indices appear at all is decided by the API key's ACLs.
search_index()Search a single Algolia index and return matching hits.
result = qbash.integrations.algolia.search_index(
index_name="…",
)
index_namestrrequiredAlgolia index name.
querystr | NonedefaultNoneSearch query text.
hits_per_pageintdefault20Maximum hits per page.
pageintdefault0Zero-based result page.
filtersstr | NonedefaultNoneAlgolia filters expression.
facet_filtersstr | list | NonedefaultNoneFacet filters as comma-separated string or list.
numeric_filtersstr | list | NonedefaultNoneNumeric filters as comma-separated string or list.
attributes_to_retrievestr | list | NonedefaultNoneAttributes to include in each hit.
restrict_searchable_attributesstr | list | NonedefaultNoneSearch only these searchable attributes.
get_ranking_infobooldefaultFalseInclude Algolia ranking details.
typo_tolerancestr | bool | NonedefaultNoneAlgolia typo tolerance setting.
{"status": "success", "index_name": str, "result": dict}"index_name" echoes the index that was passed rather than anything Algolia reports. "result" is Algolia's own /1/indexes/{index}/query body forwarded unchanged, so the hit collection and the page counters are the vendor's keys and nothing here reads or normalises them. Every key whose value is null is dropped from the body first, at every level, so a hit simply has no key for an attribute it does not carry and "result" should be read with .get() throughout. Which attributes each hit carries also depends on attributes_to_retrieve when that is passed. hitsPerPage defaults to 20 and page to 0 and both are always sent, so one page comes back per call and the next page is fetched by incrementing the page param. A query that matches nothing is still a success, so an empty hit set is not an error.
multi_search()Run multiple Algolia index searches in one request.
result = qbash.integrations.algolia.multi_search(
requests=[...],
)
requestslistrequiredList of search request dicts. Each requires index_name.
{"status": "success", "result": dict}"result" is Algolia's own /1/indexes/*/queries body forwarded unchanged, so the per-query results and their page counters are the vendor's keys and nothing here reads, splits or renames them. There is no top level count and no per-request echo, so match the answers back to what was sent by position. Every request is rebuilt before it is sent, and three things survive that rebuild: index_name, the search params search_index accepts, and a raw "params" query string, which is URI decoded and merged underneath the named params so a named one wins on a clash. Any other key in a request dict is dropped without an error. Unlike search_index no page size and no page number is defaulted here, so a request naming neither gets Algolia's own defaults. Every key whose value is null is dropped from the body before it is returned, so read "result" with .get().
browse_index()Browse records from an Algolia index with cursor pagination.
result = qbash.integrations.algolia.browse_index(
index_name="…",
)
index_namestrrequiredAlgolia index name.
querystr | NonedefaultNoneOptional browse query text.
cursorstr | NonedefaultNoneBrowse cursor from a previous response.
hits_per_pageintdefault20Maximum records per page.
filtersstr | NonedefaultNoneAlgolia filters expression.
attributes_to_retrievestr | list | NonedefaultNoneAttributes to include in each record.
{"status": "success", "index_name": str, "result": dict}"index_name" echoes the index that was passed. "result" is Algolia's own /1/indexes/{index}/browse body forwarded unchanged. The continuation cursor is inside that body and is not projected to the top level, so read it with .get() and pass it back as the cursor param for the next batch. Every key whose value is null is dropped from the body first, so treat a missing cursor as the end of the walk rather than an error. hitsPerPage defaults to 20 and is always sent and nothing here loops, so one batch comes back per call and walking a whole index means calling this mode repeatedly. Which attributes each record carries depends on attributes_to_retrieve when that is passed.
get_object()Fetch one Algolia object by object ID.
result = qbash.integrations.algolia.get_object(
index_name="…",
object_id="…",
)
index_namestrrequiredAlgolia index name.
object_idstrrequiredAlgolia objectID value.
attributes_to_retrievestr | list | NonedefaultNoneAttributes to include in the response.
{"status": "success", "index_name": str, "object_id": str, "result": dict}"index_name" and "object_id" echo what was passed. "result" is the stored Algolia record forwarded unchanged, so its attributes are the customer's own index schema and nothing here projects, renames or defaults them. Every attribute whose value is null is dropped before the record is returned, so read it with .get(), and passing attributes_to_retrieve narrows the set further. An object ID that does not exist is a 404 from Algolia and reaches the script as an error, not as an empty result, so there is no not-found shape to branch on.
people_search()Search Apollo's database for people. No credits consumed. Returns obfuscated previews, so call person_enrich to reveal contact info.
result = qbash.integrations.apollo.people_search(
person_titles=[...],
person_seniorities=[...],
)
person_titleslistdefaultNoneList of job titles to match (e.g. ["Founder","CEO","Owner"]). Fuzzy match by default. Entries that are not strings are dropped.
person_senioritieslistdefaultNoneSeniority levels: owner, founder, c_suite, partner, vp, head, director, manager, senior, entry, intern.
person_locationslistdefaultNoneList of city/state/country strings.
q_keywordsstrdefaultNoneFree-text keyword filter applied to people and their orgs.
q_organization_domains_listlistdefaultNoneList of company domains to restrict the search to (e.g. ["thoughtbot.com"]). Up to 1,000 per request.
organization_idslistdefaultNoneList of Apollo organization IDs as strings. Entries that are not strings are dropped.
organization_num_employees_rangeslistdefaultNoneHeadcount ranges as "min,max" strings (e.g. ["1,10","11,50"]).
pageintdefault1Page number (1-based).
per_pageintdefault25Results per page (max 100).
{"status": "success", "people": list[dict], "pagination": dict, "count": int}Each person is a preview built here rather than forwarded, carrying id, first_name, last_name, name, title, headline, linkedin_url, organization_name, organization_id, city, state, country and email_status. Keys whose value is null are dropped, so read them with .get() rather than by index, and note that no email or phone field is built on this path at all. "count" is the number of people on this page, not the size of the match. "pagination" is narrowed to page, per_page, total_entries and total_pages, each key dropped when Apollo omits it, and it is an empty dict when Apollo sends no pagination block, so read it with .get() and walk the result with the page param. A response Apollo returns without a people array is reported as a success with an empty list rather than an error.
person_enrich()Enrich a single person. Consumes credits when a match is found. Pass any combination of identifiers (email, linkedin_url, name+domain).
result = qbash.integrations.apollo.person_enrich(
first_name="…",
last_name="…",
)
first_namestr | NonedefaultNonePerson's first name.
last_namestr | NonedefaultNonePerson's last name.
namestr | NonedefaultNoneFull name (alternative to first/last).
emailstr | NonedefaultNoneEmail address.
hashed_emailstr | NonedefaultNoneMD5 or SHA-256 hashed email.
organization_namestr | NonedefaultNoneCurrent employer name.
domainstr | NonedefaultNoneEmployer domain (no www. or scheme).
idstr | NonedefaultNoneApollo person ID.
linkedin_urlstr | NonedefaultNoneLinkedIn profile URL.
reveal_personal_emailsbooldefaultFalseReveal personal email addresses (extra credits).
reveal_phone_numberbooldefaultFalseReveal phone numbers. Costs extra credits and requires webhook_url.
webhook_urlstr | NonedefaultNoneWebhook Apollo posts phone reveal results to. Mandatory when reveal_phone_number is True.
run_waterfall_emailbooldefaultFalseUse Apollo's waterfall to broaden email coverage across providers.
run_waterfall_phonebooldefaultFalseUse Apollo's waterfall to broaden phone coverage.
{"status": "success", "person": dict | None, "credits_consumed": int | None}"person" is None when Apollo returns no match, so check it before indexing it. When there is a match the record is built here and carries id, first_name, last_name, name, title, headline, email, email_status, personal_emails, phone_numbers, linkedin_url, twitter_url, github_url, organization_name, organization_id, organization_domain, city, state, country, seniority and departments. Keys whose value is null are dropped, so read them with .get(), though personal_emails, phone_numbers and departments fall back to empty lists and are always present on a match. "credits_consumed" is Apollo's own counter passed through, None when Apollo omits it and 0 on the no-match path, so it is not a reliable way to tell the two apart. Phone reveal is delivered to webhook_url by Apollo rather than returned here, and nothing in this integration reads that webhook. Apollo bills this call, so it is declared as a write to keep the Builder from running it speculatively to discover the result shape.
bulk_person_enrich()Enrich up to 10 people in one call. Pass a list of person identifier maps (first_name/last_name/email/domain/linkedin_url).
result = qbash.integrations.apollo.bulk_person_enrich(
people=[...],
)
peoplelistrequiredList of person identifier dicts, at most 10. Each entry may contain first_name, last_name, name, email, hashed_email, organization_name, domain, id, linkedin_url.
reveal_personal_emailsbooldefaultFalseReveal personal emails for the whole batch.
reveal_phone_numberbooldefaultFalseReveal phone numbers for the whole batch.
webhook_urlstr | NonedefaultNoneRequired when reveal_phone_number is True.
run_waterfall_emailbooldefaultFalseEmail waterfall for the batch.
run_waterfall_phonebooldefaultFalsePhone waterfall for the batch.
{"status": "success", "matches": list[dict], "count": int, "credits_consumed": int | None}The collection key is "matches", not "people", and it holds records built by the same formatter person_enrich uses. Ten entries per call is a hard cap, checked before the request, and a list longer than that is rejected without calling Apollo. Keys whose value is null are dropped, so an entry Apollo could not match arrives as an almost empty dict rather than being left out, which makes "count" the number of entries returned rather than the number of people matched. Check each entry for id or email with .get() before using it. "credits_consumed" is Apollo's own counter passed through, None when Apollo omits it and 0 when the response carried neither a matches nor a people array. Entries in the input list that are not dicts are dropped before the request, and dicts with none of the recognised identifier keys are dropped too. Apollo bills this call, so it is declared as a write to keep the Builder from running it speculatively to discover the result shape.
org_enrich()Enrich a single organization. Provide at least one of domain / linkedin_url / name / website.
result = qbash.integrations.apollo.org_enrich(
domain="…",
linkedin_url="https://…",
)
domainstr | NonedefaultNoneCompany domain, the most reliable identifier. No www. or scheme.
linkedin_urlstr | NonedefaultNoneCompany LinkedIn profile URL.
namestr | NonedefaultNoneCompany name.
websitestr | NonedefaultNoneFull company website URL.
{"status": "success", "organization": dict | None}"organization" is None when Apollo returns no match, so check it before indexing it. There is no credits_consumed key on this mode, unlike person_enrich and bulk_org_enrich. When there is a match the record is built here and carries id, name, website_url, primary_domain, linkedin_url, twitter_url, facebook_url, industry, keywords, estimated_num_employees, annual_revenue, annual_revenue_printed, total_funding, total_funding_printed, latest_funding_round_date, latest_funding_stage, founded_year, short_description, headquarters_city, headquarters_state, headquarters_country, technology_names, current_technologies, departmental_head_count and retail_location_count. Keys whose value is null are dropped, so read them with .get(), and headquarters_city is taken from Apollo's raw_address when it has one, so it can hold a full street address rather than a city name. Pass the id from here to org_jobs and org_news. Apollo bills this call, so it is declared as a write to keep the Builder from running it speculatively to discover the result shape.
bulk_org_enrich()Enrich up to 10 organizations by domain.
result = qbash.integrations.apollo.bulk_org_enrich(
orgs=[...],
)
orgslistrequiredList of domain strings (e.g. ["thoughtbot.com", "stripe.com"]) or dicts containing a domain key, at most 10. Apollo's bulk endpoint only matches on domain, so use org_enrich for single-org lookups by linkedin_url, name or website.
{"status": "success", "organizations": list[dict], "count": int, "credits_consumed": int | None}The collection key is "organizations", and it holds records built by the same formatter org_enrich uses, with keys whose value is null dropped, so read them with .get() rather than by index. Ten entries per call is a hard cap, checked before the request. Only the domain is used to match, so dict entries are reduced to their domain key and entries with no usable domain are dropped before the request, which means the result can hold fewer organizations than were asked for. Nothing here ties an entry back to the domain that requested it, so match on primary_domain rather than by position, and treat "count" as the number of records returned rather than the number of domains sent. "credits_consumed" is Apollo's own counter passed through, None when Apollo omits it and 0 when the response carried no organizations array. Apollo bills this call, so it is declared as a write to keep the Builder from running it speculatively to discover the result shape.
org_jobs()List active job postings for an Apollo organization. Strong signal for growth and AI-team building.
result = qbash.integrations.apollo.org_jobs(
organization_id="…",
)
organization_idstrrequiredApollo organization ID (returned by org_enrich).
pageintdefault1Page number (1-based).
per_pageintdefault25Results per page.
{"status": "success", "jobs": list[dict], "count": int, "pagination": dict}Each job is built here rather than forwarded and carries id, title, url, city, state, country, posted_at and last_seen_at, with keys whose value is null dropped, so read them with .get(). "count" is the number of jobs on this page. "pagination" is narrowed to page, per_page, total_entries and total_pages, each key dropped when Apollo omits it, and it is an empty dict when Apollo sends no pagination block, so read it with .get() and walk the result with the page param. A response with no job posting array is reported as a success with an empty list and an empty pagination dict rather than an error, so an organization with no postings and an unexpected payload look the same here.
org_news()Search Apollo's news index for events about given organizations (funding, hires, acquisitions, layoffs, etc.).
result = qbash.integrations.apollo.org_news(
organization_ids=[...],
)
organization_idslistrequiredList of Apollo organization IDs as strings. Entries that are not strings are dropped, and an empty result after that is an error.
categorieslistdefaultNoneEvent categories: hires, funding, investment, acquisition, layoffs, new_product, partnership.
published_at_minstr | NonedefaultNoneLower date bound (YYYY-MM-DD).
published_at_maxstr | NonedefaultNoneUpper date bound (YYYY-MM-DD).
pageintdefault1Page number (1-based).
per_pageintdefault25Results per page.
{"status": "success", "articles": list[dict], "count": int, "pagination": dict}Each article is built here rather than forwarded and carries id, title, url, source, published_at, category, summary, organization_id and organization_name, with keys whose value is null dropped, so read them with .get(). "category" holds Apollo's category when it sends one and its categories value otherwise, so it can be a string or a list. "summary" falls back to the article description. "count" is the number of articles on this page. "pagination" is narrowed to page, per_page, total_entries and total_pages, each key dropped when Apollo omits it, and it is an empty dict when Apollo sends no pagination block, so walk the result with the page param. A response with no news article array is reported as a success with an empty list rather than an error.
usage_stats()View per-endpoint Apollo API usage and rate-limit headroom. Requires a master API key.
result = qbash.integrations.apollo.usage_stats()
{"status": "success", "usage": {"endpoints": dict, "summary": dict}} or {"status": "success", "usage": {}}"endpoints" is Apollo's usage body forwarded unchanged, so its keys are the vendor's endpoint paths and the counters inside them are the vendor's too. Read it with .get() against Apollo's own documentation rather than expecting a fixed shape. "summary" holds one key, endpoint_count, which is the number of top-level keys in that body and is counted here rather than by Apollo. The second shape, a "usage" with neither key, is returned when Apollo answers with JSON that is not an object. A key without master access gets a 403 from Apollo, which surfaces as an error rather than as an empty result.
list_workspaces()List all accessible Asana workspaces.
result = qbash.integrations.asana.list_workspaces(
limit=10,
offset="…",
)
limitint | NonedefaultNoneMaximum number of workspaces to return per page. Asana accepts values from 1 to 100. Leave unset to get every workspace in one unpaginated response.
offsetstr | NonedefaultNonePagination cursor taken from next_page_offset in a previous response.
{"status": "success", "workspaces": list[dict], "count": int, "next_page_offset": str | None}Each workspace map contains gid, name and resource_type, and keys whose Asana value is null are dropped, so read them with .get() rather than by index. Without limit Asana returns every workspace in one response, which can fail when a token reaches very many. Set limit to page through them. The next_page_offset key is present only when Asana has more results, so pass it back as offset, with the same limit, to fetch the next page.
list_users()List users in a workspace.
result = qbash.integrations.asana.list_users(
workspace_gid="…",
)
workspace_gidstrrequiredGID of the workspace whose users are listed.
limitintdefault50Maximum number of users to return per page. Asana accepts values from 1 to 100.
offsetstr | NonedefaultNonePagination cursor taken from next_page_offset in a previous response.
{"status": "success", "users": list[dict], "count": int, "next_page_offset": str | None}Each user map contains gid, name and email. Use this to turn an email address into the assignee GID that create_task and update_task expect. The next_page_offset key is present only when Asana has more results, so pass it back as offset to fetch the next page. Keys whose Asana value is null are dropped, so read them with .get() rather than by index.
list_projects()List projects in a workspace.
result = qbash.integrations.asana.list_projects(
workspace_gid="…",
)
workspace_gidstrrequiredGID of the workspace whose projects are listed.
limitintdefault50Maximum number of projects to return per page. Asana accepts values from 1 to 100.
offsetstr | NonedefaultNonePagination cursor taken from next_page_offset in a previous response.
{"status": "success", "projects": list[dict], "count": int, "next_page_offset": str | None}Each project map contains gid, name, color, archived, public and permalink_url. Keys whose Asana value is null are dropped, so read them with .get() rather than by index. Archived projects are included and this mode does not expose a filter to exclude them. The next_page_offset key is present only when Asana has more results, so pass it back as offset to fetch the next page.
get_project()Get details of a specific project.
result = qbash.integrations.asana.get_project(
project_gid="…",
)
project_gidstrrequiredGID of the project to fetch.
{"status": "success", "project": dict}The project map contains gid, name, notes, color, archived, public, permalink_url, created_at, modified_at, owner and members. Keys whose Asana value is null are dropped, so read them with .get() rather than by index.
list_sections()List sections in a project.
result = qbash.integrations.asana.list_sections(
project_gid="…",
)
project_gidstrrequiredGID of the project whose sections are listed.
limitint | NonedefaultNoneMaximum number of sections to return per page. Asana accepts values from 1 to 100. Leave unset to get every section in one unpaginated response.
offsetstr | NonedefaultNonePagination cursor taken from next_page_offset in a previous response.
{"status": "success", "sections": list[dict], "count": int, "next_page_offset": str | None}Each section map contains gid, name and created_at. Without limit Asana returns every section in one response, which can fail on a project with very many. Set limit to page through them. The next_page_offset key is present only when Asana has more results, so pass it back as offset, with the same limit, to fetch the next page. Keys whose Asana value is null are dropped, so read them with .get() rather than by index.
list_tasks()List tasks in a project or section.
result = qbash.integrations.asana.list_tasks(
project_gid="…",
section_gid="…",
)
project_gidstr | NonedefaultNoneGID of the project whose tasks are listed. Either project_gid or section_gid is required.
section_gidstr | NonedefaultNoneGID of the section whose tasks are listed. Either project_gid or section_gid is required, and section_gid wins when both are given.
limitintdefault50Maximum number of tasks to return per page. Asana accepts values from 1 to 100.
completed_sincestr | NonedefaultNoneReturn only tasks that are incomplete or were completed after this ISO 8601 timestamp, for example 2026-01-31T00:00:00Z. Pass the literal string "now" to return only incomplete tasks.
offsetstr | NonedefaultNonePagination cursor taken from next_page_offset in a previous response.
{"status": "success", "tasks": list[dict], "count": int, "next_page_offset": str | None}Either project_gid or section_gid is required. When both are given the section is used and the project is ignored. The next_page_offset key is present only when Asana has more results, so pass it back as offset to fetch the next page. Each task map contains gid, name, completed, notes, due_on, assignee, custom_fields, created_at, modified_at and permalink_url. Keys whose Asana value is null are dropped, so read them with .get() rather than by index.
list_subtasks()List subtasks of a task.
result = qbash.integrations.asana.list_subtasks(
task_gid="…",
)
task_gidstrrequiredGID of the parent task.
limitintdefault50Maximum number of subtasks to return. Asana accepts values from 1 to 100.
{"status": "success", "subtasks": list[dict], "count": int}The collection key is subtasks rather than tasks. Each subtask map contains gid, name, completed, notes, due_on, assignee and permalink_url. Only the first page is returned. The service makes one request and drops the next_page cursor Asana sends, and this mode exposes no offset, so subtasks past limit, which Asana caps at 100, cannot be reached here. Keys whose Asana value is null are dropped, so read them with .get() rather than by index.
get_task()Get full details of a specific task.
result = qbash.integrations.asana.get_task(
task_gid="…",
)
task_gidstrrequiredGID of the task to fetch.
{"status": "success", "task": dict}The task map contains gid, name, completed, due_on, due_at, notes, assignee, projects, parent, num_subtasks, custom_fields, created_at, modified_at and permalink_url. Keys whose Asana value is null are dropped, so read them with .get() rather than by index.
list_task_stories()List comments and activity on a task.
result = qbash.integrations.asana.list_task_stories(
task_gid="…",
)
task_gidstrrequiredGID of the task whose stories are listed.
limitintdefault50Maximum number of stories to return per page. Asana accepts values from 1 to 100.
offsetstr | NonedefaultNonePagination cursor taken from next_page_offset in a previous response.
{"status": "success", "stories": list[dict], "count": int, "next_page_offset": str | None}Stories cover both user comments and Asana activity entries, so filter on the type key to keep only comments. Each story map contains gid, type, text, created_at and created_by. The next_page_offset key is present only when Asana has more results, so pass it back as offset to fetch the next page. Keys whose Asana value is null are dropped, so read them with .get() rather than by index.
search_tasks()Search tasks by keyword in a workspace.
result = qbash.integrations.asana.search_tasks(
workspace_gid="…",
text="…",
)
workspace_gidstrrequiredGID of the workspace to search within.
textstrrequiredText matched against task names and descriptions.
limitintdefault20Maximum number of results to return. Asana accepts values from 1 to 100.
completedbool | NonedefaultNoneSet to true to return only completed tasks or false to return only incomplete tasks. Leave unset to return both.
{"status": "success", "tasks": list[dict], "count": int, "search_term": str}Wraps the Asana workspace task search endpoint, which returns a single page with no pagination cursor, so matches past limit, which Asana caps at 100, cannot be reached here. The search_term key echoes the text that was searched. Only the text and completed filters are exposed, so assignee, project and date filters are not available through this mode. Keys whose Asana value is null are dropped, so read them with .get() rather than by index.
create_task()Create a new task.
result = qbash.integrations.asana.create_task(
name="…",
)
namestrrequiredName of the task.
workspace_gidstr | NonedefaultNoneGID of the workspace to create the task in. Either workspace_gid or project_gid is required.
project_gidstr | NonedefaultNoneGID of the project to add the task to. Either workspace_gid or project_gid is required.
notesstr | NonedefaultNonePlain text description of the task.
due_onstr | NonedefaultNoneDue date in YYYY-MM-DD format.
assigneestr | NonedefaultNoneGID of the user to assign, or the literal string "me" for the authenticated user. Use list_users to look up a GID by email address.
{"status": "success", "task": dict}Either workspace_gid or project_gid is required. Passing project_gid adds the task to that one project. A task cannot be placed in a section at creation time, so call move_task_to_section afterwards. The returned task map carries the same keys as get_task, and any key Asana leaves null or omits from the create response is dropped, so read it with .get() rather than by index.
create_subtask()Create a subtask under a parent task.
result = qbash.integrations.asana.create_subtask(
task_gid="…",
name="…",
)
task_gidstrrequiredGID of the parent task.
namestrrequiredName of the subtask.
notesstr | NonedefaultNonePlain text description of the subtask.
due_onstr | NonedefaultNoneDue date in YYYY-MM-DD format.
assigneestr | NonedefaultNoneGID of the user to assign, or the literal string "me" for the authenticated user. Use list_users to look up a GID by email address.
{"status": "success", "task": dict}The subtask inherits the parent task's workspace, so no workspace or project is needed. The created subtask is returned under the task key. That map carries the same keys as get_task, and any key Asana leaves null or omits from the create response is dropped, so read it with .get() rather than by index.
update_task()Update an existing task.
result = qbash.integrations.asana.update_task(
task_gid="…",
)
task_gidstrrequiredGID of the task to update.
namestr | NonedefaultNoneNew name for the task.
notesstr | NonedefaultNoneNew plain text description for the task.
due_onstr | NonedefaultNoneNew due date in YYYY-MM-DD format.
assigneestr | NonedefaultNoneGID of the user to assign, or the literal string "me" for the authenticated user.
completedbool | NonedefaultNoneSet to true to mark the task complete or false to reopen it.
{"status": "success", "task": dict}Only the fields you pass are changed. Omitted fields are left alone, and passing none of them sends an empty update that leaves the task unchanged. The returned task map carries the same keys as get_task, and any key Asana leaves null or omits from the update response is dropped, so read it with .get() rather than by index.
move_task_to_section()Move a task into a section.
result = qbash.integrations.asana.move_task_to_section(
task_gid="…",
section_gid="…",
)
task_gidstrrequiredGID of the task to move.
section_gidstrrequiredGID of the destination section.
{"status": "success", "result": {"moved": True, "task_gid": str, "section_gid": str}}The result map is assembled locally once Asana accepts the call and echoes the arguments back rather than returning the task. Moving a task into a section removes it from any other section of the same project, and adds it to the project when it was not already a member.
set_task_dependencies()Set tasks that a task depends on.
result = qbash.integrations.asana.set_task_dependencies(
task_gid="…",
dependency_gids=[...],
)
task_gidstrrequiredGID of the task that depends on the other tasks.
dependency_gidslist | strrequiredTask GIDs this task depends on. Accepts a list of GIDs, a JSON array string, or a comma-separated string.
{"status": "success", "result": {"dependencies_added": True, "task_gid": str, "dependency_gids": list}}Dependencies are added to the task rather than replacing the ones already set. The result map is assembled locally once Asana accepts the call and echoes the arguments back, so dependency_gids is the parsed input list rather than anything Asana returned.
delete_task()Delete a task.
result = qbash.integrations.asana.delete_task(
task_gid="…",
)
task_gidstrrequiredGID of the task to delete.
{"status": "success", "result": {"deleted": True, "gid": str}}Asana moves the task to the trash, where it can still be restored from the Asana interface. The result map is assembled locally once Asana accepts the call and echoes the deleted task GID back.
add_comment()Add a comment to a task.
result = qbash.integrations.asana.add_comment(
task_gid="…",
text="…",
)
task_gidstrrequiredGID of the task to comment on.
textstrrequiredPlain text body of the comment.
{"status": "success", "story": dict}The story map contains gid, type, text, created_at and created_by. Keys whose Asana value is null are dropped, so read them with .get() rather than by index. Comments are posted as plain text. Rich text and file attachments are not exposed.
bulk_create_tasks()Create multiple tasks at once. Returns results for each task.
result = qbash.integrations.asana.bulk_create_tasks(
tasks={"key": "value"},
)
taskslist[dict]requiredTask objects forwarded to the Asana task API as-is, so the keys are Asana field names rather than this mode's kwargs. Each needs name plus either workspace set to a workspace GID or projects set to a list of project GIDs. Optional keys include notes, due_on and assignee.
{"status": "success", "results": list[dict], "total": int, "succeeded": int, "failed": int}Tasks are created one at a time, one API call each, and a failure does not stop the rest. Each entry in results is either {"status": "success", "task": dict} or {"status": "error", "error": str, "task_data": dict}. Read succeeded and failed rather than the top-level status, which stays "success" even when every task failed. Each returned task map comes from the same formatter as create_task, so keys whose Asana value is null are dropped and have to be read with .get(). The task_data map on an error entry is the object you passed, echoed back unchanged.
bulk_update_tasks()Update multiple tasks at once. Returns results for each task.
result = qbash.integrations.asana.bulk_update_tasks(
updates={"key": "value"},
)
updateslist[dict]requiredUpdate objects, one per task.
task_gidstrrequiredGID of the task to update.
fieldsdictdefault{}Asana task fields to set, forwarded to the API as-is. Keys are Asana field names such as name, notes, completed, due_on and assignee. An empty map sends an update that changes nothing.
{"status": "success", "results": list[dict], "total": int, "succeeded": int, "failed": int}Tasks are updated one at a time, one API call each, and a failure does not stop the rest. Each entry in results is {"status": "success", "task": dict}, {"status": "error", "error": str, "task_gid": str}, or {"status": "error", "error": str, "update": dict} when the entry had no task_gid. Read succeeded and failed rather than the top-level status, which stays "success" even when every update failed. Each returned task map comes from the same formatter as update_task, so keys whose Asana value is null are dropped and have to be read with .get().
create_project()Create a new project in a workspace.
result = qbash.integrations.asana.create_project(
name="…",
workspace_gid="…",
)
namestrrequiredName of the project.
workspace_gidstrrequiredGID of the workspace to create the project in.
team_gidstr | NonedefaultNoneGID of the team that owns the project. Asana requires this when the workspace is an organization.
notesstr | NonedefaultNonePlain text description of the project.
colorstr | NonedefaultNoneProject color name forwarded to Asana unchanged, for example light-green, dark-blue or light-orange.
{"status": "success", "project": dict}The project map carries the same keys as get_project, so besides gid, name, notes, color, archived, public and permalink_url it can also hold created_at, modified_at, owner and members when Asana returns them. Keys whose Asana value is null or missing from the create response are dropped, so read them with .get() rather than by index. Asana rejects the call without team_gid when the workspace is an organization.
knowledge_base()Search an AWS Bedrock knowledge base with one or more queries.
result = qbash.integrations.aws.knowledge_base(
knowledgeBaseId="…",
queries=[...],
)
knowledgeBaseIdstrrequiredAWS Bedrock knowledge base ID.
querieslistrequiredSearch query strings, as a real list. An empty list is an error. Pass a list even when there is only one query.
contentTypeFilterslist | NonedefaultNoneContent type strings to filter results by (e.g. ['DOCUMENT']), matched against the content_type metadata field. Omit or pass an empty list for an unfiltered search. A non-list, or a list holding anything but strings, is an error.
slugslist | NonedefaultNoneContent slugs to restrict the search to, matched against the slug metadata field. A non-list, or a list holding anything but strings, is an error.
awsRegionstr | Nonedefault"us-west-2"AWS region the knowledge base lives in (e.g. 'us-east-1'). Defaults to us-west-2, which is not read from the credential.
resultsLengthint | str | Nonedefault10Maximum results to return per query, not across all queries. May span multiple AWS pages, but the same 50 page ceiling applies, so a value above roughly 5000 comes back truncated and still reports success. A string is read up to its first non-digit, so '15abc' is taken as 15, and a string starting with no digit falls back to 10.
fetchAllResultsbool | str | NonedefaultFalseFetch every matching result per query, ignoring resultsLength. Only True or the string 'true' in any case enables it, so any other string leaves it off. Bounded at 50 pages of 100 results per query, with the truncation logged rather than raised.
list[dict], each {"content": dict | None, "location": dict | None, "score": float | None, "metadata": dict | None}The result is a bare list, not a dict, so index it by position and not by a collection key. Every query in "queries" contributes to that one flat list, in the order the queries were given, with nothing recording which query produced a result and no de-duplication, so a chunk matching two queries appears twice. "resultsLength" is applied per query, so the list holds up to that many results times the number of queries. A query whose Bedrock call fails is logged and contributes nothing, so a failure is indistinguishable from an empty result and a short list is not proof the knowledge base is short. The four record keys are copied straight from Bedrock's retrieval result and are set to None when Bedrock omits them, so read them with .get() rather than indexing. Their values are Bedrock's own nested objects and nothing here reshapes them, so read each level with .get() and verify the nesting with try_call before indexing it. "metadata" is where the filters act: "contentTypeFilters" matches its content_type field and "slugs" its slug field. "fetchAllResults" stops after 50 pages of 100 results per query, roughly 5000, and logs the truncation rather than raising, so it is a large ceiling and not a guarantee of completeness.
execute_query()Execute a SQL query against an Azure SQL database. It runs with the rights of the configured login, so point the credential at a db_datareader login to keep it to reads.
result = qbash.integrations.mssql.execute_query(
query="search terms",
)
querystrrequiredSQL query to execute. Use @p1, @p2, ... placeholders for parameterised values.
outputFormatstr | Nonedefault"json_rows"Output format: json_rows, single_value, or columns_and_rows. Any other value falls back to json_rows without an error.
queryTimeoutint | str | Nonedefault30000Query timeout in milliseconds. A string of digits is parsed, and anything else falls back to 30000.
retryAttemptsint | str | Nonedefault4Attempts to make on transient errors, deadlocks and connection failures, from 1 to 10. Values outside that range are clamped rather than rejected.
queryParamslist | NonedefaultNoneValues bound to the @p1, @p2, ... placeholders, in order. Anything that is not a list is dropped and the query runs with no bound values, which fails if it holds placeholders.
executionModestr | Nonedefault"executesql"Set to raw to run the batch directly instead of through sp_executesql, for multi statement batches such as cursors or DECLARE. Any other value keeps sp_executesql.
{"status": "success", "result": {"rows": list[dict], "count": int}, "row_count": int} when outputFormat is json_rows, or {"status": "success", "result": {"columns": list[str], "rows": list[list], "count": int}, "row_count": int} when columns_and_rows, or {"status": "success", "result": {"value": str | int | float | bool | list | dict | None}, "row_count": 1} when single_valueRows are nested one level deeper than the PostgreSQL mode: they are at result["rows"], with result["count"] beside them, and the top level "row_count" repeats that count. Under json_rows each row is a dict keyed by column name, and under columns_and_rows rows are positional lists to be read alongside result["columns"]. For a statement that returns no rows, meaning DDL or an INSERT, UPDATE or DELETE, both counts hold the affected row count rather than 0. Under single_value the first column of the first row is wrapped as result["value"], which is None when nothing matched, and "row_count" is 1 in either case, so it cannot tell a match from a miss. Values are coerced to JSON types on the way out, so dates and timestamps arrive as str. There is no read only option on this integration, unlike the PostgreSQL one, so a query runs with whatever rights the credential holds. Transient Azure SQL errors and dropped connections are retried up to retryAttempts times, and the wait grows with each attempt: roughly 15 to 30 seconds before the second attempt, 30 to 45 before the third, and so on by another 15 seconds each time. Ten attempts therefore spend 11 to 13 minutes asleep before the call returns an error, and if the last error looks like Azure SQL waking from auto-pause the whole sequence runs a second time, so budget up to about double that. Set the task timeout above the figure or keep retryAttempts low. A deadlock is retried on a much shorter schedule that also grows, starting at 1 to 3 seconds and adding about a second per attempt.
list_schemas()List the user schemas in the connected database.
result = qbash.integrations.mssql.list_schemas()
{"status": "success", "schemas": list[dict]}Each entry in "schemas" holds "schema_name", "schema_id" and "table_count", where the count covers the tables in sys.tables and leaves out views. The built in schemas, meaning sys, INFORMATION_SCHEMA, guest and the db_ fixed role schemas, are excluded, so a database holding nothing else answers with an empty list rather than an error. The query runs with the rights of the configured login, so a schema the login cannot see is absent from the list.
list_tables()List the tables and views in a schema.
result = qbash.integrations.mssql.list_tables(
schema="…",
)
schemastr | Nonedefault"dbo"Schema to list. Omitting it, or passing an empty string, reads the dbo schema.
{"status": "success", "schema": str, "tables": list[dict]}"schema" repeats the schema that was read, including the default, so the caller does not have to track which one it asked for. Each entry in "tables" holds "TABLE_NAME" and "TABLE_TYPE" in the upper case INFORMATION_SCHEMA spelling, where the type is BASE TABLE or VIEW, and the rows are ordered by type and then name. A schema that does not exist, or one the configured login cannot read, answers with an empty list rather than an error, so check the length before reading the first entry.
describe_table()Describe the columns, indexes and foreign keys of a table.
result = qbash.integrations.mssql.describe_table(
table="…",
)
tablestrrequiredTable or view to describe. Missing or empty fails before any query runs.
schemastr | Nonedefault"dbo"Schema holding the table. Omitting it, or passing an empty string, reads the dbo schema.
{"status": "success", "schema": str, "table": str, "columns": list[dict], "indexes": list[dict], "foreign_keys": list[dict]}Three catalog queries run one after another and the first error ends the call, so a partial description never comes back. A column entry comes from INFORMATION_SCHEMA and is keyed in upper case: "COLUMN_NAME", "DATA_TYPE", "CHARACTER_MAXIMUM_LENGTH", which is -1 for the max types such as nvarchar(max), "NUMERIC_PRECISION", "NUMERIC_SCALE", "IS_NULLABLE", "COLUMN_DEFAULT" and "IS_PRIMARY_KEY", with the last two flags spelled YES or NO rather than as booleans. The index and foreign key entries come from the sys views and are keyed in lower case: an index holds "index_name", "type_desc", "is_unique", "is_primary_key" and a "columns" string of comma separated column names, and a foreign key holds "fk_name", "column_name", "references_table" and "references_column", one row per column of a composite key. A table that does not exist answers with three empty lists rather than an error, so read an empty "columns" as not found.
inspect()List the interactive elements on a page with the selectors that find them. Call this before writing automation for a site you have not seen.
result = qbash.integrations.browserless.inspect(
url="https://…",
)
urlstrrequiredAbsolute URL to open.
waitForSelectorstr | NonedefaultNoneCSS selector to wait for before extracting, for pages that render after load.
includeHiddenbool | NonedefaultFalseInclude elements that are not visible. Off by default.
timeoutMsint | Nonedefault60000Budget for the whole call. Maximum 180000.
{"url": str, "title": str, "elements": list[dict], "count": int, "truncated": bool}Runs a fixed extraction script through Browserless /function; a `code` param is ignored. Each element is a dict carrying whichever of tag, testId, role, type, name, ariaLabel, placeholder, label, text, href, disabled and visible apply (empty and false fields are omitted; text is trimmed to 120 characters). At most 400 elements are returned in document order, and truncated=True means that cap was hit, so elements further down the page are missing. `url` and `title` are read from the page after navigation, so they reflect any redirect.
run()Run browser automation and return what it observed. Assertions are code that throws; a thrown error comes back as ok=false with the message.
result = qbash.integrations.browserless.run(
code="…",
)
codestrrequiredA Puppeteer function as a string. Must be a default export taking {page, context} and returning {data, type}. Example: export default async ({page, context}) => { await page.goto(context.url); await page.type('[data-testid="email-input"]', context.email); await page.click('[data-testid="login-btn"]'); await page.waitForSelector('[data-testid="home"]', {timeout: 15000}); const title = await page.title(); return {data: {title}, type: "application/json"}; } Throw to fail: if (!title) throw new Error("no title").
contextdict | NonedefaultNoneValues passed to the function as `context`. Put URLs and credentials here rather than inlining them in code.
timeoutMsint | Nonedefault60000Budget for the whole call. Maximum 180000.
{"ok": True, "result": dict | list | str | int | float | bool | None} | {"ok": False, "error": str}Posts `code` to Browserless /function. `context` is the dict handed to the function as its `context` argument (`export default async ({page, context}) => ...`); put the target URL, credentials and test data there rather than inlining them in code. `result` is whatever the function returned as `data`, decoded from JSON when possible. A thrown error (a failed assertion) comes back as ok=False with the message in `error`, not as an exception. Auth, quota, timeout and transport problems raise an error instead, so ok=False always means the automation itself failed. The whole run is bounded by timeoutMs (maximum 180000).
content()Return a page's rendered HTML. Prefer `inspect` unless you need the raw markup.
result = qbash.integrations.browserless.content(
url="https://…",
)
urlstrrequiredAbsolute URL to open.
waitForSelectorstr | NonedefaultNoneCSS selector to wait for before capturing.
timeoutMsint | Nonedefault60000Budget for the whole call. Maximum 180000.
{"url": str, "html": str, "bytes": int, "truncated": bool}Fetches the rendered DOM through Browserless /content, after waitForSelector if given. `html` is cut at 500000 bytes (500 KB) with a "… (truncated)" marker appended and truncated=True; `bytes` is the size of the full document before the cut. `url` is the requested URL, not the final one after redirects. Prefer inspect for locating elements; use this only when the raw markup itself is needed.
screenshot()Capture a page as a PNG, returned base64-encoded.
result = qbash.integrations.browserless.screenshot(
url="https://…",
)
urlstrrequiredAbsolute URL to open.
fullPagebool | NonedefaultFalseCapture the full scrollable page rather than the viewport.
timeoutMsint | Nonedefault60000Budget for the whole call. Maximum 180000.
{"url": str, "png_base64": str, "bytes": int}Captures the page through Browserless /screenshot as a PNG and returns it base64-encoded in `png_base64`; `bytes` is the size of the raw PNG before encoding (the base64 string is about a third larger). fullPage=True captures the whole scroll height rather than the viewport, which on a long page can be many times the size. Images over 5000000 bytes (5 MB) are refused with an error rather than truncated, since a cut PNG is not an image; retry with fullPage=False or a smaller page.
list_organizations()List organizations for the authenticated user.
result = qbash.integrations.bugsnag.list_organizations(
per_page=0,
page_token="…",
)
per_pageint | NonedefaultNoneNumber of organizations per page, from 1 to 100. Ignored with page_token.
page_tokenstr | NonedefaultNoneAn opaque token from a previous response, the next_page_token it returned.
{"status": "success", "organizations": list[dict], "count": int, "next_page_token": str | None, "total_count": int | None}Each organization map is cut down to id, name, slug and created_at, and any of those keys that Bugsnag returns as null is dropped, so read them with .get(). Every other field Bugsnag sends is discarded. The count key is the number of organizations in this response. Bugsnag returns one page per call. The next_page_token key is present only when Bugsnag has more organizations, so pass it back as page_token to fetch the next page. It is an opaque token from a previous response of this mode and expires after 24 hours. It already carries the page size, so per_page is ignored alongside it. A page_token that has expired or did not come from this mode fails the call, so start again from the first page without it. The total_count key is Bugsnag's total across all pages and is present only when Bugsnag sends it.
list_projects()List projects for an organization.
result = qbash.integrations.bugsnag.list_projects(
organization_id="…",
)
organization_idstrrequiredOrganization ID.
per_pageint | NonedefaultNoneNumber of projects per page, from 1 to 100. Ignored with page_token.
page_tokenstr | NonedefaultNoneAn opaque token from a previous response, the next_page_token it returned.
{"status": "success", "projects": list[dict], "count": int, "next_page_token": str | None, "total_count": int | None}Each project map is cut down to id, name, slug, created_at, updated_at and url, and any of those keys that Bugsnag returns as null is dropped, so read them with .get(). Every other field Bugsnag sends is discarded. The count key is the number of projects in this response. Bugsnag returns one page per call. The next_page_token key is present only when the organization has more projects, so pass it back as page_token to fetch the next page. It is an opaque token from a previous response of this mode and expires after 24 hours. It already carries the page size and the organization, so pass the same organization_id that produced it, and per_page is ignored alongside it. A page_token that has expired or did not come from this mode fails the call, so start again from the first page without it. The total_count key is Bugsnag's total across all pages and is present only when Bugsnag sends it.
list_errors()List errors for a project with optional sorting.
result = qbash.integrations.bugsnag.list_errors(
project_id="…",
)
project_idstrrequiredProject ID.
sortstr | NonedefaultNoneSort field (last_seen, first_seen, events, users, severity).
directionstr | NonedefaultNoneSort direction: asc or desc.
per_pageint | NonedefaultNoneNumber of results per page.
{"status": "success", "errors": list[dict], "count": int}Each error map carries id, error_class, message, severity, status, events, users, first_seen, last_seen and release_stages. Bugsnag's events_count and users_affected arrive under the shorter names events and users. Keys whose value is null are dropped, so read them with .get(). The status key inside an error map is the Bugsnag error status, such as open or fixed, and is separate from the top-level status key. The count key is the size of the page that came back, not the project total, and no cursor or page number is returned, so errors past the first page cannot be reached through this mode. Only sort, direction and per_page are sent, so Bugsnag's filter parameters are not available here.
get_error()Get a single error by ID.
result = qbash.integrations.bugsnag.get_error(
project_id="…",
error_id="…",
)
project_idstrrequiredProject ID.
error_idstrrequiredError ID.
{"status": "success", "error": dict}The error map carries id, error_class, message, severity, status, events, users, first_seen, last_seen and release_stages. Keys whose value is null are dropped, so read them with .get(), and every other field Bugsnag sends is discarded. The status key inside the error map is the Bugsnag error status, such as open or fixed, and is separate from the top-level status key.
get_error_events()List events (occurrences) for an error.
result = qbash.integrations.bugsnag.get_error_events(
project_id="…",
error_id="…",
)
project_idstrrequiredProject ID.
error_idstrrequiredError ID.
per_pageint | NonedefaultNoneNumber of results per page.
{"status": "success", "events": list[dict], "count": int}Each event map carries id, error_class, message, severity, context, device, app, stacktrace, breadcrumbs and metadata. Keys whose value is null are dropped, so read them with .get(). The stacktrace, breadcrumbs, device, app and metadata values are nested structures passed through as Bugsnag sends them, so call try_call to see their shape before indexing into them. The count key is the size of the page that came back, and no cursor is returned, so events past the first page cannot be reached through this mode.
get_event()Get a single event (error occurrence) by ID.
result = qbash.integrations.bugsnag.get_event(
project_id="…",
event_id="…",
)
project_idstrrequiredProject ID.
event_idstrrequiredEvent ID.
{"status": "success", "event": dict}The event map carries id, error_class, message, severity, context, device, app, stacktrace, breadcrumbs and metadata. Keys whose value is null are dropped, so read them with .get(), and every other field Bugsnag sends is discarded. The stacktrace, breadcrumbs, device, app and metadata values are nested structures passed through as Bugsnag sends them, so call try_call to see their shape before indexing into them.
get_error_trend()Get error event count over time buckets.
result = qbash.integrations.bugsnag.get_error_trend(
project_id="…",
error_id="…",
)
project_idstrrequiredProject ID.
error_idstrrequiredError ID.
buckets_countint | NonedefaultNoneNumber of time buckets to return.
{"status": "success", "trend": list[dict], "count": int}Each bucket carries from, to and events_count, with null values dropped, so read them with .get(). The count key is the number of buckets in the response rather than a total event count, and it reflects what Bugsnag returned rather than what buckets_count asked for.
get_project_stability()Get project stability trend data.
result = qbash.integrations.bugsnag.get_project_stability(
project_id="…",
)
project_idstrrequiredProject ID.
{"status": "success", "stability": dict | list | None}The stability value is the decoded body of Bugsnag's stability trend endpoint with no reshaping, so its keys come from Bugsnag rather than from this integration and are not verified here. No formatter constrains it the way the other modes are constrained, so it can arrive as a map or as a list of time buckets. Check the type before indexing, and call try_call to see a real response. stability is None when the project has no sessions in its primary release stage. Bugsnag only has stability data for projects that report sessions, and it answers Resource not found when a project has no sessions in any release stage.
list_companies()List portfolio companies.
result = qbash.integrations.cobalt.list_companies(
sort="…",
limit="…",
)
sortstr | NonedefaultNoneField to sort the companies by. The value is passed to Cobalt as given and is not checked here.
limitstr | NonedefaultNoneMaximum number of companies to return, passed to Cobalt as the limit query parameter. Cobalt decides the value when it is omitted.
offsetstr | NonedefaultNoneNumber of companies to skip, passed to Cobalt as the offset query parameter. Pair it with limit to walk further pages.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Paging is caller driven through limit and offset. Nothing loops here, so one call is one request to Cobalt, and no cursor, next page marker or total count is added to the result. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
get_company()Get a company by identifier.
result = qbash.integrations.cobalt.get_company(
company_uid="…",
)
company_uidstrrequiredUID of the company to read. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt answering 404 becomes an error rather than a success envelope, so "data" is never a not found marker. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
create_company()Create a company.
result = qbash.integrations.cobalt.create_company(
name="…",
)
namestrrequiredCompany name.
currencystr | NonedefaultNoneCurrency code for the company, for example USD.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter given to this mode apart from mode itself is forwarded to Cobalt as the request body, not only the two declared here, and Cobalt rather than this integration decides what a created company comes back as. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
update_company()Update a company.
result = qbash.integrations.cobalt.update_company(
company_uid="…",
)
company_uidstrrequiredUID of the company to update. Missing or empty fails before the request is sent. It goes in the path and is not part of the body.
namestr | NonedefaultNoneNew company name.
currencystr | NonedefaultNoneNew currency code for the company.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode and company_uid is forwarded to Cobalt as the request body, not only the two declared here. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
delete_company()Delete a company.
result = qbash.integrations.cobalt.delete_company(
company_uid="…",
)
company_uidstrrequiredUID of the company to delete. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt decides what a delete answers with. A body is forwarded as it stands and a response with no body arrives as an empty dict, so do not rely on a deleted flag being present. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
list_custom_fields()List custom field definitions.
result = qbash.integrations.cobalt.list_custom_fields()
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}The mode takes no parameters. Client.list_custom_fields/2 accepts sort, limit and offset, but this mode passes none of them, so what comes back is whatever Cobalt returns for an unfiltered request and there is no way to page from here. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
get_custom_field()Get a custom field by identifier.
result = qbash.integrations.cobalt.get_custom_field(
custom_field_uid="…",
)
custom_field_uidstrrequiredUID of the custom field definition to read. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt answering 404 becomes an error rather than a success envelope, so "data" is never a not found marker. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
create_custom_field()Create a custom field.
result = qbash.integrations.cobalt.create_custom_field(
name="…",
field_type=0,
has_date=True,
default_fund=True,
default_company=True,
default_deal=True,
default_cashflow=True,
)
namestrrequiredCustom field name.
field_typeintrequiredCobalt field type identifier. The valid values are Cobalt's and are not checked here.
has_dateboolrequiredWhether values of this field carry a date.
default_fundboolrequiredWhether the field is applied to funds by default.
default_companyboolrequiredWhether the field is applied to companies by default.
default_dealboolrequiredWhether the field is applied to deals by default.
default_cashflowboolrequiredWhether the field is applied to cashflows by default.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode is forwarded to Cobalt as the request body. None of the seven declared here is checked before the request, so a missing one surfaces as a Cobalt error rather than a local one. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
update_custom_field()Update a custom field.
result = qbash.integrations.cobalt.update_custom_field(
custom_field_uid="…",
)
custom_field_uidstrrequiredUID of the custom field definition to update. Missing or empty fails before the request is sent. It goes in the path and is not part of the body.
namestr | NonedefaultNoneNew custom field name.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode and custom_field_uid is forwarded to Cobalt as the request body, not only the one declared here. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
delete_custom_field()Delete a custom field.
result = qbash.integrations.cobalt.delete_custom_field(
custom_field_uid="…",
)
custom_field_uidstrrequiredUID of the custom field definition to delete. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt decides what a delete answers with. A body is forwarded as it stands and a response with no body arrives as an empty dict, so do not rely on a deleted flag being present. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
list_custom_field_values()List custom field values for entities.
result = qbash.integrations.cobalt.list_custom_field_values(
entity_uids="…",
)
entity_uidsstrrequiredComma separated UIDs of the entities to read values for. Missing or empty fails before the request is sent.
custom_field_uidsstr | NonedefaultNoneComma separated UIDs of the custom fields to restrict the read to. All fields on the entities are read when omitted.
as_of_datestr | NonedefaultNoneDate to read the values as of, formatted YYYY-MM-DD. The format is not checked here.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Paging is not exposed. Client.list_custom_field_values/2 accepts sort, limit and offset, but this mode forwards only entity_uids, custom_field_uids and as_of_date, so one call returns whatever Cobalt returns for that filter with no cursor to continue from. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
get_custom_field_value()Get a custom field value by identifier.
result = qbash.integrations.cobalt.get_custom_field_value(
uid="…",
)
uidstrrequiredUID of the custom field value to read. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt answering 404 becomes an error rather than a success envelope, so "data" is never a not found marker. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
create_custom_field_value()Create a custom field value.
result = qbash.integrations.cobalt.create_custom_field_value(
custom_field_uid="…",
value="…",
)
custom_field_uidstrrequiredUID of the custom field definition the value belongs to.
valuestrrequiredValue to store.
company_uidstr | NonedefaultNoneUID of the company the value attaches to. One of company_uid, deal_uid, fund_uid or cashflow_uid is needed, and which one is required is decided by Cobalt rather than here.
deal_uidstr | NonedefaultNoneUID of the deal the value attaches to.
fund_uidstr | NonedefaultNoneUID of the fund the value attaches to.
cashflow_uidstr | NonedefaultNoneUID of the cashflow the value attaches to.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode is forwarded to Cobalt as the request body. Nothing here checks that one of the four entity UIDs was supplied, so an empty request reaches Cobalt and comes back as a Cobalt error. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
update_custom_field_value()Update a single custom field value.
result = qbash.integrations.cobalt.update_custom_field_value(
uid="…",
)
uidstrrequiredUID of the custom field value to update. Missing or empty fails before the request is sent. It goes in the path and is not part of the body.
valuestr | NonedefaultNoneNew value to store.
datestr | NonedefaultNoneDate to store alongside the value, for a custom field that carries one.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode and uid is forwarded to Cobalt as the request body, not only the two declared here. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
update_custom_field_values_batch()Batch update custom field values.
result = qbash.integrations.cobalt.update_custom_field_values_batch(
custom_field_values=[...],
)
custom_field_valueslistrequiredList of custom field value objects to write. Each object's shape is Cobalt's and is not checked here.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Only custom_field_values is forwarded. Any other parameter is dropped before the request. Cobalt decides what a batch write answers with, and nothing here reports which entries succeeded. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
delete_custom_field_value()Delete a custom field value.
result = qbash.integrations.cobalt.delete_custom_field_value(
uid="…",
)
uidstrrequiredUID of the custom field value to delete. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt decides what a delete answers with. A body is forwarded as it stands and a response with no body arrives as an empty dict, so do not rely on a deleted flag being present. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
list_deals()List deals with optional filters.
result = qbash.integrations.cobalt.list_deals(
portfolio_uid="…",
fund_uid="…",
)
portfolio_uidstr | NonedefaultNoneRestrict the deals to one portfolio by UID.
fund_uidstr | NonedefaultNoneRestrict the deals to one fund by UID.
company_uidstr | NonedefaultNoneRestrict the deals to one company by UID.
sortstr | NonedefaultNoneField to sort the deals by. The value is passed to Cobalt as given and is not checked here.
limitstr | NonedefaultNoneMaximum number of deals to return, passed to Cobalt as the limit query parameter. Cobalt decides the value when it is omitted.
offsetstr | NonedefaultNoneNumber of deals to skip, passed to Cobalt as the offset query parameter. Pair it with limit to walk further pages.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Paging is caller driven through limit and offset. Nothing loops here, so one call is one request to Cobalt, and no cursor, next page marker or total count is added to the result. The dispatch branch also forwards deal_uid, portfolio_uids, fund_uids, company_uids, deal_uids and include_external to Cobalt, and none of them is declared here, so an editor test run rejects the call before dispatch if a script passes one. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
get_deal()Get a deal by identifier.
result = qbash.integrations.cobalt.get_deal(
deal_uid="…",
)
deal_uidstrrequiredUID of the deal to read. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt answering 404 becomes an error rather than a success envelope, so "data" is never a not found marker. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
create_deal()Create a deal.
result = qbash.integrations.cobalt.create_deal(
company_uid="…",
fund_uid="…",
)
company_uidstrrequiredUID of the company the deal belongs to.
fund_uidstrrequiredUID of the fund the deal belongs to.
currency_symbolstr | NonedefaultNoneCurrency symbol for the deal, for example USD.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode is forwarded to Cobalt as the request body. Neither company_uid nor fund_uid is checked before the request, so a missing one surfaces as a Cobalt error rather than a local one. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
update_deals()Batch update deals.
result = qbash.integrations.cobalt.update_deals(
deals=[...],
)
dealslistrequiredList of deal objects to write. Each object's shape is Cobalt's and is not checked here.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Only deals is forwarded. Any other parameter is dropped before the request. Cobalt decides what a batch write answers with, and nothing here reports which entries succeeded. There is no single deal update mode. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
delete_deal()Delete a single deal.
result = qbash.integrations.cobalt.delete_deal(
deal_uid="…",
)
deal_uidstrrequiredUID of the deal to delete. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt decides what a delete answers with. A body is forwarded as it stands and a response with no body arrives as an empty dict, so do not rely on a deleted flag being present. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
delete_deals_batch()Batch delete deals.
result = qbash.integrations.cobalt.delete_deals_batch(
deal_uids="…",
)
deal_uidsstrrequiredComma separated UIDs of the deals to delete. Missing or empty fails before the request is sent. They travel as a query parameter rather than a body.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt decides what a batch delete answers with, and nothing here reports which UIDs were deleted. A response with no body arrives as an empty dict, so do not rely on a count being present. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
list_funds()List funds.
result = qbash.integrations.cobalt.list_funds(
portfolio_uid="…",
sort="…",
)
portfolio_uidstr | NonedefaultNoneRestrict the funds to one portfolio by UID.
sortstr | NonedefaultNoneField to sort the funds by. The value is passed to Cobalt as given and is not checked here.
limitstr | NonedefaultNoneMaximum number of funds to return, passed to Cobalt as the limit query parameter. Cobalt decides the value when it is omitted.
offsetstr | NonedefaultNoneNumber of funds to skip, passed to Cobalt as the offset query parameter. Pair it with limit to walk further pages.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Paging is caller driven through limit and offset. Nothing loops here, so one call is one request to Cobalt, and no cursor, next page marker or total count is added to the result. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
get_fund()Get a fund by identifier.
result = qbash.integrations.cobalt.get_fund(
fund_uid="…",
)
fund_uidstrrequiredUID of the fund to read. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt answering 404 becomes an error rather than a success envelope, so "data" is never a not found marker. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
create_fund()Create a fund.
result = qbash.integrations.cobalt.create_fund(
name="…",
)
namestrrequiredFund name.
portfolio_uidstr | NonedefaultNoneUID of the portfolio the fund belongs to.
currencystr | NonedefaultNoneCurrency code for the fund, for example USD.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode is forwarded to Cobalt as the request body, not only the three declared here. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
update_fund()Update a fund.
result = qbash.integrations.cobalt.update_fund(
fund_uid="…",
)
fund_uidstrrequiredUID of the fund to update. Missing or empty fails before the request is sent. It goes in the path and is not part of the body.
namestr | NonedefaultNoneNew fund name.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode and fund_uid is forwarded to Cobalt as the request body, not only the one declared here. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
delete_fund()Delete a fund.
result = qbash.integrations.cobalt.delete_fund(
fund_uid="…",
)
fund_uidstrrequiredUID of the fund to delete. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt decides what a delete answers with. A body is forwarded as it stands and a response with no body arrives as an empty dict, so do not rely on a deleted flag being present. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
list_portfolios()List portfolios.
result = qbash.integrations.cobalt.list_portfolios(
limit="…",
offset="…",
)
limitstr | NonedefaultNoneMaximum number of portfolios to return, passed to Cobalt as the limit query parameter. Cobalt decides the value when it is omitted.
offsetstr | NonedefaultNoneNumber of portfolios to skip, passed to Cobalt as the offset query parameter. Pair it with limit to walk further pages.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Paging is caller driven through limit and offset. Nothing loops here, so one call is one request to Cobalt, and no cursor, next page marker or total count is added to the result. Sorting is not exposed on this mode. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
get_portfolio()Get a portfolio by identifier.
result = qbash.integrations.cobalt.get_portfolio(
portfolio_uid="…",
)
portfolio_uidstrrequiredUID of the portfolio to read. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt answering 404 becomes an error rather than a success envelope, so "data" is never a not found marker. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
create_portfolio()Create a portfolio.
result = qbash.integrations.cobalt.create_portfolio(
name="…",
)
namestrrequiredPortfolio name.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode is forwarded to Cobalt as the request body, not only the one declared here. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
update_portfolio()Update a portfolio.
result = qbash.integrations.cobalt.update_portfolio(
portfolio_uid="…",
)
portfolio_uidstrrequiredUID of the portfolio to update. Missing or empty fails before the request is sent. It goes in the path and is not part of the body.
namestr | NonedefaultNoneNew portfolio name.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Accepts vendor fields beyond those listed and forwards them unchanged.
Every parameter apart from mode and portfolio_uid is forwarded to Cobalt as the request body, not only the one declared here. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
delete_portfolio()Delete a portfolio.
result = qbash.integrations.cobalt.delete_portfolio(
portfolio_uid="…",
)
portfolio_uidstrrequiredUID of the portfolio to delete. Missing or empty fails before the request is sent.
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}Cobalt decides what a delete answers with. A body is forwarded as it stands and a response with no body arrives as an empty dict, so do not rely on a deleted flag being present. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
list_currencies()List the currencies Cobalt recognises.
result = qbash.integrations.cobalt.list_currencies()
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}The mode takes no parameters and adds no paging, so what comes back is whatever Cobalt returns for an unfiltered request. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
check_status()Check Cobalt API status.
result = qbash.integrations.cobalt.check_status()
{"status": "success", "data": dict | list | str | int | float | bool | None} or {"status": "success", "data": {"no_changes": True}}A success envelope means Cobalt answered with a 2xx and accepted the credential, which is what this mode is for. The keys inside "data" are Cobalt's status body and nothing here reads them. "status" and "data" are the only two keys, and both are built by this integration. "data" is Cobalt's own response body, decoded from JSON and forwarded without reshaping, so every key inside it belongs to Cobalt apart from "_rate_limit" below, which this integration adds. Read those keys with .get(), and check the type of "data" before indexing, because a JSON array body arrives as a list and a top-level JSON string, number, boolean or null arrives as that value rather than as a dict. An empty response body arrives as an empty dict and a 304 arrives as {"no_changes": True}, so neither carries a record. A "_rate_limit" dict holding "limit", "remaining" and "reset" is merged into "data" only when Cobalt sends the matching headers and "data" is a dict, so read that key with .get() too.
keyword_volume()Look up search volume, CPC, and competition for a known list of keywords.
result = qbash.integrations.dataforseo.keyword_volume(
keywords=[...],
)
keywordslistrequiredList of keyword strings to look up (up to 1000). Accepts a comma-separated string as a fallback.
location_codeintdefault2840DataForSEO location code (2840 = United States, 2826 = United Kingdom, 2124 = Canada, 2036 = Australia, 2276 = Germany).
language_codestrdefault"en"ISO 639-1 language code (e.g. en, es, de, fr).
include_monthly_searchesbooldefaultFalseInclude the monthly_searches array on each row. The array is large, so opt in only when needed.
{"status": "success", "keywords": list[dict | str | int | float | bool | None], "count": int}DataForSEO nests results under a "tasks" array with its own status codes. The service unwraps the first task only, turns any task status other than 20000 into an error, and hands back the rows under tasks[0].result, so "status" is "success" for every result you see here and no task wrapper reaches the script. "count" is the length of "keywords" and is not guaranteed to match the number of keywords sent, so match each row on its own "keyword" field. Each row is the vendor's, with null valued top-level keys removed, so read it with .get(). The monthly_searches array is stripped from every row unless include_monthly_searches is true.
keyword_ideas()Generate keyword ideas from seed keywords. Returns related keywords with volume and CPC.
result = qbash.integrations.dataforseo.keyword_ideas(
keywords=[...],
)
keywordslistrequiredSeed keywords. List of strings or comma-separated string.
location_codeintdefault2840DataForSEO location code (default 2840 = United States).
language_codestrdefault"en"ISO 639-1 language code.
limitintdefault100Maximum number of ideas to return, from 1 to 1000. Applied client-side, so the full result set is fetched from DataForSEO and then trimmed.
{"status": "success", "keywords": list[dict | str | int | float | bool | None], "count": int}The rows come from the first task in DataForSEO's "tasks" array, so the task wrapper and its status code never reach the script and "status" is "success" for every result. "limit" is applied here as a slice after the full set arrives, and no offset is exposed, so rows past the limit cannot be reached. Raise "limit" up to 1000 instead. "count" is the length of "keywords" after that slice. Each row is the vendor's, with null valued top-level keys removed, so read it with .get().
keyword_suggestions()Long-tail keyword discovery from a single seed keyword. Returns suggestions with difficulty and volume.
result = qbash.integrations.dataforseo.keyword_suggestions(
keyword="my-key",
)
keywordstrrequiredSingle seed keyword to expand.
location_codeintdefault2840DataForSEO location code (default 2840 = United States).
language_codestrdefault"en"ISO 639-1 language code.
limitintdefault100Maximum suggestions to return, from 1 to 1000.
offsetintdefault0Pagination offset.
include_seed_keywordbooldefaultTrueWhether to include the seed keyword itself in the results.
{"status": "success", "keyword": str, "suggestions": list[dict | str | int | float | bool | None], "count": int}"keyword" echoes the seed that was sent. The rows come from tasks[0].result[0].items on the Labs endpoint, so any further result object is dropped and the counters on that result object are not exposed. An absent or empty items array gives "suggestions": [] and "count": 0 rather than an error, and "count" is the length of this page only. Page with "limit" and "offset", which are sent to DataForSEO rather than applied here. Each row is the vendor's, with null valued top-level keys removed, so read it with .get(). The service sends include_serp_info as false and does not expose it, so no SERP detail is requested.
related_keywords()Expand a seed keyword via Google's "searches related to" graph.
result = qbash.integrations.dataforseo.related_keywords(
keyword="my-key",
)
keywordstrrequiredSingle seed keyword to expand.
location_codeintdefault2840DataForSEO location code (default 2840 = United States).
language_codestrdefault"en"ISO 639-1 language code.
limitintdefault100Maximum related keywords to return, from 1 to 1000.
offsetintdefault0Pagination offset.
depthintdefault1How many levels of related-to-related to traverse. Keep this at 1, since higher values grow the result set sharply.
{"status": "success", "keyword": str, "related": list[dict | str | int | float | bool | None], "count": int}The collection key is "related" here, not "keywords". "keyword" echoes the seed that was sent. The rows come from tasks[0].result[0].items on the Labs endpoint, so any further result object is dropped and the counters on that result object are not exposed. An absent or empty items array gives "related": [] and "count": 0 rather than an error, and "count" is the length of this page only. Page with "limit" and "offset", which are sent to DataForSEO rather than applied here. Each row is the vendor's items entry with only its null valued top-level keys removed. Nothing here flattens it, so read it with .get().
get_account()Get the DigitalOcean account the API token belongs to: email, status, Droplet limit and team.
result = qbash.integrations.digitalocean.get_account()
{"status": "success", "account": dict}account has email, uuid, status (active, warning or locked), droplet_limit and team ({uuid, name}). Keys with no value are left out, so read optional keys with .get(). Calls GET /v2/account.
get_balance()Get the account's month-to-date balance and usage in US dollars.
result = qbash.integrations.digitalocean.get_balance()
{"status": "success", "month_to_date_balance": str, "account_balance": str, "month_to_date_usage": str, "generated_at": str}Amounts are decimal strings in US dollars, such as "23.44", so convert them with float() or Decimal. month_to_date_balance is account_balance plus month_to_date_usage. Keys with no value are left out, so read optional keys with .get(). A token with custom scopes needs read access to billing. Calls GET /v2/customers/my/balance.
list_invoices()List past invoices with amount and billing period, plus a preview of the invoice for the current month.
result = qbash.integrations.digitalocean.list_invoices(
page=0,
per_page=0,
)
pageintdefault1Page number, starting at 1.
per_pageintdefault50Items per page, from 1 through 200.
{"status": "success", "invoices": list[dict], "invoice_preview": dict | None, "next_page": int | None, "total": int | None}Each invoice has invoice_uuid, amount (a US dollar string), invoice_period (YYYY-MM) and updated_at. invoice_preview has the same keys for the month in progress. A token with custom scopes needs read access to billing. Calls GET /v2/customers/my/invoices. Paging: pass next_page back as page until it is None. total is the item count DigitalOcean reports, when it reports one.
list_droplets()List Droplets with status, size, region, image, public and private IPv4 addresses and tags.
result = qbash.integrations.digitalocean.list_droplets(
tag_name="…",
page=0,
)
tag_namestr | NonedefaultNoneOnly resources with this tag.
pageintdefault1Page number, starting at 1.
per_pageintdefault50Items per page, from 1 through 200.
{"status": "success", "droplets": list[dict], "next_page": int | None, "total": int | None}Each Droplet has id, name, status (new, active, off or archive), memory (MB), vcpus, disk (GB), region (slug), size_slug, image (the slug, or the name of a snapshot or custom image), public_ipv4, private_ipv4, tags and created_at. Keys with no value are left out, so read optional keys with .get(). Calls GET /v2/droplets. Paging: pass next_page back as page until it is None. total is the item count DigitalOcean reports, when it reports one.
get_droplet()Get one Droplet by id.
result = qbash.integrations.digitalocean.get_droplet(
droplet_id=0,
)
droplet_idintrequiredDroplet id, a number such as 3164444. list_droplets returns it.
{"status": "success", "droplet": dict}droplet has the list_droplets keys plus public_ipv6, vpc_uuid, volume_ids and features. Keys with no value are left out, so read optional keys with .get(). Calls GET /v2/droplets/{droplet_id}.
get_droplet_metrics()Get a Droplet's CPU, memory, load, filesystem or bandwidth metric as time series over a window. The window defaults to the last hour.
result = qbash.integrations.digitalocean.get_droplet_metrics(
droplet_id=0,
metric="…",
)
droplet_idintrequiredDroplet id, a number such as 3164444. list_droplets returns it.
metricstrrequiredcpu, memory_free, memory_available, memory_total, load_1, load_5, load_15, filesystem_free, filesystem_size or bandwidth.
startstr | NonedefaultNoneWindow start as an ISO 8601 time, such as 2026-10-14T09:00:00Z. Defaults to one hour before end.
endstr | NonedefaultNoneWindow end as an ISO 8601 time. Defaults to now.
interfacestr | NonedefaultNonepublic or private. Required for bandwidth.
directionstr | NonedefaultNoneinbound or outbound. Required for bandwidth.
{"status": "success", "series": list[dict]}Each series has labels (a dict, such as mode for cpu) and points, a list of {timestamp, value} with timestamp in unix seconds and value a float. cpu values are cumulative CPU seconds per mode, so compute usage from the change between two points. filesystem_free and filesystem_size are bytes, with one series per filesystem labelled by device, fstype and mountpoint. bandwidth needs interface and direction. An empty series list usually means the Droplet does not run the DigitalOcean metrics agent. Calls GET /v2/monitoring/metrics/droplet/{metric}.
droplet_action()Reboot, power cycle, power on, power off or shut down a Droplet.
result = qbash.integrations.digitalocean.droplet_action(
droplet_id=0,
type="…",
)
droplet_idintrequiredDroplet id, a number such as 3164444. list_droplets returns it.
typestrrequiredreboot, power_cycle, power_on, power_off or shutdown.
{"status": "success", "action": dict}shutdown asks the operating system to stop, and power_off cuts power the way a power switch would. DigitalOcean runs the action in the background: action.status starts as in-progress and later becomes completed or errored. action has id, status, type, started_at and completed_at. Calls POST /v2/droplets/{droplet_id}/actions.
list_apps()List App Platform apps with their URLs, region and active deployment.
result = qbash.integrations.digitalocean.list_apps(
page=0,
per_page=0,
)
pageintdefault1Page number, starting at 1.
per_pageintdefault50Items per page, from 1 through 200.
{"status": "success", "apps": list[dict], "next_page": int | None, "total": int | None}Each app has id, name, default_ingress, live_url, region (slug), active_deployment ({id, phase, created_at}) and updated_at. An app with no successful deployment has no active_deployment. Keys with no value are left out, so read optional keys with .get(). Calls GET /v2/apps. Paging: pass next_page back as page until it is None. total is the item count DigitalOcean reports, when it reports one.
get_app()Get one App Platform app by id, with the names of its components.
result = qbash.integrations.digitalocean.get_app(
app_id="…",
)
app_idstrrequiredApp id from list_apps.
{"status": "success", "app": dict}app has the list_apps keys plus created_at, in_progress_deployment ({id, phase, created_at}) while a deployment runs, and components, a dict of name lists under services, workers, jobs, static_sites and functions. Pass a component name to get_deployment_logs. Keys with no value are left out, so read optional keys with .get(). Calls GET /v2/apps/{app_id}.
get_app_spec()Get an App Platform app's full spec, including its environment variables, to read or to edit and pass to update_app.
result = qbash.integrations.digitalocean.get_app_spec(
app_id="…",
)
app_idstrrequiredApp id from list_apps.
{"status": "success", "app_id": str, "spec": dict}spec is the app spec exactly as DigitalOcean returns it: name, region, envs (the app-level environment variables) and the components under services, workers, jobs, static_sites and functions, each with its own envs. Each environment variable has key, value, scope (RUN_TIME, BUILD_TIME or RUN_AND_BUILD_TIME) and type (GENERAL or SECRET). A SECRET value comes back as ciphertext in the form EV[1:...], never as the plain text. A component's variable overrides an app-level one with the same key. Calls GET /v2/apps/{app_id}.
list_app_deployments()List an app's deployments with phase, cause and step progress.
result = qbash.integrations.digitalocean.list_app_deployments(
app_id="…",
)
app_idstrrequiredApp id from list_apps.
pageintdefault1Page number, starting at 1.
per_pageintdefault50Items per page, from 1 through 200.
{"status": "success", "deployments": list[dict], "next_page": int | None, "total": int | None}Each deployment has id, phase (such as PENDING_BUILD, BUILDING, DEPLOYING, ACTIVE, SUPERSEDED, ERROR or CANCELED), cause, progress ({success_steps, total_steps, error_steps}), created_at and updated_at. Calls GET /v2/apps/{app_id}/deployments. Paging: pass next_page back as page until it is None. total is the item count DigitalOcean reports, when it reports one.
get_deployment_logs()Get the build, deploy or run logs of an App Platform deployment as text, for one component or for all of them.
result = qbash.integrations.digitalocean.get_deployment_logs(
app_id="…",
)
app_idstrrequiredApp id from list_apps.
deployment_idstr | NonedefaultNoneDeployment id from list_app_deployments. Defaults to the active deployment.
component_namestr | NonedefaultNoneOne component's name from get_app, such as api. Omit for all components.
log_typestrdefaultBUILDBUILD, DEPLOY or RUN.
{"status": "success", "logs": str, "truncated": bool, "url_count": int, "deployment_id": str}deployment_id defaults to the app's active deployment, or to its latest deployment when none is active. To read why a deployment failed, pass its id from list_app_deployments. Without component_name the logs of every component come back together. DigitalOcean hands out the logs as files, which are joined in order. Only the last 1 MB is kept, and truncated is True when earlier lines were cut. Files are read from the last one back until that 1 MB is full, and a single file is read up to its first 20 MB. url_count is the number of files read. RUN logs of a running app can come back empty, because DigitalOcean serves them through a live stream that this method does not read. Calls GET /v2/apps/{app_id}/deployments/{deployment_id}/logs, or .../components/{component_name}/logs.
create_app_deployment()Start a new deployment of an App Platform app from its current spec.
result = qbash.integrations.digitalocean.create_app_deployment(
app_id="…",
)
app_idstrrequiredApp id from list_apps.
force_buildbooldefaultFalseTrue rebuilds from source even when the commit has not changed.
{"status": "success", "deployment": dict}deployment has the list_app_deployments keys. Follow its phase with list_app_deployments. Calls POST /v2/apps/{app_id}/deployments.
update_app()Replace an App Platform app's spec, such as to change its environment variables, and start the deployment that applies it.
result = qbash.integrations.digitalocean.update_app(
app_id="…",
spec={"key": "value"},
)
app_idstrrequiredApp id from list_apps.
specdictrequiredThe full app spec, as get_app_spec returned it with your changes.
update_all_source_versionsbooldefaultFalseTrue also pulls the latest commit or image for every component. By default only new components fetch their source.
{"status": "success", "app": dict, "deployment": dict | None}Read the spec with get_app_spec, change it and pass the whole spec back. The spec you pass replaces the app's spec, so a component or variable left out is removed. A SECRET variable whose value you leave as its EV[1:...] ciphertext keeps its value. To set a new secret, pass the plain text with type SECRET and DigitalOcean encrypts it. The task's step log does not store the spec, since it can hold secrets. DigitalOcean starts a deployment for the change. app has the get_app keys and deployment has the list_app_deployments keys, or is None when DigitalOcean reports no pending deployment. Follow its phase with list_app_deployments. The token needs the app:update scope. Calls PUT /v2/apps/{app_id}.
list_databases()List managed database clusters with engine, version, status, size and connection host, without credentials.
result = qbash.integrations.digitalocean.list_databases(
tag_name="…",
)
tag_namestr | NonedefaultNoneOnly resources with this tag.
{"status": "success", "databases": list[dict]}Each cluster has id, name, engine (such as pg, mysql, valkey or mongodb), version, status, region, size, num_nodes, connection ({host, port, database, user, ssl}) and created_at. Credentials such as passwords and connection URIs are removed. The list is not paged. Calls GET /v2/databases.
get_database()Get one managed database cluster by id, with credentials removed.
result = qbash.integrations.digitalocean.get_database(
database_id="…",
)
database_idstrrequiredDatabase cluster id from list_databases.
{"status": "success", "database": dict}database has the list_databases keys plus semantic_version, storage_size_mib, tags, db_names, project_id, private_network_uuid, maintenance_window, version_end_of_life, version_end_of_availability, private_connection, standby_connection and standby_private_connection (each {host, port, database, user, ssl}), and users ({name, role}). Credentials are removed: passwords, connection URIs, and Kafka access keys and certificates. Keys with no value are left out, so read optional keys with .get(). Calls GET /v2/databases/{database_id}.
list_kubernetes_clusters()List Kubernetes clusters with version, status and node pools.
result = qbash.integrations.digitalocean.list_kubernetes_clusters(
page=0,
per_page=0,
)
pageintdefault1Page number, starting at 1.
per_pageintdefault50Items per page, from 1 through 200.
{"status": "success", "clusters": list[dict], "next_page": int | None, "total": int | None}Each cluster has id, name, region, version, status (the state, such as running, provisioning or degraded), node_pools ({name, size, count}) and created_at. Calls GET /v2/kubernetes/clusters. Paging: pass next_page back as page until it is None. total is the item count DigitalOcean reports, when it reports one.
get_kubernetes_cluster()Get one Kubernetes cluster by id, with its node pools and nodes.
result = qbash.integrations.digitalocean.get_kubernetes_cluster(
cluster_id="…",
)
cluster_idstrrequiredCluster id from list_kubernetes_clusters.
{"status": "success", "cluster": dict}cluster is DigitalOcean's full cluster object, including node_pools with their nodes, endpoint, maintenance_policy and tags. It holds no kubeconfig. Calls GET /v2/kubernetes/clusters/{cluster_id}.
list_domains()List the domains in DigitalOcean DNS.
result = qbash.integrations.digitalocean.list_domains(
page=0,
per_page=0,
)
pageintdefault1Page number, starting at 1.
per_pageintdefault50Items per page, from 1 through 200.
{"status": "success", "domains": list[dict], "next_page": int | None, "total": int | None}Each domain has name and ttl. Calls GET /v2/domains. Paging: pass next_page back as page until it is None. total is the item count DigitalOcean reports, when it reports one.
list_domain_records()List a domain's DNS records, optionally filtered by type and name.
result = qbash.integrations.digitalocean.list_domain_records(
domain="…",
)
domainstrrequiredDomain name as DigitalOcean DNS lists it, such as example.com.
typestr | NonedefaultNoneOnly records of this type, such as A, CNAME, MX or TXT.
namestr | NonedefaultNoneOnly records with this full name, such as www.example.com.
pageintdefault1Page number, starting at 1.
per_pageintdefault50Items per page, from 1 through 200.
{"status": "success", "records": list[dict], "next_page": int | None, "total": int | None}Each record has id, type, name, data, priority, port, ttl, weight, flags and tag. name is relative to the domain, with @ for the domain itself, while the name filter takes the full name, such as www.example.com. Keys with no value are left out, so read optional keys with .get(). Calls GET /v2/domains/{domain}/records. Paging: pass next_page back as page until it is None. total is the item count DigitalOcean reports, when it reports one.
create_domain_record()Create a DNS record in a domain.
result = qbash.integrations.digitalocean.create_domain_record(
domain="…",
type="…",
)
domainstrrequiredDomain name as DigitalOcean DNS lists it, such as example.com.
typestrrequiredRecord type: A, AAAA, CAA, CNAME, MX, NS, SOA, SRV or TXT.
namestr | NonedefaultNoneHost name relative to the domain, such as www, or @ for the domain itself.
datastr | NonedefaultNoneRecord value: an IP address for A and AAAA, a host name for CNAME, MX, NS and SRV, or the text for TXT.
priorityint | NonedefaultNonePriority for MX and SRV records.
portint | NonedefaultNonePort for SRV records.
ttlint | NonedefaultNoneTime to live in seconds.
weightint | NonedefaultNoneWeight for SRV records.
flagsint | NonedefaultNoneFlags for CAA records, usually 0.
tagstr | NonedefaultNoneTag for CAA records: issue, issuewild or iodef.
{"status": "success", "record": dict}Only the params you pass are sent. In data, write a full host name for CNAME, MX, NS or SRV with a trailing dot (mail.example.com.). MX needs priority, SRV needs priority, port and weight, and CAA needs flags and tag. record has the list_domain_records keys. Calls POST /v2/domains/{domain}/records.
update_domain_record()Change fields of an existing DNS record.
result = qbash.integrations.digitalocean.update_domain_record(
domain="…",
record_id=0,
)
domainstrrequiredDomain name as DigitalOcean DNS lists it, such as example.com.
record_idintrequiredRecord id from list_domain_records.
typestr | NonedefaultNoneNew record type. Usually left out.
namestr | NonedefaultNoneHost name relative to the domain, such as www, or @ for the domain itself.
datastr | NonedefaultNoneRecord value: an IP address for A and AAAA, a host name for CNAME, MX, NS and SRV, or the text for TXT.
priorityint | NonedefaultNonePriority for MX and SRV records.
portint | NonedefaultNonePort for SRV records.
ttlint | NonedefaultNoneTime to live in seconds.
weightint | NonedefaultNoneWeight for SRV records.
flagsint | NonedefaultNoneFlags for CAA records, usually 0.
tagstr | NonedefaultNoneTag for CAA records: issue, issuewild or iodef.
{"status": "success", "record": dict}Only the params you pass change. record has the list_domain_records keys. Calls PATCH /v2/domains/{domain}/records/{record_id}.
delete_domain_record()Delete a DNS record from a domain.
result = qbash.integrations.digitalocean.delete_domain_record(
domain="…",
record_id=0,
)
domainstrrequiredDomain name as DigitalOcean DNS lists it, such as example.com.
record_idintrequiredRecord id from list_domain_records.
{"status": "success", "deleted": bool}Calls DELETE /v2/domains/{domain}/records/{record_id}.
list_uptime_checks()List uptime checks with their target, type and regions.
result = qbash.integrations.digitalocean.list_uptime_checks(
page=0,
per_page=0,
)
pageintdefault1Page number, starting at 1.
per_pageintdefault50Items per page, from 1 through 200.
{"status": "success", "checks": list[dict], "next_page": int | None, "total": int | None}Each check has id, name, type (ping, http or https), target, regions and enabled. Calls GET /v2/uptime/checks. Paging: pass next_page back as page until it is None. total is the item count DigitalOcean reports, when it reports one.
get_uptime_check_state()Get whether an uptime check's target is up or down in each region, with its 30-day uptime.
result = qbash.integrations.digitalocean.get_uptime_check_state(
check_id="…",
)
check_idstrrequiredUptime check id from list_uptime_checks.
{"status": "success", "state": dict}state has regions, a dict keyed by region (such as us_east) of {status, status_changed_at, thirty_day_uptime_percentage}, and previous_outage ({region, started_at, ended_at, duration_seconds}) when the check has seen one. Keys with no value are left out, so read optional keys with .get(). Calls GET /v2/uptime/checks/{check_id}/state.
list_spaces_buckets()List the Spaces buckets in the credential's Spaces region.
result = qbash.integrations.digitalocean.list_spaces_buckets()
{"status": "success", "buckets": list[dict]}Each bucket has name and creation_date. Only buckets in the credential's region come back, and a bucket in another region needs a credential set to that region. A bucket whose region the key cannot read, as with a key limited to some buckets, stays in the list. Spaces methods use the Spaces access key, secret key and region on the credential instead of the API token.
list_spaces_objects()List objects in a Spaces bucket, optionally under a key prefix.
result = qbash.integrations.digitalocean.list_spaces_objects(
bucket="…",
)
bucketstrrequiredSpaces bucket name.
prefixstr | NonedefaultNoneOnly keys that start with this prefix, such as logs/2026/.
markerstr | NonedefaultNonenext_marker from the previous call. Listing starts after this key. Omit for the first page.
max_keysintdefault1000Objects per call, from 1 through 1000.
{"status": "success", "objects": list[dict], "is_truncated": bool, "next_marker": str | None}Each object has key, size (bytes), last_modified and etag, in key order. Paging: while is_truncated is True, pass next_marker back as marker. Uses ListObjects (version 1), because Spaces does not page ListObjectsV2.
get_spaces_object()Download an object from a Spaces bucket and return it as a qbash file reference.
result = qbash.integrations.digitalocean.get_spaces_object(
bucket="…",
key="my-key",
)
bucketstrrequiredSpaces bucket name.
keystrrequiredObject key, the path inside the bucket, such as reports/2026-10.csv.
content_typestr | NonedefaultNoneMIME type override. Defaults to the type stored with the object, then the key's extension.
fileThe object is copied to qbash file storage and what comes back is a reference to it rather than the bytes. When the content type is an image the type key is aisle_input_image and the payload sits under a key of that name, so read type first and index with it. The content type comes from content_type when it is given, then from the type stored with the object, then from the key's file extension. The object streams into storage, so objects up to qbash's 5 GB file limit can be downloaded. A larger object, or one whose size Spaces does not report, is refused before the download starts. A key ending in / names a folder marker and is refused. The download is pinned to the version the size check saw, so an object that changes during the download fails the call, and calling again reads the new version. A multi-GB download needs a run whose expected runtime is over 15 minutes.
put_spaces_object()Upload text or a qbash file reference to a Spaces bucket.
result = qbash.integrations.digitalocean.put_spaces_object(
bucket="…",
key="my-key",
)
bucketstrrequiredSpaces bucket name.
keystrrequiredObject key, the path inside the bucket, such as reports/2026-10.csv.
contentstr | NonedefaultNoneText to upload. Pass content or file.
filefile | NonedefaultNoneA qbash file reference, such as the result of get_spaces_object. Pass content or file. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
content_typestr | NonedefaultNoneMIME type to store with the object.
aclstrdefaultprivateprivate or public-read.
{"status": "success", "bucket": str, "key": str, "etag": str | None, "content_type": str, "size": int}Pass content for text, written as UTF-8, or file for a file reference that another method returned, such as get_spaces_object. Text content is limited to 500 MB. A file reference goes up in parts straight from storage, up to qbash's 5 GB file limit. content_type defaults to the stored file's type for a file reference and to the type the key's extension implies otherwise. A public-read object can be fetched by anyone at https://<bucket>.<region>.digitaloceanspaces.com/<key>. An existing object at the same key is replaced.
delete_spaces_object()Delete an object from a Spaces bucket.
result = qbash.integrations.digitalocean.delete_spaces_object(
bucket="…",
key="my-key",
)
bucketstrrequiredSpaces bucket name.
keystrrequiredObject key, the path inside the bucket, such as reports/2026-10.csv.
{"status": "success", "deleted": bool, "bucket": str, "key": str}Deleting a key that does not exist also succeeds.
list_meetings()List recent meetings with optional date range and recorder filters.
result = qbash.integrations.fathom.list_meetings(
cursor="…",
recorded_by="…",
)
cursorstr | NonedefaultNonePagination cursor from a previous response.
recorded_bystr | NonedefaultNoneRecorded By (email) - filter to meetings recorded by this user.
created_afterstr | NonedefaultNoneCreated After (ISO 8601) - earliest meeting to include.
created_beforestr | NonedefaultNoneCreated Before (ISO 8601) - newest meeting to include.
teamsstr | NonedefaultNoneTeams (JSON array) - filter by team IDs.
meeting_typestr | NonedefaultNoneMeeting Type filter.
calendar_invitees_domainsstr | list | NonedefaultNoneFilter to meetings whose invitees belong to these email domains.
calendar_invitees_domains_typestr | NonedefaultNoneHow to apply the domain filter: all, only_internal, or one_or_more_external.
include_transcriptbool | NonedefaultNoneInclude Transcript in each result.
include_summarybool | NonedefaultNoneInclude Summary in each result.
include_action_itemsbool | NonedefaultNoneInclude Action Items in each result.
include_crm_matchesbool | NonedefaultNoneInclude CRM match data in each result.
{"status": "success", "meetings": list[dict], "count": int, "next_cursor": str | None}Returns one page per call. count is the size of that page rather than the number of meetings in the workspace, and next_cursor is only added to the result when Fathom sends one, so read it with .get() and stop paging when it is missing. Each meeting is narrowed to recording_id, title, url, share_url, created_at, scheduled_start_time, scheduled_end_time, recording_start_time, recording_end_time, meeting_type, transcript_language, recorded_by, calendar_invitees, transcript, summary, action_items and crm_matches, with null valued keys removed, so read them with .get(). title falls back to Fathom's meeting_title when the title field is absent. transcript, summary, action_items and crm_matches are absent unless the matching include flag is set, and summary is taken from Fathom's default_summary field. Fathom allows 60 calls a minute.
search_meetings()Search meetings by title keyword with optional date and recorder filters.
result = qbash.integrations.fathom.search_meetings(
search_term="…",
)
search_termstrrequiredKeyword to search in meeting titles.
cursorstr | NonedefaultNonePagination cursor from a previous response.
recorded_bystr | NonedefaultNoneRecorded By (email).
created_afterstr | NonedefaultNoneCreated After (ISO 8601).
created_beforestr | NonedefaultNoneCreated Before (ISO 8601).
teamsstr | NonedefaultNoneTeams (JSON array).
calendar_invitees_domainsstr | list | NonedefaultNoneFilter to meetings whose invitees belong to these email domains.
calendar_invitees_domains_typestr | NonedefaultNoneHow to apply the domain filter: all, only_internal, or one_or_more_external.
meeting_typestr | NonedefaultNoneMeeting Type filter.
{"status": "success", "meetings": list[dict], "count": int, "search_term": str, "next_cursor": str | None}The search runs here rather than at Fathom. One page of meetings is fetched and a meeting is kept only when search_term appears in its title, matched case insensitively, so count can be zero while next_cursor still points at more meetings. Follow next_cursor until it is missing before concluding there is no match, and read it with .get() because it is only added when Fathom sends one. next_cursor describes the underlying page rather than the filtered list. search_term echoes the term that was used. Each meeting is narrowed to recording_id, title, url, share_url, created_at, scheduled_start_time, scheduled_end_time, recording_start_time, recording_end_time, meeting_type, transcript_language, recorded_by, calendar_invitees, transcript, summary, action_items and crm_matches, with null valued keys removed, so read them with .get(). title falls back to Fathom's meeting_title when the title field is absent. The include flags for transcript, summary, action items and CRM matches are not exposed on this mode, so those four keys are always absent here. Use list_meetings when they are needed.
get_transcript()Get the full transcript for a specific recording.
result = qbash.integrations.fathom.get_transcript(
recording_id="…",
)
recording_idstrrequiredRecording ID.
{"status": "success", "transcript": list[dict], "recording_id": str}transcript is the list Fathom returns, taken either from the whole body or from a transcript key in it, and the entries are passed through untouched, so read entry fields such as speaker and text with .get(). The list can be empty for a recording with no transcript, so check its length before reading the first entry. recording_id echoes the value that was passed, which keeps the recording_ prefix that list_meetings returns even though that prefix is stripped before Fathom is called. Speaker diarisation and timings are whatever Fathom provides, and no summary or action items come back from this mode.
list_transcripts()Get one page of meeting transcripts.
result = qbash.integrations.fireflies.list_transcripts(
limit=10,
skip=0,
)
limitintdefault10Maximum number of transcripts to return.
skipintdefault0Number of transcripts to skip (pagination offset).
{"status": "success", "transcripts": list[dict], "count": int}One call to the transcripts query with the limit and skip that were passed, so this is a single page and never the whole account. "count" is the length of "transcripts" in this page, not the number of transcripts available, so it cannot be used to tell whether more remain. Page forward by raising "skip" by the same "limit" and stop when a page comes back shorter than the limit. Each record is rebuilt here from the fields this query asks for, id, title, date, duration, attendees, transcript_url, audio_url, video_url and summary, and any of those whose value is null is dropped from the record rather than set to None, so read every field with .get(). "sentences" is not requested by this mode so it is never present, and "organizer" is not requested by any mode. Use get_transcript for the sentences and the fuller summary.
get_transcript()Get full transcript details and AI summary.
result = qbash.integrations.fireflies.get_transcript(
transcript_id="…",
)
transcript_idstrrequiredTranscript ID.
{"status": "success", "transcript": dict}"transcript" is rebuilt here from the fields this query asks for, id, title, date, duration, attendees, transcript_url, audio_url, video_url, sentences and summary. Any of those whose value is null is dropped from the record rather than set to None, so read every field with .get() and do not assume a transcript that is still processing carries sentences or a summary. "organizer" is not requested, so it is never present. An id Fireflies does not know is an error rather than an empty transcript.
search_transcripts()Filter one page of transcripts by a word in the title.
result = qbash.integrations.fireflies.search_transcripts(
search_term="…",
)
search_termstrrequiredKeyword to find in transcripts.
limitintdefault100Maximum number of results.
skipintdefault0Pagination offset.
{"status": "success", "transcripts": list[dict], "count": int, "search_term": str}The Fireflies transcripts query takes no search argument, so this mode fetches one page of transcripts using the given limit and skip and then filters that page here. The match is a case insensitive substring of the transcript title only, so nothing in the body, the summary or the attendee list is searched. A term that appears only in transcripts outside that page is not found, and raising "limit" is the only way to widen the sweep. "count" is the number of records left after filtering, not the number fetched, and "search_term" echoes the term that was passed. The records are the same shape list_transcripts returns, with null fields dropped, so read them with .get().
get_analytics()Retrieve conversation analytics.
result = qbash.integrations.fireflies.get_analytics(
user_id="…",
start_date="…",
)
user_idstr | NonedefaultNoneUser ID to filter analytics by.
start_datestr | NonedefaultNoneStart Date (ISO 8601).
end_datestr | NonedefaultNoneEnd Date (ISO 8601).
{"status": "success", "analytics": dict}"analytics" is the vendor object for the fields this query selects, total_meetings, total_duration, avg_meeting_duration, total_transcripts, most_active_speakers, keywords_trending and action_items_count, forwarded with no reshaping. Nothing here defaults or checks them, so any of them can come back None and all of them should be read with .get().
add_bot_to_meeting()Add Fireflies bot to a live meeting.
result = qbash.integrations.fireflies.add_bot_to_meeting(
meeting_url="https://…",
)
meeting_urlstrrequiredMeeting URL (Google Meet, Zoom, Teams, etc.).
titlestr | NonedefaultNoneTitle to assign to the meeting.
attendee_emailstr | NonedefaultNoneAttendee Email to notify after the meeting.
{"status": "success", "result": dict | None}There are two success signals here and they mean different things. The outer "status" is "success" whenever the GraphQL call completed, while whether the bot was accepted is inside "result", which carries the vendor fields success, meeting_id and message. When Fireflies reports a field level error the mutation field comes back null and this code still takes the success branch, so "result" can be None with "status" still "success". Check that "result" is not None and that its "success" is true before treating the bot as joined.
update_meeting()Update meeting metadata.
result = qbash.integrations.fireflies.update_meeting(
transcript_id="…",
)
transcript_idstrrequiredTranscript ID.
titlestr | NonedefaultNoneNew title for the meeting.
channel_idstr | NonedefaultNoneChannel ID to move the meeting to.
privacystr | NonedefaultNonePrivacy setting (public / private).
{"status": "success", "result": dict | None}"result" carries the vendor fields success and transcript, where transcript holds the id and title as they stand after the update. A call that passes none of title, channel_id or privacy still runs the mutation and changes nothing. As with the other mutations here a null mutation field is still taken as success, so "result" can be None while "status" is "success", and the real outcome is its "success" field.
delete_transcript()Permanently delete a transcript.
result = qbash.integrations.fireflies.delete_transcript(
transcript_id="…",
)
transcript_idstrrequiredTranscript ID.
{"status": "success", "result": dict | None}Deletion is permanent and there is no confirmation step. "result" carries the vendor fields success and message. A null mutation field is still taken as success here, so check that "result" is not None and that its "success" is true rather than trusting the outer "status".
ask_fred()Ask AI questions about transcripts.
result = qbash.integrations.fireflies.ask_fred(
question="…",
)
questionstrrequiredQuestion to ask the Fireflies AI.
transcript_idslist | NonedefaultNoneTranscript IDs to scope the question to (list of strings). Only used when creating a thread.
thread_idstr | NonedefaultNoneThread ID to continue an existing conversation.
{"status": "success", "result": dict | None}Passing thread_id continues an existing AskFred thread and omitting it creates a new one. Both paths return the same "result" fields, thread_id, response and success, so keep the returned thread_id to carry the conversation on. transcript_ids only applies when a thread is being created and is ignored once thread_id is set. A null mutation field is still taken as success here, so "result" can be None while "status" is "success".
list_repos()List repositories accessible to the authenticated user.
result = qbash.integrations.github.list_repos(
page=0,
per_page=0,
)
pageint | NonedefaultNonePage number to read, starting at 1. When set, the call reads only that page and adds has_next. When omitted, the call reads up to the first 1000 repositories.
per_pageint | NonedefaultNoneRepositories per page when page is set, from 1 to 100. Defaults to 100.
{"status": "success", "repos": list[dict], "count": int} or {"status": "success", "repos": list[dict], "count": int, "has_next": bool}Without page, reads the GitHub list repositories endpoint and follows pagination internally at 100 repositories per page, stopping after 10 pages. An account with more than 1000 accessible repositories is truncated to the first 1000 with nothing in the result to signal it, so treat a count of 1000 as probably incomplete. With page set, makes one request for that page and adds has_next, which is true while GitHub reports a next page. Start at page 1 and keep incrementing page while has_next is true to reach every repository. The count key is then the size of that page, not the account total. per_page applies only with page and is ignored otherwise. Repositories come sorted by full name. Filtering by visibility, affiliation or repository type is not exposed.
list_pull_requests()List pull requests in a repository.
result = qbash.integrations.github.list_pull_requests(
owner="…",
repo="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
statestr | NonedefaultNonePull request state to filter by. One of open, closed or all. GitHub uses open when omitted.
sortstr | NonedefaultNoneField to sort by. One of created, updated, popularity or long-running. GitHub uses created when omitted.
directionstr | NonedefaultNoneSort order, either asc or desc. GitHub uses desc when sorting by created.
per_pageint | NonedefaultNoneNumber of pull requests per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "pull_requests": list[dict], "count": int, "has_next": bool}Returns one page per call. The count key is the size of the requested page, not the repository total. has_next is true while GitHub reports a next page, so start at page 1 and keep incrementing page while has_next is true to reach every pull request. Filtering by head or base branch is not exposed.
get_pull_request()Get details of a specific pull request.
result = qbash.integrations.github.get_pull_request(
owner="…",
repo="…",
pr_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
{"status": "success", "pull_request": dict}comment_on_pull_request()Post a comment on a pull request.
result = qbash.integrations.github.comment_on_pull_request(
owner="…",
repo="…",
pr_number=0,
body="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
bodystrrequiredComment text. GitHub renders it as Markdown.
{"status": "success", "comment": dict}Posts through the GitHub issue comments endpoint, so the comment lands on the pull request conversation timeline and cannot be attached to a diff line. The comment map is the created comment as GitHub returns it, including its id and html_url.
list_pr_comments()List comments on a pull request.
result = qbash.integrations.github.list_pr_comments(
owner="…",
repo="…",
pr_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
per_pageint | NonedefaultNoneNumber of comments per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "comments": list[dict], "count": int, "has_next": bool}Reads the GitHub issue comments endpoint, so it returns conversation comments only and never inline review comments on the diff. The count key is the size of the requested page, not the total number of comments. has_next is true while GitHub reports a next page, so keep incrementing page while has_next is true to reach every comment.
list_pr_files()List files changed in a pull request.
result = qbash.integrations.github.list_pr_files(
owner="…",
repo="…",
pr_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
per_pageint | NonedefaultNoneNumber of files per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "files": list[dict], "count": int}Each file carries filename, status, additions and deletions. The patch key is absent on binary files and on diffs GitHub considers too large, so read it with .get() rather than by index. GitHub returns at most 3000 files across all pages. The count key is the size of the requested page, not the total number of changed files, and no next page cursor is returned, so keep incrementing page until a call returns fewer items than per_page.
get_file_contents()Get the contents of a file in a repository.
result = qbash.integrations.github.get_file_contents(
owner="…",
repo="…",
path="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pathstrrequiredPath to the file or directory within the repository, for example lib/app/router.ex.
refstr | NonedefaultNoneBranch name, tag or commit SHA to read from. The repository default branch is used when omitted.
{"status": "success", "file": dict} or {"status": "success", "file": list[dict]}When path points at a file, file is a single object whose content key holds the base64 payload already decoded to raw bytes, and the encoding key still reads base64 even though the content has already been decoded. The bytes are not checked or converted, so a binary file such as an image arrives as invalid UTF-8 under that same key and will break string handling or JSON encoding. Check the path before treating content as text. When path points at a directory, the same file key holds a list of directory entries instead of a single object and no entry carries content, so check whether file is a list before reading it. A response GitHub does not send base64 encoded, such as a file over 1 MB or a submodule, passes through undecoded and its content key can be empty.
list_commits()List commits in a repository.
result = qbash.integrations.github.list_commits(
owner="…",
repo="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
shastr | NonedefaultNoneCommit SHA or branch name to start listing from. The repository default branch is used when omitted.
pathstr | NonedefaultNoneOnly return commits that touch this file or directory path.
authorstr | NonedefaultNoneGitHub username or email address to filter commits by.
sincestr | NonedefaultNoneISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only commits after this time are returned.
untilstr | NonedefaultNoneISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only commits before this time are returned.
per_pageint | NonedefaultNoneNumber of commits per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "commits": list[dict], "count": int}Commits in this listing carry the commit message and author but no file diffs. Call get_commit for the changed files. The count key is the size of the requested page, not the total number of commits, and no next page cursor is returned, so keep incrementing page until a call returns fewer items than per_page.
get_commit()Get details of a specific commit, including files changed and patch.
result = qbash.integrations.github.get_commit(
owner="…",
repo="…",
sha="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
shastrrequiredCommit SHA, branch name or tag to fetch.
{"status": "success", "commit": dict}The commit map carries a files list and a stats map of additions and deletions. This mode sends no paging parameters and exposes none, so the files list stops at GitHub's first page, which is 30 files by default and well short of the 300 file endpoint ceiling. A commit touching more files cannot be read in full through this mode. The patch on a file is absent for binary files and for diffs GitHub considers too large, so read it with .get() rather than by index.
compare_commits()Compare two commits, branches, or tags. Returns ahead/behind counts and changed files with patches.
result = qbash.integrations.github.compare_commits(
owner="…",
repo="…",
base="…",
head="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
basestrrequiredCommit SHA, branch name or tag to compare from. Branch names containing slashes are accepted.
headstrrequiredCommit SHA, branch name or tag to compare to. Branch names containing slashes are accepted.
{"status": "success", "comparison": dict}The comparison map carries status, ahead_by, behind_by, total_commits, a commits list and a files list. The patch on a file is absent for binary files and for diffs GitHub considers too large, so read it with .get() rather than by index. This mode sends no paging parameters and exposes none, so both the commits and files lists stop at GitHub's first page, which is 30 entries by default. That is well short of the 250 commit and 300 file ceilings the endpoint itself applies, and a larger comparison cannot be read in full through this mode.
search_code()Search for code across repositories. Returns matching file paths and text fragments.
result = qbash.integrations.github.search_code(
query="search terms",
)
querystrrequiredCode search query. Supports qualifiers such as repo:owner/name, path:, language: and filename:.
sortstr | NonedefaultNoneField to sort by. The only accepted value is indexed, which orders by when the file was last indexed. Omit for best match ordering.
orderstr | NonedefaultNoneSort order, either asc or desc. Only applies when sort is set.
per_pageint | NonedefaultNoneNumber of results per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "result": {"total_count": int, "incomplete_results": bool, "items": list[dict]}}Unlike the other list modes this one does not unwrap the response, so result is the GitHub search envelope and the matches are at result["items"]. total_count is how many matches GitHub found rather than how many came back, and items holds only the requested page, with no next page cursor returned. incomplete_results is true when the search timed out and the page is partial. The text match media type is requested, so every item carries a text_matches list of matching fragments. GitHub code search needs at least one search term, only covers the default branch, and returns at most 1000 results across all pages however large total_count is.
get_repo()Get repository metadata including default branch, description, visibility, and language.
result = qbash.integrations.github.get_repo(
owner="…",
repo="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
{"status": "success", "repository": dict}get_issue()Get details of a specific issue, including title, body, labels, assignees, and state.
result = qbash.integrations.github.get_issue(
owner="…",
repo="…",
issue_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
issue_numberintrequiredIssue number as it appears in the issue URL.
{"status": "success", "issue": dict}GitHub models pull requests as issues, so passing a pull request number succeeds and the returned issue carries an extra pull_request key. Find issue numbers with list_issues or search_issues.
list_issues()List issues in a repository.
result = qbash.integrations.github.list_issues(
owner="…",
repo="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
statestr | NonedefaultNoneIssue state to filter by. One of open, closed or all. GitHub uses open when omitted.
labelsstr | list[str] | NonedefaultNoneLabel names as a comma-separated string such as bug,ui, or as a list of strings. Only issues carrying every listed label are returned.
assigneestr | NonedefaultNoneLogin of the assigned user. Pass none for issues with no assignee or * for issues with any assignee.
creatorstr | NonedefaultNoneLogin of the user who opened the issue.
mentionedstr | NonedefaultNoneLogin of a user mentioned in the issue.
milestoneint | str | NonedefaultNoneMilestone number, as an integer or a string. Pass none for issues with no milestone or * for issues with any milestone.
sincestr | NonedefaultNoneISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only issues updated at or after this time are returned.
sortstr | NonedefaultNoneField to sort by. One of created, updated or comments. GitHub uses created when omitted.
directionstr | NonedefaultNoneSort order, either asc or desc. GitHub uses desc when omitted.
include_pull_requestsbooldefaultFalseKeep pull requests in the result. Each pull request carries a pull_request key.
per_pageint | NonedefaultNoneNumber of items read from GitHub per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "issues": list[dict], "count": int, "has_next": bool}Reads the GitHub repository issues endpoint one page per call. That endpoint returns pull requests as well, and this mode drops them unless include_pull_requests is true. The count key is the number of issues returned after pull requests are dropped, so a page can be shorter than per_page while has_next is still true. Start at page 1 and keep incrementing page while has_next is true to reach every issue. Each issue carries number, title, state, labels and assignees. Pass the number to get_issue, update_issue or list_issue_comments.
search_issues()Search issues and pull requests with GitHub issue search syntax.
result = qbash.integrations.github.search_issues(
query="search terms",
)
querystrrequiredSearch query in GitHub issue search syntax, for example repo:octocat/Hello-World is:issue state:open label:bug. Other qualifiers include is:pr, author:, assignee: and mentions:.
sortstr | NonedefaultNoneField to sort by, for example created, updated, comments, reactions or interactions. Omit for best match ordering.
orderstr | NonedefaultNoneSort order, either asc or desc. Only applies when sort is set. GitHub uses desc when omitted.
per_pageint | NonedefaultNoneNumber of results per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "issues": list[dict], "total_count": int, "incomplete_results": bool, "count": int, "has_next": bool}Reads the GitHub issue search endpoint one page per call. Results include pull requests unless the query has is:issue, and each pull request carries a pull_request key. Results can span repositories, so read repository_url on each item to tell which repository it belongs to. Follow-up calls such as get_issue or update_issue take owner and repo from repository_url and the issue number from number. total_count is how many matches GitHub found, while the issues list and count cover only the requested page. has_next is true while GitHub reports a next page, so keep incrementing page while has_next is true. GitHub returns at most 1000 results across all pages however large total_count is. incomplete_results is true when the search timed out and the page is partial.
create_issue()Create an issue in a repository.
result = qbash.integrations.github.create_issue(
owner="…",
repo="…",
title="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
titlestrrequiredIssue title.
bodystr | NonedefaultNoneIssue description. GitHub renders it as Markdown.
labelslist[str] | NonedefaultNoneLabel names to apply. list_labels returns the names a repository has.
assigneeslist[str] | NonedefaultNoneLogins of the users to assign.
milestoneint | NonedefaultNoneMilestone number to attach the issue to.
{"status": "success", "issue": dict}Sends only the fields that are set. GitHub applies labels, assignees and milestone only when the token has push access to the repository and leaves them off the new issue otherwise. The issue map is the created issue as GitHub returns it, including its number and html_url.
update_issue()Change the title, body, state, labels, assignees or milestone of an issue.
result = qbash.integrations.github.update_issue(
owner="…",
repo="…",
issue_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
issue_numberintrequiredIssue number as it appears in the issue URL.
titlestr | NonedefaultNoneNew issue title.
bodystr | NonedefaultNoneNew issue description. GitHub renders it as Markdown.
statestr | NonedefaultNoneEither open or closed.
state_reasonstr | NonedefaultNoneWhy the state changed. One of completed, not_planned, duplicate or reopened.
labelslist[str] | NonedefaultNoneLabel names that replace every label on the issue. Pass an empty list to remove them all.
assigneeslist[str] | NonedefaultNoneLogins that replace every assignee on the issue. Pass an empty list to remove them all.
milestoneint | NonedefaultNoneMilestone number to attach the issue to.
{"status": "success", "issue": dict}Sends only the fields that are set, and at least one is required besides owner, repo and issue_number. labels and assignees replace the whole set on the issue, so pass every label or login the issue should keep. An empty list removes them all. Close an issue with state closed and an optional state_reason. A milestone can be set or changed, but the milestone cannot be cleared through this mode. GitHub accepts a pull request number here too and applies the same changes to that pull request. The issue map is the updated issue as GitHub returns it.
list_issue_comments()List conversation comments on an issue or pull request.
result = qbash.integrations.github.list_issue_comments(
owner="…",
repo="…",
issue_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
issue_numberintrequiredIssue or pull request number as it appears in the URL.
sincestr | NonedefaultNoneISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only comments updated at or after this time are returned.
per_pageint | NonedefaultNoneNumber of comments per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "comments": list[dict], "count": int, "has_next": bool}Reads the GitHub issue comments endpoint, oldest first. A pull request number works too and returns its conversation comments, which never include inline review comments on the diff. The count key is the size of the requested page. has_next is true while GitHub reports a next page, so keep incrementing page while has_next is true to reach every comment.
comment_on_issue()Post a comment on an issue or pull request.
result = qbash.integrations.github.comment_on_issue(
owner="…",
repo="…",
issue_number=0,
body="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
issue_numberintrequiredIssue or pull request number as it appears in the URL.
bodystrrequiredComment text. GitHub renders it as Markdown.
{"status": "success", "comment": dict}Posts through the GitHub issue comments endpoint. A pull request number works too, and the comment lands on the pull request conversation timeline. The comment map is the created comment as GitHub returns it, including its id and html_url.
list_labels()List the labels defined in a repository.
result = qbash.integrations.github.list_labels(
owner="…",
repo="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
per_pageint | NonedefaultNoneNumber of labels per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "labels": list[dict], "count": int, "has_next": bool}Each label carries name, color and description. The label names are what list_issues, create_issue and update_issue take. The count key is the size of the requested page. has_next is true while GitHub reports a next page, so keep incrementing page while has_next is true to reach every label.
list_branches()List branches in a repository.
result = qbash.integrations.github.list_branches(
owner="…",
repo="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
protectedbool | NonedefaultNoneTrue returns only protected branches and False only unprotected ones. Every branch is returned when omitted.
per_pageint | NonedefaultNoneNumber of branches per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "branches": list[dict], "count": int, "has_next": bool}Each branch carries name, protected and a commit map holding the head sha. The count key is the size of the requested page. has_next is true while GitHub reports a next page, so keep incrementing page while has_next is true to reach every branch.
list_pr_reviews()List the reviews submitted on a pull request.
result = qbash.integrations.github.list_pr_reviews(
owner="…",
repo="…",
pr_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
per_pageint | NonedefaultNoneNumber of reviews per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "reviews": list[dict], "count": int, "has_next": bool}Reviews come oldest first. Each review carries id, user, body, commit_id and state, which is one of APPROVED, CHANGES_REQUESTED, COMMENTED, DISMISSED or PENDING. A PENDING review is a draft and only its author sees it. The count key is the size of the requested page. has_next is true while GitHub reports a next page, so keep incrementing page while has_next is true to reach every review.
list_pr_review_comments()List the inline review comments on a pull request's diff.
result = qbash.integrations.github.list_pr_review_comments(
owner="…",
repo="…",
pr_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
sortstr | NonedefaultNoneField to sort by, either created or updated. GitHub uses created when omitted.
directionstr | NonedefaultNoneSort order, either asc or desc. Only applies when sort is set.
sincestr | NonedefaultNoneISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only comments updated at or after this time are returned.
per_pageint | NonedefaultNoneNumber of comments per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "comments": list[dict], "count": int, "has_next": bool}Reads the comments attached to lines of the diff. Conversation comments on the pull request come from list_pr_comments instead. Each comment carries id, path, line, side, in_reply_to_id and body, and in_reply_to_id is set on replies within a thread. line is null on an outdated comment whose lines changed in a later push, and original_line keeps the position it was made at. Answer a comment with reply_to_review_comment and its id. The count key is the size of the requested page. has_next is true while GitHub reports a next page, so keep incrementing page while has_next is true to reach every comment.
list_check_runs()List the check runs reported for a commit, branch or tag.
result = qbash.integrations.github.list_check_runs(
owner="…",
repo="…",
ref="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
refstrrequiredCommit SHA, branch name or tag. Branch names containing slashes are accepted.
check_namestr | NonedefaultNoneOnly return check runs with this name.
statusstr | NonedefaultNoneOnly return check runs in this status. One of queued, in_progress or completed.
filterstr | NonedefaultNonelatest returns the most recent run of each check and all returns every run. GitHub uses latest when omitted.
per_pageint | NonedefaultNoneNumber of check runs per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the result set, starting at 1.
{"status": "success", "check_runs": list[dict], "total_count": int, "count": int, "has_next": bool}GitHub Actions and other GitHub Apps report results here. Each check run carries id, name, status, conclusion and html_url. conclusion stays null until status is completed and then reads a result such as success, failure, cancelled, skipped or timed_out. For a branch or tag, the runs are those of the commit it points at. GitHub covers only the runs of the 1000 most recent check suites on the commit, so with filter all on a commit with more suites than that, older runs are missing and nothing in the result says so. total_count is how many check runs match, while check_runs and count cover only the requested page. has_next is true while GitHub reports a next page, so keep incrementing page while has_next is true.
get_commit_status()Get the combined commit status for a commit, branch or tag.
result = qbash.integrations.github.get_commit_status(
owner="…",
repo="…",
ref="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
refstrrequiredCommit SHA, branch name or tag. Branch names containing slashes are accepted.
per_pageint | NonedefaultNoneNumber of statuses per page, maximum 100. GitHub uses 30 when omitted.
pageint | NonedefaultNonePage number of the statuses list, starting at 1.
{"status": "success", "combined_status": dict, "has_next": bool}Reads the legacy commit statuses posted through the statuses API, which some CI providers and deploy tools still use. combined_status carries state, total_count and a statuses list. state and total_count cover every status context, while the statuses list holds only the requested page, 30 per page unless per_page is set. has_next is true while GitHub reports a next page, so keep incrementing page while has_next is true to read every status. state is pending when no status has been posted at all, so check total_count before reading pending as a running build. GitHub Actions report through check runs and never appear here. Read them with list_check_runs.
create_pull_request()Open a pull request.
result = qbash.integrations.github.create_pull_request(
owner="…",
repo="…",
title="…",
head="…",
base="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
titlestrrequiredPull request title.
headstrrequiredBranch holding the changes. For a branch in a fork, use owner:branch.
head_repostr | NonedefaultNoneName of the fork holding head, without the owner. GitHub requires it when the fork and the base repository belong to the same organization.
basestrrequiredBranch the changes should be merged into, for example main.
bodystr | NonedefaultNonePull request description. GitHub renders it as Markdown.
draftbool | NonedefaultNoneTrue opens the pull request as a draft.
maintainer_can_modifybool | NonedefaultNoneTrue lets maintainers of the base repository push to the head branch of a fork.
{"status": "success", "pull_request": dict}Sends only the fields that are set. The head branch must already be pushed and must differ from base. GitHub answers 422 when a pull request between the same branches is already open or when there are no commits between head and base. The pull_request map is the new pull request as GitHub returns it, including its number and html_url.
update_pull_request()Change the title, body, state or base branch of a pull request.
result = qbash.integrations.github.update_pull_request(
owner="…",
repo="…",
pr_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
titlestr | NonedefaultNoneNew pull request title.
bodystr | NonedefaultNoneNew pull request description. GitHub renders it as Markdown.
statestr | NonedefaultNoneEither open or closed.
basestr | NonedefaultNoneBranch to merge into instead of the current base.
{"status": "success", "pull_request": dict}Sends only the fields that are set, and at least one is required besides owner, repo and pr_number. Setting state to closed closes the pull request without merging it. Merge with merge_pull_request. Labels and assignees on a pull request change through update_issue with the same number.
merge_pull_request()Merge a pull request.
result = qbash.integrations.github.merge_pull_request(
owner="…",
repo="…",
pr_number=0,
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
merge_methodstr | NonedefaultNoneOne of merge, squash or rebase. GitHub uses merge when omitted.
commit_titlestr | NonedefaultNoneTitle of the merge or squash commit. GitHub writes one when omitted.
commit_messagestr | NonedefaultNoneExtra detail appended to the merge or squash commit message.
shastr | NonedefaultNoneHead commit SHA the pull request must still be at for the merge to go ahead.
{"status": "success", "merge": dict}The merge map carries sha, merged and message as GitHub returns them. GitHub answers 405 when the pull request cannot be merged, for example while required checks or reviews are missing or the branches conflict. It answers 409 when sha is set and no longer matches the head of the pull request, so the merge never includes commits pushed after the caller checked it. The chosen merge_method must be enabled in the repository settings. A base branch that requires a merge queue cannot be merged through this mode.
create_pr_review()Submit a review on a pull request, with optional inline comments.
result = qbash.integrations.github.create_pr_review(
owner="…",
repo="…",
pr_number=0,
event="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
eventstrrequiredOne of APPROVE, REQUEST_CHANGES or COMMENT.
bodystr | NonedefaultNoneReview summary. Required for REQUEST_CHANGES and COMMENT. GitHub renders it as Markdown.
commentslist[dict] | NonedefaultNoneInline comments to attach to lines of the diff.
pathstrrequiredFile path relative to the repository root.
bodystrrequiredComment text.
lineintrequiredLine number in the file the comment applies to. For a range, the last line.
sidestr | NonedefaultNoneRIGHT for added or unchanged lines, LEFT for removed ones. RIGHT is sent when omitted.
start_lineint | NonedefaultNoneFirst line of a multi-line comment.
start_sidestr | NonedefaultNoneSide of start_line, either LEFT or RIGHT. Defaults to the comment's side when start_line is set.
commit_idstr | NonedefaultNoneSHA of the commit the review applies to. GitHub uses the latest commit when omitted.
{"status": "success", "review": dict}Submits the review in one call with event APPROVE, REQUEST_CHANGES or COMMENT. Draft reviews held for later submission are not supported. REQUEST_CHANGES and COMMENT need a body, even when inline comments are given. event is matched case-insensitively. Each inline comment names a path and a line in the diff, on the RIGHT side for added or unchanged lines and the LEFT side for removed ones. A line outside the diff makes GitHub reject the whole review with 422. GitHub does not let the author of a pull request approve it or request changes on it.
reply_to_review_comment()Reply to an inline review comment on a pull request.
result = qbash.integrations.github.reply_to_review_comment(
owner="…",
repo="…",
pr_number=0,
comment_id=0,
body="…",
)
ownerstrrequiredRepository owner, either a user or an organisation login.
repostrrequiredRepository name without the owner prefix.
pr_numberintrequiredPull request number as it appears in the pull request URL.
comment_idintrequiredId of the inline review comment to answer.
bodystrrequiredReply text. GitHub renders it as Markdown.
{"status": "success", "comment": dict}The reply joins the thread of the comment it answers. GitHub does not support replies to replies, so pass the id of the first comment in the thread. list_pr_review_comments returns the ids. The comment map is the created reply as GitHub returns it.
get_email()Fetch a Gmail message by ID.
result = qbash.integrations.gmail.get_email(
email_id={"key": "value"},
)
email_idstr | dict | listrequiredGmail message ID. Also accepts an email object with an id field, a comma-separated string of IDs, or a list of IDs or email objects.
formatstrdefault"full"Gmail message format. One of full, metadata or minimal. The value is passed to Gmail unchanged and the result is always parsed, so raw is not rejected but comes back with no headers and no body.
{"success": True, "email": dict} for one ID, or {"success": True, "emails": list[dict], "count": int, "failed_count": int} when email_id is a list or a comma-separated stringemail and each entry of emails hold id, thread_id, label_ids, labels, snippet, from, sender, to, recipient, cc, bcc, subject, date, parsed_date, message_id, body, attachments, has_attachments, is_unread, internal_date and size_estimate. Every one of those keys is always present, but any value taken from the message itself is None when the message does not carry it, so read those with .get rather than indexing. sender, recipient and labels duplicate from, to and label_ids for older scripts. message_id is the RFC 5322 Message-ID header, not the Gmail message ID, which is id. body is the text/plain part falling back to text/html, and is an empty string when neither is present. Each attachments entry has filename, mime_type, attachment_id, size and message_id, so it can be passed straight to download-attachment. format only changes what Gmail sends, and the same parser runs either way, so metadata yields an empty body and an empty attachments list and minimal yields those plus None for every header field. In the multi-ID shape a message that fails to fetch is counted in failed_count instead of failing the call, unless every message fails, in which case the whole call returns an error.
get_profile()Get the authenticated Gmail user's profile.
result = qbash.integrations.gmail.get_profile()
{"success": True, "profile": dict}profile is the Gmail users.getProfile payload exactly as Gmail returned it, so its keys are camelCase and nothing is renamed, added or defaulted. Gmail normally sends emailAddress, messagesTotal, threadsTotal and historyId, but since the payload is not projected, read those keys with .get rather than assuming any of them is present. historyId is the value to store before calling list-user-history or poll-messages.
search_messages()Search Gmail messages using a query string.
result = qbash.integrations.gmail.search_messages(
query="search terms",
max_results=10,
)
querystr | NonedefaultNoneGmail search query, for example from:user@example.com is:unread. Omit to list messages unfiltered.
max_resultsintdefault10Maximum number of messages to return in one page.
label_idsstr | list | NonedefaultNoneLabel IDs to restrict the search to. Accepts a list or a comma-separated string.
include_spam_trashbooldefaultFalseInclude messages in Spam and Trash.
page_tokenstr | NonedefaultNonePage token from a previous response, returned there as nextPageToken.
fetch_fullbooldefaultFalseFetch full message bodies instead of ID and thread ID only.
{"success": True, "messages": list[dict], "count": int, "nextPageToken": str | None}Each messages entry holds only id and threadId in Gmail camelCase, unless fetch_full is True, in which case entries are full parsed emails with the same keys get-email returns under email. count is the number of entries in messages rather than the number of matches. Keep calling with page_token until nextPageToken is None. fetch_full re-fetches each message one API call at a time and silently drops any message whose re-fetch fails, so under fetch_full both messages and count can be smaller than the page Gmail returned. fetch_full entries are also not post-processed the way get-email is, so their attachments entries carry no message_id and download-attachment rejects them until you add one.
download_attachment()Download a single Gmail attachment.
result = qbash.integrations.gmail.download_attachment(
attachment={"key": "value"},
)
attachmentdictrequiredAttachment object from a get-email result. It must have message_id or email_id, attachment_id, filename and mime_type.
fileThe result is a qbash file reference rather than the attachment bytes. The attachment is stored as a qbash file and the reference is {"type": "aisle_input_file", "aisle_input_file": dict}, or {"type": "aisle_input_image", "aisle_input_image": dict} for an image. The inner dict holds file_upload_id, filename, content_type and byte_size. Pass the reference to another integration or open it with qbash.files. If the file cannot be stored the call returns an error.
download_all_attachments()Download all attachments from a Gmail message.
result = qbash.integrations.gmail.download_all_attachments(
email={"key": "value"},
)
emaildictrequiredEmail object from a get-email result. It must have an id field and an attachments list.
list[file], or [] when the email has no attachmentsEach entry is the same file reference download-attachment returns, in the order the attachments appear on the email. Downloads run in parallel and the mode is all or nothing, so if any attachment fails the whole call returns an error naming how many failed rather than a partial list. When every attachment already carries filename, mime_type and attachment_id the downloads go straight out. When any one of them does not, the email is re-fetched and the attachments you passed are matched to the fresh ones by filename and then by size, and an attachment that matches nothing falls back to the first attachment on the email, so pass the attachments list exactly as get-email returned it.
modify_message()Add or remove labels on a Gmail message.
result = qbash.integrations.gmail.modify_message(
message_id="…",
)
message_idstrrequiredGmail message ID to modify.
add_label_idsstr | list | NonedefaultNoneLabel IDs or names to add. Accepts a list or a comma-separated string.
remove_label_idsstr | list | NonedefaultNoneLabel IDs or names to remove. Accepts a list or a comma-separated string.
{"success": True, "message": dict}At least one of add_label_ids or remove_label_ids must be given. message holds id, thread_id and labels, where labels are the message labels after the change. id falls back to the message_id you passed when Gmail echoes none, thread_id is taken straight from Gmail and is None when Gmail omits it, and labels is an empty list rather than None when the message has none. Label names are accepted as well as label IDs, and a name that does not exist yet is created as a new Gmail label, but when that creation or the label lookup fails the name is sent to Gmail unresolved instead.
add_labels()Add labels to a Gmail message.
result = qbash.integrations.gmail.add_labels(
message_id="…",
add_label_ids=[...],
)
message_idstrrequiredGmail message ID to label.
add_label_idsstr | listrequiredLabel IDs or names to add. Accepts a list or a comma-separated string.
{"success": True, "message": dict}message holds id, thread_id and labels, where labels are the message labels after the change. thread_id is taken straight from Gmail and is None when Gmail omits it. Label names are accepted as well as label IDs, and a name that does not exist yet is created as a new Gmail label, but when that creation or the label lookup fails the name is sent to Gmail unresolved instead.
remove_labels()Remove labels from a Gmail message.
result = qbash.integrations.gmail.remove_labels(
message_id="…",
remove_label_ids=[...],
)
message_idstrrequiredGmail message ID to unlabel.
remove_label_idsstr | listrequiredLabel IDs or names to remove. Accepts a list or a comma-separated string.
{"success": True, "message": dict}message holds id, thread_id and labels, where labels are the message labels after the change. thread_id is taken straight from Gmail and is None when Gmail omits it. Label names are accepted as well as label IDs, but a name that does not exist yet is created before it is removed, so pass label IDs here where you can.
mark_as_read()Mark a Gmail message as read.
result = qbash.integrations.gmail.mark_as_read(
message_id="…",
)
message_idstrrequiredGmail message ID to mark as read.
{"success": True, "message": dict}Removes the UNREAD label. message holds id, thread_id and labels, where labels are the message labels after the change. thread_id is taken straight from Gmail and is None when Gmail omits it.
mark_as_unread()Mark a Gmail message as unread.
result = qbash.integrations.gmail.mark_as_unread(
message_id="…",
)
message_idstrrequiredGmail message ID to mark as unread.
{"success": True, "message": dict}Adds the UNREAD label. message holds id, thread_id and labels, where labels are the message labels after the change. thread_id is taken straight from Gmail and is None when Gmail omits it.
archive()Archive a Gmail message (remove from Inbox).
result = qbash.integrations.gmail.archive(
message_id="…",
)
message_idstrrequiredGmail message ID to archive.
{"success": True, "message": dict}Removes the INBOX label. message holds id, thread_id and labels, where labels are the message labels after the change. thread_id is taken straight from Gmail and is None when Gmail omits it.
star()Star a Gmail message.
result = qbash.integrations.gmail.star(
message_id="…",
)
message_idstrrequiredGmail message ID to star.
{"success": True, "message": dict}Adds the STARRED label. message holds id, thread_id and labels, where labels are the message labels after the change. thread_id is taken straight from Gmail and is None when Gmail omits it.
unstar()Remove the star from a Gmail message.
result = qbash.integrations.gmail.unstar(
message_id="…",
)
message_idstrrequiredGmail message ID to unstar.
{"success": True, "message": dict}Removes the STARRED label. message holds id, thread_id and labels, where labels are the message labels after the change. thread_id is taken straight from Gmail and is None when Gmail omits it.
trash_message()Move a Gmail message to trash.
result = qbash.integrations.gmail.trash_message(
message_id="…",
)
message_idstrrequiredGmail message ID to trash.
{"success": True, "message": dict}The message is moved to Trash rather than deleted. message holds id, thread_id and labels, where labels are the message labels after the change. thread_id is taken straight from Gmail and is None when Gmail omits it. The labels it comes back with include TRASH.
setup_watch()Set up Gmail push notifications via Google Cloud Pub/Sub.
result = qbash.integrations.gmail.setup_watch(
topic_name="…",
)
topic_namestrrequiredGoogle Cloud Pub/Sub topic to publish notifications to, in the form projects/PROJECT_ID/topics/TOPIC_NAME.
label_idsstr | list | NonedefaultNoneLabel IDs the watch is limited to. Accepts a list or a comma-separated string. Watches INBOX when omitted.
label_filter_behaviorstrdefault"include"One of include or exclude. include watches only the listed labels, exclude watches everything except them.
{"history_id": str | None, "expiration": str | None}There is no success key on this result. Both values are read straight off the Gmail users.watch response with no default, so either is None when Gmail does not send it. expiration is a Unix timestamp in milliseconds as a string. A Gmail watch lapses after 7 days, so call this again before then. Store history_id and pass it to poll-messages or list-user-history.
list_user_history()List changes to the user's mailbox since a given history ID.
result = qbash.integrations.gmail.list_user_history(
stored_history_id="…",
)
stored_history_idstrrequiredStart history ID from a previous response or watch notification.
label_idstr | NonedefaultNoneLabel ID to restrict the history to, for example INBOX.
{"history": list[dict] | None, "historyId": str, "nextPageToken": str | None}This is the Gmail users.history.list payload verbatim, so the keys are camelCase, there is no success wrapper and nothing is renamed or defaulted. history is absent entirely when nothing changed. Each history record is a Gmail History resource carrying at least id and messagesAdded, since only messageAdded changes are requested, and its other keys vary, so read a record with .get rather than indexing. Only the first page is fetched and this mode takes no page token, so nextPageToken is reported but cannot be followed from here. Use poll-messages when you want every page and the messages already parsed.
poll_messages()Poll for new Gmail messages since a given history ID.
result = qbash.integrations.gmail.poll_messages(
start_history_id="…",
)
start_history_idstrrequiredHistory ID to poll from, received in a watch notification payload.
label_idstr | NonedefaultNoneLabel ID to restrict polling to, for example INBOX.
max_messagesintdefault500Safety ceiling on messages fetched in one poll.
max_api_callsintdefault10Safety ceiling on Gmail history API calls in one poll.
{"completed": True, "message_count": int, "api_calls_made": int, "new_history_id": str, "messages": list[dict], "skipped_message_count": int | None, "skipped_message_ids": list[str] | None} or {"completed": False, "message_count": int, "api_calls_made": int, "safety_limit_reason": str, "error": str} when a safety limit is hitmessages entries are full parsed emails with the same keys get-email returns under email, except that they are not post-processed the way get-email is, so their attachments entries carry no message_id and download-attachment rejects them until you add one. message_count counts the entries in messages and excludes any that were skipped. skipped_message_count and skipped_message_ids appear only when a message could not be fetched, which happens when it was deleted after its history record was written. A safety limit result is still an ok result and carries no new_history_id, so do not advance your stored history ID in that case. Nothing in the mailbox is changed, but one poll can make up to max_api_calls history calls plus one fetch per message.
check_availability()Check if a domain name is available to register.
result = qbash.integrations.godaddy.check_availability(
domain="…",
)
domainstrrequiredDomain name to check, e.g. "example.com".
{"status": "success", "domain": str | None, "available": bool | None, "definitive": bool | None, "price": float | None, "currency": str | None, "period": int | None}The result is flat. Client.parse_domain_available/1 builds the six domain keys and the integration merges "status" in beside them, so there is no nested domain object to index. All six keys are always present and every one of them is read straight off GoDaddy's body with no fallback, so any key the body leaves out or sends as null arrives as None. The only check made here is that the body has an "available" key at all, which says nothing about its value or about "domain", so read all six with .get(). A name that is already registered is the normal case for price, currency and period coming back None, because there is nothing to price. "price" is converted out of GoDaddy's micro units into a decimal currency amount, so 11990000 comes back as 11.99, and "currency" says which currency that is. The check is sent with checkType FAST, which is why "definitive" can be false. A response GoDaddy sends without an available field, and a domain it rejects or cannot find, both reach the script as an error rather than as a result with a different shape.
bulk_check_availability()Check availability of multiple domain names at once (up to 500).
result = qbash.integrations.godaddy.bulk_check_availability(
domains=[...],
)
domainslistrequiredList of domain names to check, max 500.
{"status": "success", "domains": list[dict], "count": int}Each entry in "domains" carries the same six keys as check_availability (domain, available, definitive, price, currency and period) under the same rules, including prices converted out of micro units and None for anything GoDaddy omitted. "count" is the length of the list that came back, not the number of domains that were asked about. Only the domains key of GoDaddy's response is read and the rest of the body is discarded, so a name GoDaddy could not check is absent from the list with no record of why. Compare the returned domain values against what was sent rather than trusting count. A response carrying no domains key at all is an error, so a batch where nothing could be checked does not arrive as an empty list. The 500 name limit is GoDaddy's and is not enforced here, so a longer list is rejected by the API rather than split.
get_suggestions()Get domain name suggestions based on a keyword or phrase.
result = qbash.integrations.godaddy.get_suggestions(
query="search terms",
)
querystrrequiredKeyword or phrase to generate domain suggestions for.
limitint | Nonedefault20Number of suggestions to return (max 100, default 20).
tldslist | NonedefaultNoneFilter to specific TLDs, e.g. ["com", "io"].
{"status": "success", "suggestions": list[dict], "count": int, "query": str}Every entry in "suggestions" has exactly one key, "domain". Client.get_suggestions/4 projects each of GoDaddy's suggestion objects down to that field and drops everything else, so availability, price and any ranking score are not reachable from this mode. Feed the names into check_availability or bulk_check_availability for those. The field is read with no fallback, so an entry GoDaddy sends without a domain gives None. "query" echoes the phrase that was passed and "count" is the length of "suggestions", which the limit param caps at 20 by default. There is no offset and no cursor, so one batch is all this mode can reach.
list_tlds()List all TLDs supported and enabled for sale. Each entry is projected down to name and type here, so no other field reaches the caller. Use check_availability for prices.
result = qbash.integrations.godaddy.list_tlds()
{"status": "success", "tlds": list[dict], "count": int}Every entry in "tlds" has exactly two keys, "name" and "type", projected from GoDaddy's TLD summary. Both are read with no fallback, so either is None when GoDaddy's entry omits it, and there is no pricing field on any entry. Use check_availability for a price. The mode sends one request with no paging params and nothing here loops, so "count" is the size of that single response.
transcript()Fetch call transcripts from Gong.
result = qbash.integrations.gong.transcript(
dateFrom="…",
dateTo="…",
)
dateFromstr | NonedefaultNoneISO 8601 start datetime to filter calls (e.g. 2024-01-01T00:00:00Z). A value that does not parse is passed to Gong as given.
dateTostr | NonedefaultNoneISO 8601 end datetime to filter calls.
workspaceIdstr | NonedefaultNoneGong workspace ID to filter calls by workspace.
callIdslist | NonedefaultNoneSpecific Gong call IDs to retrieve transcripts for. Blank entries are dropped, and if that leaves nothing the filter is omitted and every call in the date range is fetched.
limitint | str | Nonedefault100Maximum transcripts to return across all pages. A string of digits is parsed, and anything else falls back to 100.
list[dict] of Gong callTranscript records, whose per record keys are Gong's ownThe result is a bare list of Gong callTranscript records, not a dict, so index it by position. Nothing here reads or renames the records, so their keys are Gong's and should be read with .get() against Gong's own transcript reference. A speaker is identified only by a speakerId with no name attached, and nothing here resolves it, so pair this with get_extensive on the same call and match against its "parties" rather than assuming the id is a Gong user id, which an external attendee may not have. Pages are followed internally until "limit" records are collected or Gong runs out, and no cursor is returned, so raising limit is the only way to reach more records and there is no way to resume where a previous call stopped. When no call matches the filters the result is an empty list rather than an error. Passing callIds whose entries are all blank drops the filter entirely and fetches every call in the date range, so check the list is non-empty before relying on it.
get_extensive()Fetch detailed call metadata from Gong for a single call: parties (with name, email and affiliation) and CRM context (Account name, website and so on). Pair with `transcript` to label speakerIds.
result = qbash.integrations.gong.get_extensive(
call_id="…",
)
call_idstrrequiredGong call ID to retrieve detailed data for. A blank value is an error rather than a request for every call.
dict holding Gong's call object, or None when Gong has no match for call_idThe result is one call object taken from Gong's own response, or None when Gong reports no call for the id, so test for None before indexing. The body below the top level is Gong's and is passed through untouched, so read it with .get(). "parties" is asked for explicitly and is what resolves the speakerId values in the transcript mode to names, emails and affiliations. "context" is asked for as Extended so CRM field values such as Account name and website come back inline rather than as bare links to the CRM. Asking is not the same as receiving: nothing here inserts either key, so a call with no CRM association, or one where Gong omits a field, simply lacks it. Read both with .get(). Only the first call in Gong's response is returned, which is all a single call_id can match.
list_calls()List calls from Gong with optional date range and workspace filtering.
result = qbash.integrations.gong.list_calls(
fromDateTime="…",
toDateTime="…",
)
fromDateTimestr | NonedefaultNoneISO 8601 start datetime to filter calls (e.g. 2024-01-01T00:00:00Z). A value that does not parse is passed to Gong as given.
toDateTimestr | NonedefaultNoneISO 8601 end datetime to filter calls.
workspaceIdstr | NonedefaultNoneGong workspace ID to filter calls by workspace.
cursorstr | NonedefaultNoneCursor to start the walk from, taken from a Gong response obtained elsewhere. No continuation cursor is returned by this mode.
limitint | str | Nonedefault100Maximum calls to return across all pages, requested 100 at a time. A string of digits is parsed, and anything else falls back to 100.
list[dict], each a Gong call summary whose keys are Gong's ownThe result is a bare list of Gong call summaries, not a dict, so index it by position rather than looking for a "calls" key. The record keys are Gong's and are passed through untouched, so read them with .get(). Pages are followed internally until "limit" calls are collected or Gong runs out, and no cursor comes back, so raising limit is the only way to reach more calls. The cursor param only seeds where that internal walk starts, so it cannot be advanced from one call to the next. When nothing matches the filters the result is an empty list rather than an error. Call summaries carry no transcript or party detail, so follow up with transcript or get_extensive for either.
list_users()List users in the Gong account.
result = qbash.integrations.gong.list_users(
cursor="…",
include_avatars=True,
)
cursorstr | NonedefaultNonePagination cursor from a previous response, found under records.cursor.
include_avatarsbool | NonedefaultFalseInclude avatar URLs in the returned user records.
limitint | str | Nonedefault100Page size for the single request made, capped at Gong's maximum of 100. A string of digits is parsed, and anything else falls back to 100.
{"users": list[dict], "records": dict | None}This is Gong's own /v2/users body, so read it with .get(). "users" holds the user records and "records" the paging metadata, including the cursor for the next page under records["cursor"]. Exactly one page is fetched, so to walk the account pass that cursor back in on a further call, and stop when it is absent. When Gong reports no users the result is {"users": []} with no "records" key at all, so read records with .get() and treat a missing one as the end. Gong has no full name field, so each user gains a "display_name" built here from firstName and lastName, falling back to emailAddress, and left out when all three are missing. Nothing else in the body is changed. "limit" is the page size and is capped at Gong's own maximum of 100.
get_highlights()Fetch AI-generated highlights (Next Steps) for one or more Gong calls.
result = qbash.integrations.gong.get_highlights(
call_ids=[...],
)
call_idslistrequiredGong call IDs to fetch highlights for. Blank entries are dropped, and a list left with nothing is an error.
from_date_timestr | NonedefaultNoneISO 8601 start datetime filter applied alongside the call IDs.
to_date_timestr | NonedefaultNoneISO 8601 end datetime filter applied alongside the call IDs.
list[dict] of Gong call objects, whose per record keys are Gong's ownThe result is a bare list of Gong call objects, not a dict, so index it by position rather than looking for a "calls" key. Highlights are the only field this mode asks Gong to expose, but nothing here reads or reshapes the response, so where Gong nests them inside each call object is Gong's to decide. Verify the nesting with try_call before indexing it, read every level with .get(), and expect a call with no generated highlights to omit the key. When Gong matches none of the call ids the result is an empty list rather than an error, and a partial match returns only the calls that were found, so compare the returned ids against the ones requested rather than assuming they line up. Only one page is fetched and any cursor Gong sends is discarded rather than followed, so a request covering enough calls to be paged comes back short with nothing to signal it. Keep call_ids small and batch across several calls rather than sending one long list.
get_scorecards()Fetch scorecard results from Gong for reviewed calls.
result = qbash.integrations.gong.get_scorecards(
call_from_date="…",
call_to_date="…",
)
call_from_datestr | NonedefaultNoneStart date for filtering calls (ISO 8601).
call_to_datestr | NonedefaultNoneEnd date for filtering calls (ISO 8601).
reviewed_user_idslist | NonedefaultNoneList of user IDs whose scorecards to retrieve.
scorecard_idslist | NonedefaultNoneList of scorecard IDs to filter by.
cursorstr | NonedefaultNonePagination cursor returned by a previous response.
{"scorecards": [], "records": {"totalRecords": 0}} when Gong reports no results, or dict holding Gong's own scorecards body otherwiseThe two branches do not share a collection key, which is the trap here. When Gong reports no results the shape above is built locally, with an empty "scorecards" list. On any other success Gong's own /v2/stats/activity/scorecards body is passed through untouched and nothing here reads or renames it, so its collection key is Gong's to choose and is not necessarily "scorecards". Read the body with .get() against the keys Gong's own API reference lists for that endpoint, and treat a missing "scorecards" key as a result to inspect rather than an error. Verify the live shape with try_call before indexing it. Paging metadata, when Gong sends it, is under "records", and exactly one page is fetched, so pass its cursor back in through the cursor param to continue.
list_library_folders()List Gong library folders, optionally filtered by workspace.
result = qbash.integrations.gong.list_library_folders(
workspace_id="…",
)
workspace_idstr | NonedefaultNoneGong workspace ID to filter folders by. Omit to list folders across every workspace the credential can see.
{"folders": list[dict]}This is Gong's own /v2/library/folders body passed through untouched, so "folders" is the key to index and anything else Gong sends alongside it is Gong's to choose. Read it with .get(). Each folder record carries the id to pass to get_library_content. Every folder the credential can see is returned in one request with no paging. A 404 carrying Gong's standard errors list body is flattened to an empty "folders" list without reading the error text, so a workspace_id that does not exist looks the same as a workspace with no folders. A 404 shaped any other way is returned as an error.
get_library_content()Get the calls and content inside a specific Gong library folder.
result = qbash.integrations.gong.get_library_content(
folder_id="my-folder",
)
folder_idstrrequiredID of the library folder to fetch content from, taken from list_library_folders. A blank value is an error.
cursorstr | NonedefaultNonePagination cursor from Gong's paging metadata on a previous response.
{"calls": list[dict]}This is Gong's own /v2/library/folder-content body passed through untouched, so "calls" is the key to index and anything else Gong sends alongside it, paging metadata included, is Gong's to choose. Read it with .get(). When Gong reports the folder as empty or missing, the same shape with an empty "calls" list is built here, so a wrong folder_id looks like an empty folder rather than an error. Exactly one page is fetched. Nothing here lifts a cursor to the top level, but because the body is Gong's own, whatever paging metadata Gong sends is still in the result, normally under a "records" key with the next cursor inside it. Read that with .get() and pass it back in through the cursor param to continue, and treat its absence as the end.
list_trackers()List smart tracker definitions configured in Gong, optionally filtered by workspace.
result = qbash.integrations.gong.list_trackers(
workspace_id="…",
)
workspace_idstr | NonedefaultNoneGong workspace ID to filter trackers by. Omit to list trackers across every workspace the credential can see.
{"trackers": list[dict]}This is Gong's own /v2/settings/trackers body passed through untouched, so "trackers" is the key to index and anything else Gong sends alongside it is Gong's to choose. Read it with .get(). These are the tracker definitions only, meaning names, ids and the phrases they match, and not which calls hit them. Every tracker is returned in one request with no paging. Any 404 from Gong is flattened to an empty "trackers" list without reading the error text, so a workspace_id that does not exist looks the same as a workspace with no trackers.
list_customers()List accessible Google Ads accounts and manager hierarchies. Discovery follows hierarchy pages within a time bound and max_accounts root cap. Timed-out roots appear in skipped and set truncated.
result = qbash.integrations.google_ads.list_customers(
max_accounts=10,
)
max_accountsintdefault10Accessible roots expanded, from 1 to 50.
{"status": "success", "customers": list[dict], "count": int, "skipped": list[dict], "truncated": bool}search()Run one page of GAQL reporting. Cut pages omit next_page_token. Narrow with WHERE/LIMIT or raise max_rows to avoid losing rows.
result = qbash.integrations.google_ads.search(
customer_id="…",
query="search terms",
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
querystrrequiredGAQL, use WHERE date filters and LIMIT. Money is micros (1000000 = one account currency unit). segments.date needs a filter. Examples: SELECT campaign.id, campaign.name, campaign.status, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions FROM campaign WHERE segments.date DURING LAST_30_DAYS LIMIT 1000 SELECT ad_group_criterion.keyword.text, ad_group_criterion.quality_info.quality_score, metrics.clicks FROM keyword_view WHERE segments.date DURING LAST_30_DAYS LIMIT 1000 SELECT search_term_view.search_term, metrics.clicks FROM search_term_view WHERE segments.date DURING LAST_30_DAYS LIMIT 1000 SELECT change_event.change_date_time, change_event.changed_fields FROM change_event WHERE change_event.change_date_time DURING LAST_14_DAYS LIMIT 1000 SELECT campaign_budget.name, campaign_budget.amount_micros FROM campaign_budget LIMIT 1000 SELECT ad_group_ad.ad.responsive_search_ad.headlines FROM ad_group_ad LIMIT 1000 SELECT recommendation.type, recommendation.resource_name FROM recommendation LIMIT 1000 SELECT conversion_action.name, conversion_action.type FROM conversion_action LIMIT 1000 change_event needs LIMIT <= 10000 and a date range within the last 30 days.
page_tokenstr | NonedefaultNoneGoogle next_page_token from an uncut page.
max_rowsintdefault1000Maximum returned rows, 1 to 10000.
include_summary_rowbooldefaultFalseInclude Google summary row.
return_total_results_countbooldefaultFalseInclude total count.
{"status": "success", "rows": list[dict], "row_count": int, "next_page_token": str | None, "total_results_count": int | str | None, "summary_row": dict | None, "truncated": bool, "field_mask": list[str]}. Dotted snake_case keys. Only id, *_id, *_micros, impressions, clicks, interactions and video_views strings become integers within JavaScript's safe range; larger values remain exact strings.get_field_metadata()Discover selectable, filterable and sortable GAQL fields.
result = qbash.integrations.google_ads.get_field_metadata(
resource="…",
fields=[...],
)
resourcestr | NonedefaultNoneResource name such as campaign, or provide fields.
fieldslist[str]default[]Specific field names.
include_selectable_withbooldefaultFalseInclude potentially large selectable_with lists.
{"status": "success", "fields": list[dict], "count": int}manage_campaigns()Manage Google Ads campaigns. Money is micros. Remove is permanent. read_only=True validates without applying.
result = qbash.integrations.google_ads.manage_campaigns(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. campaign: campaign_budget resource, status PAUSED/ENABLED, contains_eu_political_advertising DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING, exactly one bidding strategy such as manual_cpc: {}. Create {name: Brand, campaign_budget: customers/ID/campaignBudgets/1, manual_cpc: {}}. Update {status: PAUSED}.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}manage_budgets()Manage Google Ads budgets. Money is micros. Remove is permanent. read_only=True validates without applying.
result = qbash.integrations.google_ads.manage_budgets(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. campaign_budget: Create {name: Daily, amount_micros: 50000000, delivery_method: STANDARD, explicitly_shared: false}. Update {amount_micros: 80000000}.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}manage_ad_groups()Manage Google Ads ad groups. Money is micros. Remove is permanent. read_only=True validates without applying.
result = qbash.integrations.google_ads.manage_ad_groups(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. ad_group: Create {name: Core, campaign: customers/ID/campaigns/1, type: SEARCH_STANDARD, cpc_bid_micros: 2000000}. Update {status: PAUSED}.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}manage_criteria()Manage Google Ads criteria. Money is micros. Remove is permanent. read_only=True validates without applying.
result = qbash.integrations.google_ads.manage_criteria(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. campaign_criterion: Create {campaign: customers/ID/campaigns/1, negative: true, keyword: {text: free, match_type: BROAD}} or location: {geo_target_constant: geoTargetConstants/2840} or language: {language_constant: languageConstants/1000}. Update supported mutable fields with an explicit mask when needed. ad_group_criterion: Create {ad_group: customers/ID/adGroups/1, keyword: {text: automation, match_type: PHRASE}, status: ENABLED}. Update {cpc_bid_micros: 1500000}. Match types EXACT/PHRASE/BROAD. negative: true creates negatives. customer_negative_criterion: Create {keyword: {text: free, match_type: BROAD}}. No update, remove and recreate. campaign_bid_modifier: Create {campaign: customers/ID/campaigns/1, interaction_type: {type: CALLS}, bid_modifier: 1.2}. Update {bid_modifier: 1.1}. ad_group_bid_modifier: Create {ad_group: customers/ID/adGroups/1, device: {type: MOBILE}, bid_modifier: 1.2}. Update {bid_modifier: 1.1}.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}manage_ads()Manage Google Ads ads. Money is micros. Remove is permanent. read_only=True validates without applying.
result = qbash.integrations.google_ads.manage_ads(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. ad_group_ad: Create {ad_group: customers/ID/adGroups/1, status: ENABLED, ad: {final_urls: [https://example.com], responsive_search_ad: {headlines: [{text: ...}], descriptions: [{text: ...}]}}}. At least 3 headlines of at most 30 chars and 2 descriptions of at most 90 chars. Update {status: PAUSED}. ad: Update only, {responsive_search_ad: {headlines: [{text: ...}], descriptions: [{text: ...}]}}. Use ad_group_ad to create or remove. RSAs update in place. Legacy TextAd, ExpandedDynamicSearchAd, GmailAd and ImageAd cannot update.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}manage_assets()Manage Google Ads assets. Money is micros. Remove is permanent. read_only=True validates without applying.
result = qbash.integrations.google_ads.manage_assets(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. asset: Create {text_asset: {text: Offer}} or {sitelink_asset: {link_text: Pricing, description1: View plans}, final_urls: [https://example.com/pricing]}. Update {sitelink_asset: {link_text: Plans}}. Asset removal unsupported, remove its associations. customer_asset: Create {asset: customers/ID/assets/1, field_type: SITELINK}. Update {status: REMOVED} where supported. campaign_asset: Create {campaign: customers/ID/campaigns/1, asset: customers/ID/assets/1, field_type: SITELINK}. Update {status: REMOVED} where supported. ad_group_asset: Create {ad_group: customers/ID/adGroups/1, asset: customers/ID/assets/1, field_type: CALLOUT}. Update {status: REMOVED} where supported.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}manage_bidding_strategies()Manage Google Ads bidding strategies. Money is micros. Remove is permanent. read_only=True validates without applying.
result = qbash.integrations.google_ads.manage_bidding_strategies(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. bidding_strategy: Create {name: Portfolio, target_cpa: {target_cpa_micros: 10000000}}. Update {target_cpa: {target_cpa_micros: 12000000}}. One bidding strategy oneof. bidding_seasonality_adjustment: Create {name: Sale, scope: CAMPAIGN, campaigns: [customers/ID/campaigns/1], start_date_time: 2026-11-01 00:00:00, end_date_time: 2026-11-02 00:00:00, conversion_rate_modifier: 1.2}. Update {conversion_rate_modifier: 1.1}. bidding_data_exclusion: Create {name: Outage, scope: CAMPAIGN, campaigns: [customers/ID/campaigns/1], start_date_time: 2026-11-01 00:00:00, end_date_time: 2026-11-02 00:00:00}. Update {end_date_time: 2026-11-03 00:00:00}.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}manage_conversion_actions()Manage Google Ads conversion actions. Money is micros. Remove is permanent. read_only=True validates without applying.
result = qbash.integrations.google_ads.manage_conversion_actions(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. conversion_action: Create {name: Signup, type: UPLOAD_CLICKS, category: SIGNUP, status: ENABLED}. Update {primary_for_goal: false}.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}manage_shared_sets()Manage Google Ads shared sets. Money is micros. Remove is permanent. read_only=True validates without applying.
result = qbash.integrations.google_ads.manage_shared_sets(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. shared_set: Create {name: Global negatives, type: NEGATIVE_KEYWORDS}. Update {name: Brand negatives}. shared_criterion: Create {shared_set: customers/ID/sharedSets/1, keyword: {text: free, match_type: BROAD}}. No update, remove and recreate. campaign_shared_set: Create {campaign: customers/ID/campaigns/1, shared_set: customers/ID/sharedSets/1}. No update, remove and recreate.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}manage_experiments()Manage Google Ads experiments. Money is micros. Remove is permanent. read_only=True validates without applying. Schedule/promote return operation_name. Poll experiment.status with search.
result = qbash.integrations.google_ads.manage_experiments(
customer_id="…",
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]default[]1 to 1000 objects with kind, action create/update/remove, fields and resource_name. Single-kind modes may omit kind. Updates derive a leaf update_mask excluding resource_name. Lists, null and empty maps are leaves. Explicit update_mask overrides derivation. Negative resource IDs reference creates in this request. Money is micros. Remove is permanent. experiment: Create {name: Bid test, type: SEARCH_CUSTOM, suffix: trial}. Update {name: New test}. Lifecycle actions schedule/end/promote/graduate accept experiment resource and validate with read_only. experiment_arm: Create {experiment: customers/ID/experiments/1, name: Trial, control: false, traffic_split: 50, campaigns: [customers/ID/campaigns/2]}. Update {name: Treatment}.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors. Cannot be true when creating experiment arms.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
actionstrdefault"mutate"mutate, schedule, end, promote or graduate.
experimentstr | NonedefaultNonecustomers/ID/experiments/ID for lifecycle actions.
campaign_budget_mappingslist[dict]default[]Graduate requires one mapping with experiment_campaign and campaign_budget.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None, "experiment": str | None, "operation_name": str | None}mutate()Send raw MutateOperation objects of any kind. Prefer named manage modes. Remove is permanent. Money is micros. read_only=True validates without applying.
result = qbash.integrations.google_ads.mutate(
customer_id="…",
mutate_operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
mutate_operationslist[dict]required1 to 1000 raw operation objects in camelCase or snake_case. Negative IDs can reference previous creates.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors. Cannot be true when creating experiment arms.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resource objects alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}create_search_campaign()Create an atomic Search campaign with budget, targeting, groups, keywords, negatives and RSAs. Defaults to PAUSED. Money is micros. read_only=True validates without applying.
result = qbash.integrations.google_ads.create_search_campaign(
customer_id="…",
name="…",
daily_budget_micros=0,
bidding={"key": "value"},
ad_groups={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
namestrrequiredCampaign name.
daily_budget_microsintrequiredPositive daily budget in micros.
biddingdictrequiredOne strategy, for example manual_cpc: {} or maximize_conversions: {}.
ad_groupslist[dict]requiredNamed groups with optional cpc_bid_micros, keywords and negative_keywords (text, match_type) and responsive_search_ad (headlines, descriptions, final_urls, path1, path2). At least 3 headlines <=30 chars and 2 descriptions <=90 chars.
locationslist[str]default[]geoTargetConstants resource names.
languageslist[str]default[]languageConstants resource names.
statusstrdefault"PAUSED"PAUSED or ENABLED.
contains_eu_political_advertisingstrdefault"DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING"Political advertising declaration.
read_onlybooldefaultFalseTrue validates with Google without applying changes.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None, "campaign_resource_name": str | None, "budget_resource_name": str | None, "ad_group_resource_names": list[str]}upload_click_conversions()Upload offline click conversions. Forces partial failure. read_only=True validates without applying.
result = qbash.integrations.google_ads.upload_click_conversions(
customer_id="…",
conversions={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
conversionslist[dict]requiredconversion_action resource, conversion_date_time yyyy-mm-dd hh:mm:ss+|-hh:mm and exactly one gclid/gbraid/wbraid. Optional conversion_value, currency_code, order_id and consent (ad_user_data/ad_personalization).
read_onlybooldefaultFalseTrue validates with Google without applying changes.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "job_id": int | str | None}apply_recommendation()Apply recommendations. Test runs refuse this write because Google has no validation endpoint.
result = qbash.integrations.google_ads.apply_recommendation(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
operationslist[dict]required1 to 100 objects, each with resource_name and optional per-type parameters such as campaign_budget (new_budget_amount_micros) or keyword (cpc_bid_micros).
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
{"status": "success", "results": list[dict], "errors": list[dict]}dismiss_recommendation()Dismiss recommendations. Test runs refuse this write because Google has no validation endpoint.
result = qbash.integrations.google_ads.dismiss_recommendation(
customer_id="…",
resource_names=[...],
)
customer_idstrrequired10 digit Google Ads customer ID. Dashes are stripped.
login_customer_idstr | NonedefaultNoneTop manager ID from list_customers when accessing client accounts.
resource_nameslist[str]required1 to 100 recommendation resource names to dismiss.
partial_failurebooldefaultFalseTrue applies valid operations and returns per-item errors.
{"status": "success", "results": list[dict], "errors": list[dict]}generate_keyword_ideas()Discover keyword ideas from keywords, a URL, keywords with a URL, or a site. Planning quotas are stricter; save results instead of repeatedly fetching them. Reads retry once when Google supplies a retry delay of at most five seconds.
result = qbash.integrations.google_ads.generate_keyword_ideas(
customer_id="…",
)
customer_idstrrequired10-digit advertiser account ID; dashes are stripped.
login_customer_idstr | NonedefaultNoneManager ID from list_customers for manager access.
languagestr | NonedefaultNonelanguageConstants/ID; English is languageConstants/1000. Omit for all languages.
geo_target_constantslist[str]default[]Up to 10 geoTargetConstants/ID locations from suggest_geo_targets. Empty means all locations.
keyword_plan_networkstrdefault"GOOGLE_SEARCH"GOOGLE_SEARCH or GOOGLE_SEARCH_AND_PARTNERS.
include_adult_keywordsbooldefaultFalseInclude adult keyword results.
historical_metrics_optionsdict | NonedefaultNoneinclude_average_cpc boolean and/or year_month_range {start: {year: 2025, month: JANUARY}, end: {year: 2025, month: DECEMBER}}. Uppercase months. Default last 12 months; Google may return only available months within the requested range.
aggregate_metricsdict | NonedefaultNoneUse {aggregate_metric_types: [DEVICE]} for device-segmented searches.
keywordslist[str]default[]Up to 20 seed keywords. May combine with url; exclusive with site.
urlstr | NonedefaultNoneHTTP(S) page URL to generate ideas from. May combine with keywords.
sitestr | NonedefaultNoneDomain such as qbash.com; exclusive with keywords and url.
page_sizeintdefault1000Requested and returned results per page, 1 to 10000. A locally cut page suppresses its cursor.
page_tokenstr | NonedefaultNonenext_page_token from an uncut page. Keep all other arguments identical.
keyword_annotationlist[str]default[]Use [KEYWORD_CONCEPT] for brand/non-brand concept annotations.
{"status": "success", "results": list[dict], "count": int, "total_size": int | str | None, "next_page_token": str | None, "truncated": bool, "aggregate_metric_results": dict | None}. Results contain text, close_variants, keyword_idea_metrics and optional keyword_annotations. Nested int64 metrics remain decimal strings, including micros. Missing metrics remain absent, not zero.generate_keyword_historical_metrics()Get search volume, competition and bid estimates for supplied keywords. Google combines close variants; one result per input is not guaranteed. Historical data refreshes monthly.
result = qbash.integrations.google_ads.generate_keyword_historical_metrics(
customer_id="…",
keywords=[...],
)
customer_idstrrequired10-digit advertiser account ID; dashes are stripped.
login_customer_idstr | NonedefaultNoneManager ID from list_customers for manager access.
keywordslist[str]required1 to 10000 keywords to evaluate.
languagestr | NonedefaultNonelanguageConstants/ID; English is languageConstants/1000. Omit for all languages.
geo_target_constantslist[str]default[]Up to 10 geoTargetConstants/ID locations from suggest_geo_targets. Empty means all locations.
keyword_plan_networkstrdefault"GOOGLE_SEARCH"GOOGLE_SEARCH or GOOGLE_SEARCH_AND_PARTNERS.
include_adult_keywordsbooldefaultFalseInclude adult keyword results.
historical_metrics_optionsdict | NonedefaultNoneinclude_average_cpc boolean and/or year_month_range {start: {year: 2025, month: JANUARY}, end: {year: 2025, month: DECEMBER}}. Uppercase months. Default last 12 months; Google may return only available months within the requested range.
aggregate_metricsdict | NonedefaultNoneUse {aggregate_metric_types: [DEVICE]} for device-segmented searches.
{"status": "success", "results": list[dict], "aggregate_metric_results": dict | None}. Results contain text, close_variants and keyword_metrics: avg_monthly_searches, monthly_search_volumes, competition, competition_index, low_top_of_page_bid_micros, high_top_of_page_bid_micros and optional average_cpc_micros. int64 values remain decimal strings. Missing metrics are not zero.generate_ad_group_themes()Suggest keyword text, match types and assignments to existing ad groups. Does not create ad groups or keywords. Returns unusable groups separately.
result = qbash.integrations.google_ads.generate_ad_group_themes(
customer_id="…",
keywords=[...],
ad_groups=[...],
)
customer_idstrrequired10-digit advertiser account ID; dashes are stripped.
login_customer_idstr | NonedefaultNoneManager ID from list_customers for manager access.
keywordslist[str]required1 to 10000 keywords to group.
ad_groupslist[str]required1 to 1000 existing customers/ID/adGroups/ID resource names in customer_id. Google assigns keywords to these groups; this is not arbitrary clustering.
{"status": "success", "ad_group_keyword_suggestions": list[dict], "unusable_ad_groups": list[dict]}. Suggestions include keyword_text, suggested_keyword_text, suggested_match_type, suggested_ad_group and suggested_campaign.generate_keyword_forecast_metrics()Estimate clicks, cost or conversions for an inline v25 campaign. Does not create a campaign or saved plan. Metrics depend on strategy and may be absent.
result = qbash.integrations.google_ads.generate_keyword_forecast_metrics(
customer_id="…",
campaign={"key": "value"},
)
customer_idstrrequired10-digit advertiser account ID; dashes are stripped.
login_customer_idstr | NonedefaultNoneManager ID from list_customers for manager access.
campaigndictrequiredInline v25 campaign: language_constants [languageConstants/1000], unique geo_target_constants [geoTargetConstants/2124], bidding_strategy and ad_groups [{keywords: [{text: workflow, match_type: EXACT}]}]. One strategy: manual_cpc_bidding_strategy {max_cpc_bid_micros: 1000000, optional daily_budget_micros}; maximize_clicks_bidding_strategy {daily_target_spend_micros, optional max_cpc_bid_ceiling_micros}; or maximize_conversions_bidding_strategy {daily_target_spend_micros}. Positive micros accept integers or decimal strings. Match types: EXACT, PHRASE, BROAD. No keyword-level bids or network field in v25.
forecast_perioddict | NonedefaultNone{start_date: YYYY-MM-DD, end_date: YYYY-MM-DD}. Google requires a future start and end within one year using the customer account timezone. Omit for next-week default. Google enforces currency-specific minimum budgets.
currency_codestr | NonedefaultNoneOptional three-letter currency such as CAD; default is account currency.
{"status": "success", "campaign_forecast_metrics": dict | None}. Includes clicks or conversions, cost_micros and optional average_cpc_micros or average_cpa_micros. Micros remain decimal strings in account currency (or currency_code). Clicks/conversions are numbers. Estimates are not guarantees.suggest_geo_targets()Find location resource names for keyword research and targeting. Uses connected OAuth; no customer ID required.
result = qbash.integrations.google_ads.suggest_geo_targets(
location_names=[...],
geo_target_constants=[...],
)
location_nameslist[str]default[]Up to 25 location names, such as Toronto or Canada; exclusive with geo_target_constants.
geo_target_constantslist[str]default[]Up to 25 geoTargetConstants/ID resource names to resolve; exclusive with location_names.
localestrdefault"en"Translation locale. Google may fall back to English.
country_codestr | NonedefaultNoneRestrict to an uppercase two-letter country code, such as CA.
{"status": "success", "suggestions": list[dict]}. Includes search_term, locale, reach, geo_target_constant and geo_target_constant_parents. Targets include resource_name, name, canonical_name, country_code, target_type and status. int64 values remain decimal strings.list_keyword_plans()List saved plans. Use search to inspect keyword_plan_campaign, keyword_plan_ad_group, keyword_plan_ad_group_keyword and keyword_plan_campaign_keyword resources.
result = qbash.integrations.google_ads.list_keyword_plans(
customer_id="…",
)
customer_idstrrequired10-digit advertiser account ID; dashes are stripped.
login_customer_idstr | NonedefaultNoneManager ID from list_customers for manager access.
page_tokenstr | NonedefaultNoneGoogle next_page_token from an uncut page.
max_rowsintdefault1000Maximum returned plans, 1 to 10000.
{"status": "success", "plans": list[dict], "count": int, "next_page_token": str | None, "truncated": bool}. Plans contain resource_name, id, name and forecast_period. IDs remain strings. Cut pages suppress cursors; narrow with search or raise max_rows to avoid losing rows.manage_keyword_plans()Create, update or remove saved plans and their campaigns, ad groups and keywords. Planning resources do not serve ads. read_only=True validates without saving. Removal is permanent.
result = qbash.integrations.google_ads.manage_keyword_plans(
customer_id="…",
operations={"key": "value"},
)
customer_idstrrequired10-digit advertiser account ID; dashes are stripped.
login_customer_idstr | NonedefaultNoneManager ID from list_customers for manager access.
operationslist[dict]required1 to 1000 operations with kind, action create/update/remove, fields, and resource_name for update/remove. Kinds: keyword_plan, keyword_plan_campaign, keyword_plan_ad_group, keyword_plan_ad_group_keyword, keyword_plan_campaign_keyword. Updates derive update_mask; explicit masks override. Negative resource IDs reference earlier creates in an atomic request. Plan create: name; optional forecast_period {date_interval: NEXT_WEEK|NEXT_MONTH|NEXT_QUARTER} or {date_range: {start_date, end_date}}. Campaign create: keyword_plan, name, keyword_plan_network, cpc_bid_micros; optional language_constants (max 1; [] clears targeting on update), geo_targets [{geo_target_constant: geoTargetConstants/ID}] (max 20). Ad group create: keyword_plan_campaign, name; optional cpc_bid_micros. Ad-group keyword: keyword_plan_ad_group, text, match_type; optional cpc_bid_micros and negative. Negative ad-group keywords cannot carry cpc_bid_micros. On update, cpc_bid_micros=None removes an optional ad-group or positive keyword override to inherit the parent bid; campaign bids remain required. Campaign keyword: keyword_plan_campaign, text, match_type, negative=true. All resources belong to customer_id. Google enforces per-plan limits: one campaign, 200 ad groups, 10000 keywords. Use search to retrieve saved components.
partial_failurebooldefaultFalseApply valid operations and return per-item errors. Keep false for dependent creates using temporary resource names.
read_onlybooldefaultFalseValidate with Google without applying changes.
return_resourcesbooldefaultFalseReturn mutable resources alongside resource names.
{"status": "success", "applied": bool, "results": list[dict], "errors": list[dict], "request_id": str | None}. Results contain index, kind, resource_name and optional resource. Errors include index, code, field_path and message.list_properties()List Google Analytics accounts and GA4 properties available to the connected account.
result = qbash.integrations.google_analytics.list_properties(
page_size=10,
page_token="…",
)
page_sizeintdefault50Maximum account summaries to return, from 1 through 200.
page_tokenstr | NonedefaultNoneToken from the previous page.
{"status": "success", "accounts": list[dict], "account_count": int, "properties": list[dict], "property_count": int, "next_page_token": str | None}Returns one Admin API page. properties is a flattened copy of every property in the returned accounts and includes property_id, display_name, property_type, parent, can_edit, account, and account_display_name when Google provides them. Pass next_page_token back as page_token until it is absent.
get_metadata()List dimensions, metrics, and saved comparisons available to a GA4 property.
result = qbash.integrations.google_analytics.get_metadata(
property_id="…",
)
property_idstrrequiredNumeric GA4 property ID, with or without the properties/ prefix.
kindstrdefault"all"Metadata group: all, dimensions, metrics, or comparisons.
searchstr | NonedefaultNoneOptional case-insensitive metadata search text.
{"status": "success", "property_id": str, "dimensions": list[dict], "metrics": list[dict], "comparisons": list[dict], "dimension_count": int, "metric_count": int, "comparison_count": int}Property-specific metadata includes custom dimensions and metrics. Use property_id 0 for universal metadata only. search performs a case-insensitive client-side match over API name, UI name, category, and description; kind can suppress unneeded groups to keep the result small.
check_compatibility()Check whether GA4 dimensions and metrics can be used together in a standard report.
result = qbash.integrations.google_analytics.check_compatibility(
property_id="…",
)
property_idstrrequiredNumeric GA4 property ID, with or without the properties/ prefix.
dimensionslist[str]default[]Dimension API names to check.
metricslist[str]default[]Metric API names to check.
compatibility_filterstrdefault"COMPATIBLE"COMPATIBLE, INCOMPATIBLE, or COMPATIBILITY_UNSPECIFIED.
{"status": "success", "property_id": str, "dimensions": list[dict], "metrics": list[dict]}Checks Core reporting compatibility, not realtime compatibility. Google returns metadata plus COMPATIBLE or INCOMPATIBLE for each candidate. At least one dimension or metric is required.
run_report()Run a standard GA4 report with dimensions, metrics, filters, ordering, and pagination.
result = qbash.integrations.google_analytics.run_report(
property_id="…",
date_ranges={"key": "value"},
metrics=[...],
)
property_idstrrequiredNumeric GA4 property ID, with or without the properties/ prefix.
date_rangeslist[dict]requiredOne to four date ranges.
start_datestrrequiredInclusive YYYY-MM-DD date or a GA4 relative date such as 28daysAgo.
end_datestrrequiredInclusive YYYY-MM-DD date or a GA4 relative date such as yesterday.
namestr | NonedefaultNoneOptional range name returned in report metadata.
dimensionslist[str]default[]Up to 9 GA4 dimension API names.
metricslist[str]requiredFrom 1 through 10 GA4 metric API names.
dimension_filterslist[dict]default[]Dimension filters combined by filter_logic.
field_namestrrequiredDimension API name, for example pagePath or sessionDefaultChannelGroup.
operatorstrdefault"exact"exact, begins_with, ends_with, contains, full_regexp, partial_regexp, in_list, or empty.
valuestr | NonedefaultNoneSingle value for every operator except in_list and empty.
valueslist[str]default[]Values for the in_list operator.
case_sensitivebooldefaultFalseWhether string matching is case-sensitive.
metric_filterslist[dict]default[]Post-aggregation metric filters combined by filter_logic.
field_namestrrequiredMetric API name, for example sessions or activeUsers.
operatorstrdefault"equal"equal, less_than, less_than_or_equal, greater_than, or greater_than_or_equal.
valueint | floatrequiredNumeric comparison value.
filter_logicstrdefault"and"Combine each filter list with and or or.
return_property_quotabooldefaultFalseInclude the property's current Data API quota state.
order_byslist[dict]default[]Report ordering rules.
field_namestrrequiredDimension or metric API name to sort by.
typestrdefault"metric"Sort target type: metric or dimension.
descendingbooldefaultFalseSort descending when true.
order_typestrdefault"alphanumeric"Dimension ordering: alphanumeric, case_insensitive_alphanumeric, or numeric.
metric_aggregationslist[str]default[]Any of TOTAL, MINIMUM, MAXIMUM, or COUNT.
limitintdefault10000Maximum rows to return, from 1 through 250000.
offsetintdefault0Zero-based row offset for pagination.
currency_codestr | NonedefaultNoneOptional ISO 4217 currency code.
keep_empty_rowsbooldefaultFalseKeep rows whose requested metrics are all zero.
{"status": "success", "property_id": str, "dimension_headers": list[str], "metric_headers": list[dict], "rows": list[dict], "row_count": int, "totals": list[dict] | None, "maximums": list[dict] | None, "minimums": list[dict] | None, "metadata": dict | None, "property_quota": dict | None, "pivot_headers": list[dict] | None}Rows are normalized into dimensions and metrics maps keyed by the requested API names. Integer metrics are integers and other numeric metric values are floats when parseable. GA4 returns at most 250,000 rows per request; compare row_count with offset plus the returned row length to decide whether to request another page. Date ranges may use YYYY-MM-DD or GA4 relative dates such as 28daysAgo, yesterday, and today.
run_realtime_report()Run a GA4 realtime report for activity from the last 30 or 60 minutes.
result = qbash.integrations.google_analytics.run_realtime_report(
property_id="…",
metrics=[...],
)
property_idstrrequiredNumeric GA4 property ID, with or without the properties/ prefix.
minute_rangeslist[dict]default[]Optional named realtime ranges. Omit for Google's default window.
start_minutes_agointrequiredStart of the realtime range in minutes ago.
end_minutes_agointrequiredEnd of the realtime range in minutes ago.
namestr | NonedefaultNoneOptional range name.
dimensionslist[str]default[]GA4 realtime dimension API names.
metricslist[str]requiredOne or more GA4 realtime metric API names.
dimension_filterslist[dict]default[]Dimension filters combined by filter_logic.
field_namestrrequiredDimension API name, for example pagePath or sessionDefaultChannelGroup.
operatorstrdefault"exact"exact, begins_with, ends_with, contains, full_regexp, partial_regexp, in_list, or empty.
valuestr | NonedefaultNoneSingle value for every operator except in_list and empty.
valueslist[str]default[]Values for the in_list operator.
case_sensitivebooldefaultFalseWhether string matching is case-sensitive.
metric_filterslist[dict]default[]Post-aggregation metric filters combined by filter_logic.
field_namestrrequiredMetric API name, for example sessions or activeUsers.
operatorstrdefault"equal"equal, less_than, less_than_or_equal, greater_than, or greater_than_or_equal.
valueint | floatrequiredNumeric comparison value.
filter_logicstrdefault"and"Combine each filter list with and or or.
return_property_quotabooldefaultFalseInclude the property's current Data API quota state.
order_byslist[dict]default[]Realtime report ordering rules.
field_namestrrequiredDimension or metric API name to sort by.
typestrdefault"metric"Sort target type: metric or dimension.
descendingbooldefaultFalseSort descending when true.
order_typestrdefault"alphanumeric"Dimension ordering: alphanumeric, case_insensitive_alphanumeric, or numeric.
metric_aggregationslist[str]default[]Any of TOTAL, MINIMUM, MAXIMUM, or COUNT.
limitintdefault10000Maximum rows to return, from 1 through 250000.
{"status": "success", "property_id": str, "dimension_headers": list[str], "metric_headers": list[dict], "rows": list[dict], "row_count": int, "totals": list[dict] | None, "maximums": list[dict] | None, "minimums": list[dict] | None, "metadata": dict | None, "property_quota": dict | None, "pivot_headers": list[dict] | None}Realtime data usually covers the last 30 minutes and can cover 60 minutes for Analytics 360 properties. Realtime dimensions and metrics have different compatibility rules from standard reports. Rows use the same normalized dimensions and metrics maps as run_report.
run_pivot_report()Run an advanced GA4 pivot report across one or more dimension axes.
result = qbash.integrations.google_analytics.run_pivot_report(
property_id="…",
date_ranges={"key": "value"},
pivots={"key": "value"},
metrics=[...],
)
property_idstrrequiredNumeric GA4 property ID, with or without the properties/ prefix.
date_rangeslist[dict]requiredOne to four date ranges.
start_datestrrequiredInclusive YYYY-MM-DD date or a GA4 relative date such as 28daysAgo.
end_datestrrequiredInclusive YYYY-MM-DD date or a GA4 relative date such as yesterday.
namestr | NonedefaultNoneOptional range name returned in report metadata.
pivotslist[dict]requiredOne or more pivot axes covering every requested dimension exactly once.
field_nameslist[str]requiredDimensions shown by this pivot. A dimension may belong to only one pivot.
offsetintdefault0Zero-based starting row or column for this pivot.
limitintdefault100Maximum unique dimension combinations for this pivot.
metric_aggregationslist[str]default[]Any of TOTAL, MINIMUM, MAXIMUM, or COUNT.
order_byslist[dict]default[]Ordering rules scoped to this pivot.
field_namestrrequiredDimension or metric API name to sort by.
typestrdefault"metric"Sort target type: metric or dimension.
descendingbooldefaultFalseSort descending when true.
order_typestrdefault"alphanumeric"Dimension ordering: alphanumeric, case_insensitive_alphanumeric, or numeric.
dimensionslist[str]default[]Up to 9 GA4 dimension API names.
metricslist[str]requiredFrom 1 through 10 GA4 metric API names.
dimension_filterslist[dict]default[]Dimension filters combined by filter_logic.
field_namestrrequiredDimension API name, for example pagePath or sessionDefaultChannelGroup.
operatorstrdefault"exact"exact, begins_with, ends_with, contains, full_regexp, partial_regexp, in_list, or empty.
valuestr | NonedefaultNoneSingle value for every operator except in_list and empty.
valueslist[str]default[]Values for the in_list operator.
case_sensitivebooldefaultFalseWhether string matching is case-sensitive.
metric_filterslist[dict]default[]Post-aggregation metric filters combined by filter_logic.
field_namestrrequiredMetric API name, for example sessions or activeUsers.
operatorstrdefault"equal"equal, less_than, less_than_or_equal, greater_than, or greater_than_or_equal.
valueint | floatrequiredNumeric comparison value.
filter_logicstrdefault"and"Combine each filter list with and or or.
return_property_quotabooldefaultFalseInclude the property's current Data API quota state.
currency_codestr | NonedefaultNoneOptional ISO 4217 currency code.
keep_empty_rowsbooldefaultFalseKeep pivot rows whose requested metrics are all zero.
{"status": "success", "property_id": str, "dimension_headers": list[str], "metric_headers": list[dict], "rows": list[dict], "row_count": int, "aggregates": list[dict] | None, "metadata": dict | None, "property_quota": dict | None, "pivot_headers": list[dict] | None}Every requested dimension must appear in exactly one pivot. Rows are normalized like standard report rows and pivot_headers preserves Google's pivot-specific column and cardinality metadata. A pivot's metric_aggregations come back in aggregates, rows normalized the same way whose dimension values are RESERVED_TOTAL, RESERVED_MINIMUM, RESERVED_MAXIMUM, or RESERVED_COUNT. Pivot reports have no totals, maximums, or minimums. Use this for cross-tabs and multi-axis reports; use run_report for ordinary flat tables.
find_folder()Find a folder in Google Drive by name, or list every folder.
result = qbash.integrations.google_drive.find_folder(
name="…",
parent_id="…",
)
namestr | NonedefaultNoneName of the folder to find. Omit to list every folder in scope. Not used for a by_id lookup.
parent_idstr | NonedefaultNoneParent folder ID to search within. Omit to search every accessible folder, including shared ones.
exact_matchbooldefaultFalseMatch the name exactly. False matches any folder whose name contains the value.
search_typestrdefault"by_name"Lookup strategy, either by_name or by_id.
folder_idstr | NonedefaultNoneFolder ID to look up, required when search_type is by_id.
drive_idstr | NonedefaultNoneShared Drive ID for targeted queries. Improves reliability when working with Shared Drives.
page_sizeint | NonedefaultNoneMaximum number of results to return per page (1-1000, default 100).
page_tokenstr | NonedefaultNonePagination cursor from a previous response.
{"status": "success", "folders": list[dict], "count": int, "search_term": str, "exact_match": bool, "parent_id": str | None, "first_folder_id": str | None, "next_page_token": str | None, "message": str} when search_type is by_name, or {"status": "success", "folders": list[dict], "count": int, "search_type": "by_id", "folder_id": str, "message": str} when search_type is by_idEach folder map holds id, name, createdTime, modifiedTime, parents and webViewLink, with an empty string for anything Drive did not return. Leave name out to list every folder in scope, ordered by name, and search_term then comes back as an empty string. A by_id lookup needs only folder_id. page_size and page_token apply to the by_name search only, and next_page_token is absent on the last page. A search that matches nothing comes back with count 0 and an empty folders list. On the by_name shape parent_id is echoed back only when one was given, and first_folder_id only when there was at least one match, so read both with .get().
create_folder()Create a new folder in Google Drive.
result = qbash.integrations.google_drive.create_folder(
name="…",
)
namestrrequiredName of the folder to create.
parent_idstr | NonedefaultNoneParent folder ID for the new folder. Defaults to the Drive root.
{"status": "success", "folder": dict, "message": str}The folder map holds id, name, createdTime, modifiedTime, parents and webViewLink. modifiedTime is always an empty string because the create request asks Drive only for id, name, createdTime, webViewLink and parents. Drive allows two folders with the same name in one parent, so this always creates a new folder rather than reusing a matching one.
delete_folder()Delete a folder from Google Drive.
result = qbash.integrations.google_drive.delete_folder(
name="…",
)
namestrrequiredName of the folder to delete.
parent_idstr | NonedefaultNoneParent folder ID to search within. Omit to search every accessible folder, including shared ones.
exact_matchbooldefaultTrueMatch the name exactly. False deletes every folder whose name contains the value.
drive_idstr | NonedefaultNoneShared Drive ID for targeted queries. Improves reliability when working with Shared Drives.
{"status": "success", "deleted_folders": list[dict], "count": int, "folder_name": str, "parent_id": str | None, "errors": list[str] | None, "message": str}Deletes the folders that match the name, along with everything inside them, so an inexact match can remove several folders. One page of up to 1000 matches is deleted and no cursor is exposed, so a name matching more than that leaves the rest in place. Deletion is permanent and does not go through the Drive trash. A partial failure still returns status success, with the failed deletions listed under errors. Nothing matching returns count 0.
find_files()List files in Google Drive, optionally filtered by folder.
result = qbash.integrations.google_drive.find_files(
parent_id="…",
query="search terms",
)
parent_idstr | NonedefaultNoneFolder ID to list files in. Omit to search every accessible folder, including shared ones.
querystr | NonedefaultNoneText to match against file names. Omit to return every file in scope.
file_typestrdefault"all"Filter by type: all, documents, spreadsheets, presentations, images, videos, pdfs, or folders.
exact_matchbooldefaultFalseMatch the query exactly. False matches any file whose name contains the value.
drive_querystr | NonedefaultNoneA clause in Drive's files.list q syntax, ANDed with the other filters, for example modifiedTime > '2026-01-01T00:00:00'.
order_bystrdefault"name"Sort order as Drive sort keys, for example modifiedTime desc or createdTime,name.
drive_idstr | NonedefaultNoneShared Drive ID for targeted queries. Improves reliability when working with Shared Drives.
page_sizeint | NonedefaultNoneMaximum number of results to return per page (1-1000, default 100).
page_tokenstr | NonedefaultNonePagination cursor from a previous response.
{"status": "success", "files": list[dict], "count": int, "next_page_token": str | None, "parent_id": str | None, "file_type": str, "name_contains": str, "message": str}Each file map holds id, name, mimeType, size, createdTime, modifiedTime, webViewLink, iconLink and parents. next_page_token is absent on the last page. The query matches file names only, not file contents. Trashed files are excluded, and folders are excluded unless file_type is folders. To filter on anything else, such as file properties, modifiedTime or file contents, pass a Drive search clause as drive_query, for example not properties has { key='qbash_posted' and value='done' } to skip files update_file has marked. It is ANDed with the other filters.
delete_files()Delete files from Google Drive by name.
result = qbash.integrations.google_drive.delete_files(
file_name="…",
)
file_namestrrequiredExact name of the file to delete.
parent_idstr | NonedefaultNoneFolder ID to search within. Omit to search every accessible folder, including shared ones.
drive_idstr | NonedefaultNoneShared Drive ID for targeted queries. Improves reliability when working with Shared Drives.
{"status": "success", "delete_files_items": list[dict], "delete_files_count": int, "file_name": str, "parent_id": str | None, "errors": list[str] | None, "message": str} when at least one file matched, or {"status": "success", "deleted_files": [], "count": 0, "file_name": str, "parent_id": str | None, "message": str} when nothing matchedMatches the file name exactly and deletes every match, up to 1000 files. Deletion is permanent and does not go through the Drive trash. The collection key differs between the two shapes, so read delete_files_items when delete_files_count is present and deleted_files otherwise. A partial failure still returns status success, with the failed deletions listed under errors.
copy_files()Copy a file to a new location in Google Drive.
result = qbash.integrations.google_drive.copy_files(
file_name="…",
destination_file_name="…",
)
file_namestrrequiredExact name of the source file to copy.
destination_file_namestrrequiredName for the copied file at the destination.
source_folder_idstr | NonedefaultNoneFolder ID of the source file. Omit, or pass root, to search every accessible folder.
destination_folder_idstr | NonedefaultNoneFolder ID for the copy. Defaults to the Drive root.
drive_idstr | NonedefaultNoneShared Drive ID for targeted queries. Improves reliability when working with Shared Drives.
{"status": "success", "copy_files_items": list[dict], "copy_files_count": int, "file_name": str, "source_folder_id": str | None, "destination_folder_id": str, "destination_file_name": str, "errors": list[str] | None, "message": str} when at least one file matched, or {"status": "success", "copied_files": [], "count": 0, "file_name": str, "source_folder_id": str | None, "destination_folder_id": str, "destination_file_name": str, "message": str} when nothing matchedMatches the source name exactly and copies every match, so several source files can end up sharing the destination name. Only the first page of up to 1000 matches is copied and no cursor is exposed, so a name matching more than that leaves the rest uncopied. Each copied item holds id, name, mimeType, parents and a source_file map with the original id and name. parents always comes back empty because the copy request does not ask Drive for it, so use destination_folder_id to know where the copy landed. The collection key differs between the two shapes, so read copy_files_items when copy_files_count is present and copied_files otherwise.
move_files()Move a file to a different folder in Google Drive.
result = qbash.integrations.google_drive.move_files(
file_name="…",
)
file_namestrrequiredExact name of the file to move.
source_folder_idstr | NonedefaultNoneCurrent folder ID of the file. Omit, or pass root, to search every accessible folder.
destination_folder_idstr | NonedefaultNoneTarget folder ID. Defaults to the Drive root.
destination_file_namestr | NonedefaultNoneNew name for the file at the destination. Defaults to the current name.
drive_idstr | NonedefaultNoneShared Drive ID for targeted queries. Improves reliability when working with Shared Drives.
{"status": "success", "move_files_items": list[dict], "move_files_count": int, "file_name": str, "source_folder_id": str | None, "destination_folder_id": str, "destination_file_name": str, "errors": list[str] | None, "message": str} when at least one file matched, or {"status": "success", "moved_files": [], "count": 0, "file_name": str, "source_folder_id": str | None, "destination_folder_id": str, "destination_file_name": str, "message": str} when nothing matchedMatches the file name exactly and moves every match, up to the first page of 1000 matches. No cursor is exposed, so a name matching more than that leaves the rest in place. Each moved item holds id, name, mimeType, parents and a source_file map with the original id, name and previous_parents. parents always comes back empty because the move request does not ask Drive for it, so use destination_folder_id to know where the file landed. The move removes the file from all of its current folders. The collection key differs between the two shapes, so read move_files_items when move_files_count is present and moved_files otherwise.
download_files()Download a file from Google Drive.
result = qbash.integrations.google_drive.download_files(
file_id="…",
file_name="…",
)
file_idstr | NonedefaultNoneGoogle Drive file ID to download. Give either file_id or file_name.
file_namestr | NonedefaultNoneExact file name to look up and download. Give either file_id or file_name.
parent_idstr | NonedefaultNoneFolder ID to search within when looking up by file_name. Omit to search every accessible folder.
output_formatstrdefault"base64"How file.content comes back: base64 (the default, deprecated, read stored_file instead), text_file for a text file as text, or raw_text for the text on its own.
export_formatstr | NonedefaultNoneMIME type to export a Google Doc, Sheet, Slides or Drawing as, for example application/pdf. Ignored for other file types.
{"status": "success", "file": dict, "stored_file": file, "prompt_file": file, "file_for_prompt": list[file], "message": str}, or str holding the file text when output_format is raw_text, or {"status": "error", "message": str, "file_id": str | None, "file_name": str | None, "parent_id": str | None} when no file matchesStores the Drive file as a qbash file and returns it as stored_file, which qbash.files and other integrations' file params take. prompt_file and file_for_prompt hold the same file, ready to pass to a prompt. The file map holds id, name, original_mime_type, download_mime_type, size, original_size, parents and webViewLink, plus content with a content_encoding of base64 or text when the file is 10 MB or less. Over 10 MB, content is left out and content_omitted says so, so read the bytes through stored_file. size is the byte count of the downloaded content as an integer, while original_size is the size Drive reports for the file as a string and is null when Drive reports none, so read original_size with .get(). Google Docs, Sheets, Slides and Drawings are exported, and Google caps an export at 10 MB. Other files are streamed into storage, up to 5 GB. A large file can take many minutes, so a task that downloads one needs an expected runtime over 15 minutes. A file that cannot be stored fails the call. With output_format raw_text the call returns the text on its own instead of a map when the file is already text, or is a PDF, Word, PowerPoint, Excel or OpenDocument file that converts successfully. Anything else (an image, an archive, or a text or document file over 100 MB) and any failed conversion falls back to the same map the other formats return, so check the type of the result rather than assuming the requested format. A file that cannot be found comes back with status error rather than a failed call, so check status before reading file. Lookup by name matches exactly and takes the first result. Shared Drive targeting is not exposed on this mode.
update_file()Rename, move or mark a Google Drive file by ID.
result = qbash.integrations.google_drive.update_file(
file_id="…",
)
file_idstrrequiredGoogle Drive ID of the file to update.
namestr | NonedefaultNoneNew file name.
add_parentsstr | NonedefaultNoneFolder ID to move the file into. A Drive file is in one folder, so give one ID and put the folders to leave in remove_parents.
remove_parentsstr | list[str] | NonedefaultNoneFolder ID, or a list of them, to take the file out of.
propertiesdict | NonedefaultNoneString keys and values to set on the file, visible to every app. A value of None deletes that key.
app_propertiesdict | NonedefaultNoneString keys and values visible only to this app. Same rules as properties.
descriptionstr | NonedefaultNoneNew file description.
{"status": "success", "file": dict, "message": str}Changes only what is given, in one request. The file map holds id, name, mimeType, size, parents, description, properties, appProperties, modifiedTime and webViewLink after the update. To move a file, pass the new folder in add_parents and the old one in remove_parents. properties is merged into the file's existing properties, and a key set to None is deleted. Use properties to mark files, for example {"qbash_posted": "done"}, then find the marked or unmarked ones with find_files and a drive_query such as properties has { key='qbash_posted' and value='done' }. properties are visible to every app with access to the file. app_properties are visible only to the app that set them, so prefer properties.
create_files()Create a new file in Google Drive.
result = qbash.integrations.google_drive.create_files(
file_name="…",
)
file_namestrrequiredName of the file to create.
create_typestrdefault"content"Creation type: content, upload, google_doc, or google_sheet.
contentstr | NonedefaultNoneText content for the file. Required when create_type is content, optional for google_doc and google_sheet.
parent_idstr | NonedefaultNoneParent folder ID for the new file. Defaults to the Drive root.
content_formatstrdefault"plain_text"Format of the content when create_type is google_doc: plain_text or markdown. Markdown is converted to formatted HTML.
file_datastr | NonedefaultNoneBase64-encoded file contents, or a data URI. Required when create_type is upload.
content_typestrdefault"text/plain"MIME type recorded for the new file.
{"status": "success", "file": dict, "message": str}The file map holds id, name, mimeType, createdTime, modifiedTime, parents and webViewLink, and holds size only when Drive reports one, so read size with .get(). content_type applies to create_type content and upload only and defaults to text/plain, so set it explicitly when create_type is upload. create_type google_doc sends text/plain or text/html depending on content_format, and create_type google_sheet ignores content_type. create_type google_sheet fills cells from A1, splitting content on newlines and then on commas or tabs, and a failed content write still returns status success with an empty sheet.
upload_files()Upload a local file to Google Drive.
result = qbash.integrations.google_drive.upload_files(
file=[qbash.inputs["file"]],
)
filefile | list[file] | dict | list[dict]requiredFile reference to upload, or a list of them to upload several. A dict with filename and file_data (a data URI) also works for a small file. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
parent_idstr | NonedefaultNoneParent folder ID for the upload. Defaults to the Drive root.
{"status": "success", "file": dict, "message": str} for a single file, or list[dict] of those maps when file is a listAccepts a file reference from a task input or an earlier step, or a map with filename and file_data holding a data URI for a small file. A file reference goes up with a resumable upload read from storage in 8 MB pieces, up to qbash's 5 GB file limit, and must be cleared by the malware scan first. Passing a list uploads each file in turn and returns a list, and a single failure fails the whole call without removing the files already uploaded. A multi-GB upload needs a run whose expected runtime is over 15 minutes.
docs_fill_template()Make a Google Doc from a template by filling its {{name}} placeholders.
result = qbash.integrations.google_drive.docs_fill_template(
template_id="…",
output_name="…",
values={"key": "value"},
)
template_idstrrequiredGoogle Doc ID or docs.google.com/document/d/ URL of the template.
output_namestrrequiredName for the new document.
valuesdictrequiredPlaceholder name to value. A value is a string, or a list of strings for a placeholder that is the only text in its paragraph.
parent_idstr | NonedefaultNoneFolder ID for the new document, or root for My Drive. Defaults to the template's folder when the account can see it.
{"status": "success", "file": dict, "filled": list[str], "unfilled": list[str], "unused": list[str]}Copies the template with Drive, so images, headers, footers and styles carry over, then replaces each {{name}} placeholder in the copy. Placeholder names use letters, digits and underscores and are case-sensitive. A string value replaces the placeholder inline. A list value turns the placeholder's paragraph into one paragraph per item, keeping its bullet and indentation, and needs the placeholder to be the only text in that paragraph. An empty list removes the paragraph, except at the end of the document or right before a table, where it leaves a blank line. Values may use [label](url) links with http, https or mailto URLs, **bold** and *italic*, with a backslash before a marker to keep it as typed. Other markdown stays as typed. Newlines in a value become line breaks. Inserted text takes the placeholder's own style, so style each placeholder the way the output should look. Only body paragraphs of the first tab are filled, and placeholders in tables, headers and footers are left as they are. A template with suggested edits is refused. file holds id, name, mimeType, webViewLink and parents. filled lists the names that were replaced, unfilled the placeholders in the document that had no value (left as typed), and unused the supplied names the template does not contain. Without parent_id the copy goes in the template's folder when the connected account can see that folder, otherwise in My Drive, and file.parents says where it landed. If filling fails after the copy is made, the copy is deleted. Test runs refuse this call unless writes are turned on.
docs_get()Read a Google Doc's paragraphs and indices through the Docs API.
result = qbash.integrations.google_drive.docs_get(
document_id="…",
)
document_idstrrequiredGoogle Doc ID or docs.google.com/document/d/ URL.
fieldsstr | NonedefaultNoneDocs API field mask. Defaults to paragraph text and indices. Pass * for everything.
{"status": "success", "document_id": str, "title": str | None, "revision_id": str | None, "document": dict}document is the Docs API document resource for the first tab, limited by fields. The default mask returns body paragraphs with their text, start and end indices and bullets, without styles, headings or table contents. Pass fields="*" for the whole resource, which can be large. title and revision_id are read from the same response, so they are null when a custom fields mask leaves them out. Indices count UTF-16 code units, the unit docs_batch_update expects. When the connected account can only view the document, Docs returns it without suggested edits, so its indices can differ from what docs_batch_update would see.
docs_batch_update()Apply Google Docs API batchUpdate requests to a document.
result = qbash.integrations.google_drive.docs_batch_update(
document_id="…",
requests={"key": "value"},
)
document_idstrrequiredGoogle Doc ID or docs.google.com/document/d/ URL.
requestslist[dict]requiredOne to 100 Google Docs API batchUpdate request objects.
required_revision_idstr | NonedefaultNoneRevision ID from docs_get. The update fails if the document has changed since.
{"status": "success", "document_id": str, "replies": list[dict]}Pass 1 to 100 Google Docs API request objects, such as insertText, deleteContentRange or updateTextStyle, each holding one operation. Google applies the batch atomically, so one invalid request fails them all. Requests apply to the first tab unless they set tabId. Indices count UTF-16 code units and every insert or delete shifts the indices after it, so order edits from the end of the document. Use docs_get to read indices. With required_revision_id the call fails if the document changed since that revision.
geocode()Convert an address, city/state, or zip code to lat/lng coordinates.
result = qbash.integrations.google_maps.geocode(
address="…",
)
addressstrrequiredAddress, city/state, or zip code to geocode.
{"status": "success", "result": {"lat": float, "lng": float, "formatted_address": str, "place_id": str}}Google can return several candidate matches for one address and only the first is kept, so a vague address resolves to a single coordinate pair with nothing in the result to say other matches existed. The four keys are always present. The rest of the match, including the address components, the location type and the viewport, is dropped. An address Google cannot match comes back as an error rather than an empty result, so there is no empty success to branch on.
reverse_geocode()Convert lat/lng coordinates to a human-readable address.
result = qbash.integrations.google_maps.reverse_geocode(
lat="…",
lng="…",
)
latstrrequiredLatitude coordinate.
lngstrrequiredLongitude coordinate.
{"status": "success", "result": {"formatted_address": str, "place_id": str, "components": dict}}Google can return several addresses for one coordinate pair and only the first is kept. The components map is keyed by Google's address component type names, such as street_number, route, locality and postal_code, with the long name as the value. Which keys are present depends on the location, so read them with .get(). Short names are not exposed, and where two components share a type the first one wins. The components map is empty when Google returns no components. Coordinates Google cannot match come back as an error rather than an empty result.
distance_matrix()Calculate travel time and distance between two points.
result = qbash.integrations.google_maps.distance_matrix(
origin="…",
destination="…",
)
originstrrequiredOrigin address or lat,lng.
destinationstrrequiredDestination address or lat,lng.
travel_modestr | Nonedefault"driving"Travel mode (driving / walking / bicycling / transit).
unitsstr | Nonedefault"metric"Unit system (metric / imperial).
{"status": "success", "result": {"distance_text": str, "distance_meters": int, "duration_text": str, "duration_seconds": int, "mode": str}}One origin and one destination per call. Only the first element of the matrix Google returns is read and anything else is dropped, so this cannot be used to price a matrix of several origins against several destinations. The mode key echoes the travel_mode that was sent rather than coming from Google. distance_text and duration_text are Google's formatted strings for the requested unit system, and distance_meters and duration_seconds are its numeric values. A pair Google cannot route comes back as an error rather than a result with empty values. Traffic-aware durations, departure times, waypoints and transit fares are not exposed.
nearby_search()Find places near a location by type or keyword.
result = qbash.integrations.google_maps.nearby_search(
lat="…",
lng="…",
)
latstrrequiredLatitude of the search centre.
lngstrrequiredLongitude of the search centre.
radiusintdefault1000Search radius in metres.
typestr | NonedefaultNonePlace type to filter by (e.g. restaurant).
keywordstr | NonedefaultNoneKeyword to filter results.
max_resultsintdefault20Maximum number of places to return.
{"status": "success", "results": list[dict], "count": int}Each place map carries name, address, lat, lng, rating, place_id and types, where address is Google's vicinity string and types is an empty list when Google sends none. Keys whose value is missing are dropped, so read rating and address with .get(). Google's next page token is not read, so a single page of results is available and max_results only trims that page rather than fetching more. The count key is the number of places left after that trim. A search with no matches returns an empty results list and a count of zero rather than an error. Opening hours, phone number and website are not part of the place map, so take a place_id from this mode and pass it to place_details for those.
place_details()Get detailed info about a specific place by place_id.
result = qbash.integrations.google_maps.place_details(
place_id="…",
)
place_idstrrequiredGoogle Maps place_id.
fieldsstr | NonedefaultNoneComma-separated list of fields to return.
{"status": "success", "result": {"name": str | None, "address": str | None, "phone": str | None, "website": str | None, "rating": float | None, "lat": float | None, "lng": float | None, "hours": list[str] | None, "types": list[str] | None, "maps_url": str | None}}Every key whose value is missing is dropped, so read each one with .get() rather than by index. The shape depends on the fields parameter. Leaving it unset asks Google for name, formatted_address, formatted_phone_number, website, opening_hours, rating, geometry, types and url, which is what fills the keys above. Setting fields narrows the request, and anything not asked for is absent from the result. Field names outside that list, such as reviews, price_level or photos, are sent to Google but are not carried into the result, so naming them changes nothing that comes back. The renamed keys are address from formatted_address, phone from formatted_phone_number, hours from the weekday_text lines of opening_hours, and maps_url from url. A place_id Google cannot find comes back as an error.
timezone()Get timezone information for a lat/lng location.
result = qbash.integrations.google_maps.timezone(
lat="…",
lng="…",
)
latstrrequiredLatitude coordinate.
lngstrrequiredLongitude coordinate.
timestampint | NonedefaultNoneUnix timestamp to use for DST calculation (defaults to current time).
{"status": "success", "result": {"timezone_id": str, "timezone_name": str, "raw_offset_seconds": int, "dst_offset_seconds": int, "total_offset_seconds": int}}All five keys are always present. total_offset_seconds is raw_offset_seconds plus dst_offset_seconds, added here rather than returned by Google. Either offset is reported as 0 when Google omits it, so a zero offset on its own does not confirm UTC. The offsets describe the instant given by timestamp, which defaults to the time of the call, so a lookup for a past or future date needs timestamp set. A location Google has no timezone for comes back as an error rather than a result with empty values.
Google Search Console
qbash.integrations.google_search_console6 methods · Google Search Console connectorlist_sites()List Search Console properties available to the connected Google account.
result = qbash.integrations.google_search_console.list_sites()
{"status": "success", "sites": list[dict], "count": int}Each site has site_url and permission_level. site_url is either a URL-prefix property such as https://www.example.com/ or a domain property such as sc-domain:example.com. Use the returned site_url exactly in the other modes.
get_site()Get one Search Console property and the connected account's permission level.
result = qbash.integrations.google_search_console.get_site(
site_url="https://…",
)
site_urlstrrequiredExact URL-prefix or sc-domain property identifier from list_sites.
{"status": "success", "site_url": str, "permission_level": str}Use the exact URL-prefix or sc-domain property identifier returned by list_sites. A property the account cannot access returns a permission or not-found error.
query_search_analytics()Query clicks, impressions, CTR, and average position for a Search Console property.
result = qbash.integrations.google_search_console.query_search_analytics(
site_url="https://…",
start_date="…",
end_date="…",
)
site_urlstrrequiredExact URL-prefix or sc-domain property identifier from list_sites.
start_datestrrequiredInclusive start date in YYYY-MM-DD format.
end_datestrrequiredInclusive end date in YYYY-MM-DD format.
dimensionslist[str]default[]Grouping dimensions: country, device, page, query, searchAppearance, date, or hour.
filterslist[dict]default[]Dimension filters combined with AND.
dimensionstrrequiredDimension to filter.
operatorstrdefault"equals"One of contains, equals, notContains, notEquals, includingRegex, or excludingRegex.
expressionstrrequiredFilter value or regular expression.
search_typestrdefault"web"Search type: web, image, video, news, discover, or googleNews.
aggregation_typestrdefault"auto"Aggregation: auto, byPage, byProperty, or byNewsShowcasePanel.
data_statestrdefault"final"Data freshness: final, all, or hourly_all. hourly_all requires the hour dimension.
row_limitintdefault1000Rows per page, from 1 through 25000.
start_rowintdefault0Zero-based row offset for pagination.
{"status": "success", "site_url": str, "start_date": str, "end_date": str, "dimensions": list[str], "rows": list[dict], "row_count": int, "start_row": int, "row_limit": int, "response_aggregation_type": str | None, "metadata": dict | None}Dates are interpreted in Pacific Time by Google. Each row has a dimensions map keyed by the requested dimension names plus clicks, impressions, ctr, and position. Google returns top rows rather than a guaranteed exhaustive result set, sorted by clicks unless date is requested. For pagination, increment start_row by row_limit until a page contains fewer rows than row_limit. row_limit may be 1-25000; Search Console documents a maximum of 50,000 rows per day per property and search type.
inspect_url()Inspect Google's indexed version of a URL.
result = qbash.integrations.google_search_console.inspect_url(
site_url="https://…",
inspection_url="https://…",
)
site_urlstrrequiredExact URL-prefix or sc-domain property identifier from list_sites.
inspection_urlstrrequiredAbsolute HTTP or HTTPS URL to inspect.
language_codestrdefault"en-US"BCP-47 language code for translated issue messages.
{"status": "success", "inspection_url": str, "site_url": str, "verdict": str | None, "coverage_state": str | None, "robots_txt_state": str | None, "indexing_state": str | None, "last_crawl_time": str | None, "page_fetch_state": str | None, "google_canonical": str | None, "user_canonical": str | None, "crawled_as": str | None, "sitemaps": list[str], "referring_urls": list[str], "inspection_result_link": str | None, "rich_results": dict | None, "amp": dict | None}This reports the version in Google's index; it is not a live URL test. The inspected URL must belong to the property identified by site_url. Optional fields that Google omits are also omitted from the result.
list_sitemaps()List submitted sitemaps for a Search Console property or sitemap index.
result = qbash.integrations.google_search_console.list_sitemaps(
site_url="https://…",
)
site_urlstrrequiredExact URL-prefix or sc-domain property identifier from list_sites.
sitemap_indexstr | NonedefaultNoneOptional absolute sitemap-index URL whose child entries should be listed.
{"status": "success", "site_url": str, "sitemaps": list[dict], "count": int}Each sitemap can include path, last_submitted, is_pending, is_sitemaps_index, type, last_downloaded, warnings, errors, and contents. sitemap_index filters the result to entries included by that sitemap index. The contents list contains submitted and indexed counts by content type, not the individual page URLs.
get_sitemap()Get status, counts, warnings, and errors for one submitted sitemap.
result = qbash.integrations.google_search_console.get_sitemap(
site_url="https://…",
sitemap_url="https://…",
)
site_urlstrrequiredExact URL-prefix or sc-domain property identifier from list_sites.
sitemap_urlstrrequiredAbsolute URL of the submitted sitemap.
{"status": "success", "site_url": str, "sitemap": dict}The sitemap map uses the same normalized fields as list_sitemaps. contents reports aggregate submitted and indexed counts; Search Console does not return the sitemap's individual URLs through this endpoint.
search()Search Hacker News by relevance.
result = qbash.integrations.hackernews.search(
query="search terms",
tags="…",
)
querystr | NonedefaultNoneSearch query text.
tagsstr | NonedefaultNoneHN/Algolia tags such as story, comment, front_page, or author_pg.
numeric_filtersstr | list | NonedefaultNoneAlgolia numeric filters, for example created_at_i>1781020800.
hits_per_pageintdefault20Maximum hits per page.
pageintdefault0Zero-based result page.
restrict_searchable_attributesstr | list | NonedefaultNoneSearch only specific attributes such as title,url,author.
typo_tolerancestr | bool | NonedefaultNoneAlgolia typo tolerance setting.
{"status": "success", "result": dict}This is the hn.algolia.com HN Search API, not the Firebase API, so a search answers with a body of hydrated items rather than a list of IDs and no per-item fetch happens here. "result" is that body forwarded unchanged. Nothing reads, renames or projects it, so the hit collection and the page counters are the vendor's keys and none of them can be guaranteed from this side. The one reshaping applied is that every key whose value is null is dropped, at every level, so an item that does not carry a url, a title, a text or children has no such key rather than a null one. Read "result" and each hit with .get(). hitsPerPage defaults to 20 and page to 0 and both are always sent, so one page comes back per call and the next is fetched by incrementing the page param. Nothing loops here, so a call never covers the whole match set. No credential is needed for this provider.
search_by_date()Search Hacker News by newest items first.
result = qbash.integrations.hackernews.search_by_date(
query="search terms",
tags="…",
)
querystr | NonedefaultNoneSearch query text.
tagsstr | NonedefaultNoneHN/Algolia tags such as story, comment, front_page, or author_pg.
numeric_filtersstr | list | NonedefaultNoneAlgolia numeric filters, for example created_at_i>1781020800.
hits_per_pageintdefault20Maximum hits per page.
pageintdefault0Zero-based result page.
restrict_searchable_attributesstr | list | NonedefaultNoneSearch only specific attributes such as title,url,author.
typo_tolerancestr | bool | NonedefaultNoneAlgolia typo tolerance setting.
{"status": "success", "result": dict}Same request and same body as search, against the /search_by_date endpoint, so the newest-first ordering is the API's own and nothing here re-sorts. "result" is that body forwarded unchanged, with every null-valued key dropped at every level, so read it and each hit with .get() rather than indexing. hitsPerPage defaults to 20 and page to 0 and both are always sent, so one page comes back per call and the next is fetched by incrementing the page param.
front_page()List current Hacker News front-page stories.
result = qbash.integrations.hackernews.front_page(
hits_per_page=0,
page=0,
)
hits_per_pageintdefault30Maximum front-page stories to return.
pageintdefault0Zero-based result page.
{"status": "success", "result": dict}This runs the /search_by_date endpoint with the front_page tag applied by the service, so "result" is that endpoint's body forwarded unchanged: a search body of the same shape the search modes return, and not a bare list of stories. The tag is set by the service, so the mode takes no tags param. hitsPerPage defaults to 30 and page to 0 and both are always sent, so one page of front-page items comes back per call and the rest is reached by incrementing the page param. Every null-valued key is dropped from the body at every level, so read it and each story with .get().
get_item()Fetch a Hacker News item and its nested children.
result = qbash.integrations.hackernews.get_item(
id=0,
)
idintrequiredHacker News item ID.
{"status": "success", "id": int | str, "result": dict}"id" echoes the value that was passed without converting it, so a string id comes back as a string. "result" is the /items/{id} body forwarded unchanged, children and all, and nothing here flattens, counts or depth-limits that tree. Every key whose value is null is dropped from the body at every level, so an item type that carries no text, no url or no title has no such key rather than a null one, and the same applies to every nested child. Read the item with .get() throughout. An id the API does not know is a 404 and reaches the script as an error rather than as an empty item.
get_user()Fetch Hacker News user metadata.
result = qbash.integrations.hackernews.get_user(
username="…",
)
usernamestrrequiredHacker News username.
{"status": "success", "username": str, "result": dict}"username" echoes the value that was passed. "result" is the /users/{username} body forwarded unchanged, so its keys are the vendor's and nothing here projects, renames or defaults them. Every key whose value is null is dropped, so read it with .get(). A username the API does not know is a 404 and reaches the script as an error rather than as an empty profile.
list_contacts()List contacts with cursor pagination.
result = qbash.integrations.hubspot.list_contacts(
limit=10,
after="…",
)
limitintdefault50Max results per page, from 1 to 100.
afterstr | NonedefaultNonePagination cursor from paging.next.after of previous response.
propertiesstr | NonedefaultNoneComma-separated property names (e.g. "email,firstname,phone"). Defaults to common contact properties.
archivedbooldefaultFalseIf true, return only archived contacts.
{"status": "success", "contacts": list[dict], "count": int, "next_after": str | None}Only one page is returned per call and nothing here loops. next_after is the paging cursor HubSpot sent, and it is null when HubSpot sent none, which is the signal to stop. Pass it back as the after param to read the next page. count is the number of records on this page rather than a total. Each record is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. Property values live under the properties key of each contact, and only the properties named in the properties param come back. Leaving that param out asks HubSpot for email, firstname, lastname, phone, company, lifecyclestage, createdate and lastmodifieddate, so any other property has to be named to be returned.
get_contact()Fetch a single contact by ID, or by email when id_property="email".
result = qbash.integrations.hubspot.get_contact(
id="…",
)
idstrrequiredContact ID, or email address if id_property="email".
id_propertystr | NonedefaultNoneSet to "email" to look up by email address.
propertiesstr | NonedefaultNoneComma-separated property names to return.
{"status": "success", "contact": dict}There is no top level id key, so read the id from the contact record. The contact is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. A contact that does not exist comes back as an error rather than an empty result, and so does a lookup by email that matches nothing.
create_contact()Create a contact. Pass standard fields directly or use `properties` for custom fields.
result = qbash.integrations.hubspot.create_contact(
email="…",
firstname="…",
)
emailstr | NonedefaultNoneContact email address (HubSpot's primary identifier).
firstnamestr | NonedefaultNoneFirst name.
lastnamestr | NonedefaultNoneLast name.
phonestr | NonedefaultNonePhone number.
companystr | NonedefaultNoneCompany name (free-text property on the contact).
propertiesdict | NonedefaultNoneMap of HubSpot property names to values for custom or additional standard fields.
company_idslist | NonedefaultNoneList of HubSpot company record IDs to associate this contact with.
{"status": "success", "contact": dict, "id": str}id repeats the id of the created record, which is also on the contact. contact is HubSpot's response, so it carries the values HubSpot stored including ones it defaulted. The contact is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. The call fails before any request is made when none of email, firstname, lastname, phone, company or properties is supplied. company_ids associates the new contact with existing companies, and that is the only association type this mode exposes.
update_contact()Patch properties on an existing contact by ID.
result = qbash.integrations.hubspot.update_contact(
id="…",
properties={"key": "value"},
)
idstrrequiredContact ID.
propertiesdictrequiredMap of property names to new values.
{"status": "success", "contact": dict, "id": str}id repeats the id of the patched record. contact is the record as it stands after the patch, so it carries values HubSpot recomputed rather than only the properties that were sent. The contact is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. properties must be a non empty map or the call fails before any request is made. Only the properties named in the map are touched, and no association can be changed through this mode.
search_contacts()Search contacts by free-text query or HubSpot filterGroups syntax.
result = qbash.integrations.hubspot.search_contacts(
filter_groups=[...],
properties="…",
)
filter_groupslist | NonedefaultNoneHubSpot filterGroups (list of {filters: [...]} dicts). Each filter is {propertyName, operator, value}. Leave it out to search on query alone.
propertiesstr | NonedefaultNoneComma-separated property names to return.
sortslist | NonedefaultNoneList of {propertyName, direction} sort specs.
querystr | NonedefaultNoneFree-text query applied across default searchable properties.
limitintdefault10Max results per page (max 200).
afterstr | NonedefaultNonePagination cursor from previous response.
{"status": "success", "contacts": list[dict], "count": int, "total": int | None, "next_after": str | None}Only one page is returned per call and nothing here loops. count is the number of records on this page. total is the figure HubSpot reported for the whole query and is null when the response carried none, so read it with .get. next_after is the paging cursor and is null on the last page. Pass it back as the after param. limit defaults to 10 here rather than the 50 the list modes use. Each record is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. properties is sent as a comma separated string and split into the array HubSpot's search endpoint expects. A search that matches nothing returns an empty contacts list with status success rather than an error. filter_groups is optional. Leaving it out, or passing an empty list, searches on query alone, and with no query either every contact matches. HubSpot returns at most 10,000 results for one search, and limit goes up to 200.
list_companies()List companies with cursor pagination.
result = qbash.integrations.hubspot.list_companies(
limit=10,
after="…",
)
limitintdefault50Max results per page, from 1 to 100.
afterstr | NonedefaultNonePagination cursor.
propertiesstr | NonedefaultNoneComma-separated property names. Defaults to common company properties.
archivedbooldefaultFalseIf true, return only archived companies.
{"status": "success", "companies": list[dict], "count": int, "next_after": str | None}Only one page is returned per call and nothing here loops. next_after is the paging cursor HubSpot sent, and it is null when HubSpot sent none, which is the signal to stop. Pass it back as the after param to read the next page. count is the number of records on this page rather than a total. Each record is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. Leaving the properties param out asks HubSpot for name, domain, industry, city, country and createdate, so any other property has to be named to be returned.
create_company()Create a company.
result = qbash.integrations.hubspot.create_company(
name="…",
domain="…",
)
namestr | NonedefaultNoneCompany name.
domainstr | NonedefaultNonePrimary domain (e.g. acme.com).
industrystr | NonedefaultNoneIndustry property value.
propertiesdict | NonedefaultNoneMap of additional HubSpot property names to values.
{"status": "success", "company": dict, "id": str}id repeats the id of the created record, which is also on the company. company is HubSpot's response, so it carries the values HubSpot stored including ones it defaulted. The company is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. The call fails before any request is made when none of name, domain, industry or properties is supplied. This mode sends no associations, so link a company to other records through create_contact or create_deal.
search_companies()Search companies by free-text query or filterGroups syntax.
result = qbash.integrations.hubspot.search_companies(
filter_groups=[...],
properties="…",
)
filter_groupslist | NonedefaultNoneHubSpot filterGroups list. Leave it out to search on query alone.
propertiesstr | NonedefaultNoneComma-separated property names.
sortslist | NonedefaultNoneSort specs.
querystr | NonedefaultNoneFree-text query applied across default searchable properties.
limitintdefault10Max results per page (max 200).
afterstr | NonedefaultNonePagination cursor.
{"status": "success", "companies": list[dict], "count": int, "total": int | None, "next_after": str | None}Only one page is returned per call and nothing here loops. count is the number of records on this page. total is the figure HubSpot reported for the whole query and is null when the response carried none, so read it with .get. next_after is the paging cursor and is null on the last page. Pass it back as the after param. limit defaults to 10 here rather than the 50 the list modes use. Each record is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. properties is sent as a comma separated string and split into the array HubSpot's search endpoint expects. A search that matches nothing returns an empty companies list with status success rather than an error. filter_groups is optional. Leaving it out, or passing an empty list, searches on query alone, and with no query either every company matches. HubSpot returns at most 10,000 results for one search, and limit goes up to 200.
list_deals()List deals with cursor pagination.
result = qbash.integrations.hubspot.list_deals(
limit=10,
after="…",
)
limitintdefault50Max results per page, from 1 to 100.
afterstr | NonedefaultNonePagination cursor.
propertiesstr | NonedefaultNoneComma-separated property names. Defaults to common deal properties.
archivedbooldefaultFalseIf true, return only archived deals.
{"status": "success", "deals": list[dict], "count": int, "next_after": str | None}Only one page is returned per call and nothing here loops. next_after is the paging cursor HubSpot sent, and it is null when HubSpot sent none, which is the signal to stop. Pass it back as the after param to read the next page. count is the number of records on this page rather than a total. Each record is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. Leaving the properties param out asks HubSpot for dealname, amount, dealstage, pipeline, closedate, createdate and hs_lastmodifieddate, so any other property has to be named to be returned.
create_deal()Create a deal. Optionally associate with existing contacts and companies.
result = qbash.integrations.hubspot.create_deal(
dealname="…",
)
dealnamestrrequiredDeal name (required by HubSpot).
amountfloat | NonedefaultNoneDeal amount.
pipelinestr | NonedefaultNonePipeline ID (default if omitted). Use list_pipelines to discover IDs.
dealstagestr | NonedefaultNoneStage ID within the pipeline.
closedatestr | NonedefaultNoneExpected close date (ISO 8601, e.g. 2026-12-31).
propertiesdict | NonedefaultNoneMap of additional HubSpot property names to values.
contact_idslist | NonedefaultNoneContact IDs to associate with this deal.
company_idslist | NonedefaultNoneCompany IDs to associate with this deal.
{"status": "success", "deal": dict, "id": str}id repeats the id of the created record, which is also on the deal. deal is HubSpot's response, so it carries the pipeline and stage HubSpot applied when none was sent. The deal is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. dealname is the only property required here. contact_ids and company_ids attach the deal to existing records at creation time, and each accepts a list or a comma separated string. The result says nothing about whether those associations were made, so read them back from HubSpot if that matters.
update_deal()Patch properties on an existing deal by ID.
result = qbash.integrations.hubspot.update_deal(
id="…",
properties={"key": "value"},
)
idstrrequiredDeal ID.
propertiesdictrequiredMap of property names to new values.
{"status": "success", "deal": dict, "id": str}id repeats the id of the patched record. deal is the record as it stands after the patch, so it carries values HubSpot recomputed rather than only the properties that were sent. The deal is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. properties must be a non empty map or the call fails before any request is made. Associations cannot be changed through this mode.
search_deals()Search deals by free-text query or filterGroups syntax.
result = qbash.integrations.hubspot.search_deals(
filter_groups=[...],
properties="…",
)
filter_groupslist | NonedefaultNoneHubSpot filterGroups list. Leave it out to search on query alone.
propertiesstr | NonedefaultNoneComma-separated property names.
sortslist | NonedefaultNoneSort specs.
querystr | NonedefaultNoneFree-text query applied across default searchable properties.
limitintdefault10Max results per page (max 200).
afterstr | NonedefaultNonePagination cursor.
{"status": "success", "deals": list[dict], "count": int, "total": int | None, "next_after": str | None}Only one page is returned per call and nothing here loops. count is the number of records on this page. total is the figure HubSpot reported for the whole query and is null when the response carried none, so read it with .get. next_after is the paging cursor and is null on the last page. Pass it back as the after param. limit defaults to 10 here rather than the 50 the list modes use. Each record is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. properties is sent as a comma separated string and split into the array HubSpot's search endpoint expects. A search that matches nothing returns an empty deals list with status success rather than an error. filter_groups is optional. Leaving it out, or passing an empty list, searches on query alone, and with no query either every deal matches. HubSpot returns at most 10,000 results for one search, and limit goes up to 200.
create_note()Create a note engagement, optionally associated with CRM records.
result = qbash.integrations.hubspot.create_note(
body="…",
)
bodystrrequiredNote body text (becomes hs_note_body).
timestampstr | NonedefaultNoneISO 8601 timestamp for when the note occurred. Defaults to now.
owner_idstr | NonedefaultNoneHubSpot owner ID for hubspot_owner_id.
contact_idslist | NonedefaultNoneContact IDs to associate.
company_idslist | NonedefaultNoneCompany IDs to associate.
deal_idslist | NonedefaultNoneDeal IDs to associate.
{"status": "success", "note": dict, "id": str}id repeats the id of the created note, which is also on the note. note is HubSpot's response for the engagement. The note is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. The body is stored as hs_note_body and the timestamp as hs_timestamp, which falls back to the current UTC time when timestamp is not supplied. contact_ids, company_ids and deal_ids attach the note to existing records at creation time, and the result does not report whether each association was made.
create_task()Create a task engagement (to-do for a user) with associations.
result = qbash.integrations.hubspot.create_task(
subject="…",
)
subjectstrrequiredTask subject / title (becomes hs_task_subject).
bodystr | NonedefaultNoneTask description (becomes hs_task_body).
due_timestampstr | NonedefaultNoneISO 8601 due timestamp (becomes hs_timestamp). Defaults to now.
statusstr | Nonedefault"NOT_STARTED"Task status (NOT_STARTED, IN_PROGRESS, WAITING, COMPLETED, DEFERRED).
prioritystr | Nonedefault"MEDIUM"Task priority (LOW, MEDIUM, HIGH).
task_typestr | Nonedefault"TODO"Task type (TODO, CALL, EMAIL).
owner_idstr | NonedefaultNoneHubSpot owner ID assigned to this task.
contact_idslist | NonedefaultNoneContact IDs to associate.
company_idslist | NonedefaultNoneCompany IDs to associate.
deal_idslist | NonedefaultNoneDeal IDs to associate.
{"status": "success", "task": dict, "id": str}id repeats the id of the created task, which is also on the task. task is HubSpot's response for the engagement. The task is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. The subject is stored as hs_task_subject and the due date as hs_timestamp, which falls back to the current UTC time when due_timestamp is not supplied. status, priority and task_type fall back to NOT_STARTED, MEDIUM and TODO and are sent to HubSpot without being checked against the allowed values, so a typo comes back as an API error.
list_owners()List workspace users (owners) for record ownership lookups.
result = qbash.integrations.hubspot.list_owners(
limit=10,
after="…",
)
limitintdefault100Max results per page.
afterstr | NonedefaultNonePagination cursor.
emailstr | NonedefaultNoneFilter to the owner with this email.
{"status": "success", "owners": list[dict], "count": int, "next_after": str | None}Only one page is returned per call and nothing here loops. next_after is the paging cursor HubSpot sent, and it is null when HubSpot sent none, which is the signal to stop. Pass it back as the after param to read the next page. count is the number of records on this page rather than a total. Each record is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. limit defaults to 100 on this mode. An email filter uses the same shape, so an email that matches no owner gives an empty owners list with status success rather than an error. The owner id these records carry is what owner_id expects on create_note and create_task.
list_pipelines()List deal pipelines and their stage IDs (required for create_deal).
result = qbash.integrations.hubspot.list_pipelines()
{"status": "success", "pipelines": list[dict], "count": int}There is no next_after key on this mode. The service reads only the results array out of HubSpot's response and drops anything else it carried, and no paging param is sent, so there is no cursor to continue with and a second page cannot be reached from here. count is the number of pipelines returned. Each pipeline is the object as HubSpot returned it, and this integration removes every top level key whose value is null before returning it, so read its keys with .get rather than indexing. That trimming does not reach inside the record properties map, which still carries null values for properties that have no value. The stage ids that create_deal and update_deal need for dealstage come from these pipeline records.
get_ticket()Get a JIRA ticket by ID, with its comments, attachments, links and custom fields.
result = qbash.integrations.jira.get_ticket(
ticket_id="…",
)
ticket_idstrrequiredJIRA ticket ID (e.g. PROJ-123).
comment_limitintdefault20Number of newest comments to include, from 0 to 100.
custom_fieldslist[str] | NonedefaultNoneField ids (e.g. customfield_10050) or display names to include in custom_fields. When given, only those are returned.
{"status": "success", "ticket_id": str, "ticket": dict, "message": str}ticket is rebuilt from the Jira issue (REST API v3) with a fixed key set. Every key is always there and any the issue does not carry is None or an empty list, so read them with .get rather than indexing. The keys are key, id, summary, description, status, assignee, reporter, issuetype, priority, created, updated, resolution, labels, components, project, parent, links, attachments, comments, comments_total and custom_fields. description is plain text converted from Atlassian Document Format, with paragraphs on blank lines. status, issuetype, priority and resolution each hold name and id. assignee and reporter each hold displayName, emailAddress and accountId, and emailAddress is None when Jira hides it. labels is a list of strings and components is a list of component names. project holds key and name. parent holds key and summary, and is None on a ticket with no parent. Each entry of links holds type (the link type name), direction (inward or outward), description (the wording for that direction, such as is blocked by), and the key, summary and status (name and id) of the linked ticket. Each entry of attachments holds id, filename, mime_type, size in bytes, created and author. Pass an id to download_attachment to fetch the file. comments are the newest comment_limit comments, newest first, and each holds id, author, created, updated, body (plain text) and is_internal. is_internal is True for a Jira Service Management comment that is not public. comments_total counts every comment on the ticket, so it can be larger than the list, and comment_limit 0 returns no comments and still sets it. Use list_comments for the rest. custom_fields maps each custom field id to a dict with name and value, for the custom fields that have a value. Pass custom_fields to name the fields you want. Each entry is an id such as customfield_10050 or a display name, which is looked up in the site's field list. The result then holds exactly those fields, with a value of None where the ticket has none, and an entry can also name a standard field such as environment or fixVersions. A display name that several fields share returns all of them, and a name that matches no field is an error. Use list_fields to find ids. A field value is read as follows. An option becomes its text and a multi select becomes a list of text. A user becomes displayName and accountId. Rich text becomes plain text. Numbers, dates and strings stay as Jira sent them, and any other value is passed through as Jira sent it. Any other field of the issue is left out. ticket_id is echoed from the request, so use ticket.key for the canonical key.
get_tickets_by_time_span()List JIRA tickets filtered by date range or ticket number range.
result = qbash.integrations.jira.get_tickets_by_time_span(
query_type="search terms",
start_date="…",
)
query_typestrdefault"date_range"Query type: date_range or ticket_range.
start_datestr | NonedefaultNoneStart date for date_range query (YYYY-MM-DD).
end_datestr | NonedefaultNoneEnd date for date_range query (YYYY-MM-DD).
start_ticketstr | NonedefaultNoneFirst ticket ID for ticket_range query (e.g. PROJ-1).
end_ticketstr | NonedefaultNoneLast ticket ID for ticket_range query (e.g. PROJ-100).
max_resultsint | NonedefaultNoneMaximum number of tickets to return.
{"status": "success", "query_type": "date_range", "tickets": list[str], "total_found": int, "start_date": str, "end_date": str, "max_results": int, "message": str} or {"status": "success", "query_type": "ticket_range", "tickets": list[str], "total_found": int, "start_ticket": str, "end_ticket": str, "max_results": int, "message": str}tickets is a list of ticket key strings such as PROJ-123, not ticket objects, because the search asks Jira for the key field on its own. Call get_ticket on a key to read the issue. total_found counts the entries in tickets rather than the matches in Jira. query_type decides the other keys: a date_range result carries start_date and end_date, a ticket_range result carries start_ticket and end_ticket. max_results is echoed from the request, or 1000 when it was left out. A date_range search runs newest first in batches of 30 days and stops once it already holds max_results keys, so it is the oldest end of the range that goes missing, and the list can run past max_results because the check happens between batches. It runs at most 50 batches, and each stops at 3000 keys or after 51 pages of 100. A ticket_range search covers at most 1000 ticket numbers and leaves out the ones that do not exist. It sends one search per 100 numbers, 10 at most. A search that Jira answers with a 400, as it does for a missing ticket, is followed by up to 100 single lookups. Any other batch that Jira rejects is skipped and the rest of the search still returns, so tickets can be short of what the range holds. A 401 from a rejected credential, a 429 that lasts through two retries, or a failure to reach the site stops the search with an error instead, even when tickets were already found.
update_ticket()Update fields and labels on a JIRA ticket.
result = qbash.integrations.jira.update_ticket(
ticket_id="…",
)
ticket_idstrrequiredJIRA ticket ID to update (e.g. PROJ-123).
field_idstr | NonedefaultNoneJIRA field ID to update in the single-field form (e.g. labels, customfield_10001).
tag_namestr | NonedefaultNoneValue to set for field_id in the single-field form.
fieldsdict | NonedefaultNoneFields to set, by field id or display name, such as {"customfield_10050": "Gold"}. Do not combine with field_id.
add_labelslist[str] | NonedefaultNoneLabels to add. The ticket's other labels are kept.
remove_labelslist[str] | NonedefaultNoneLabels to remove. The ticket's other labels are kept.
read_onlybooldefaultFalseTrue runs every read and every check, sends nothing that writes, and returns the request it would send.
{"status": "success", "ticket_id": str, "field_id": str, "tag_name": str | list, "updated": dict, "message": str} or {"status": "success", "ticket_id": str, "updated": dict, "message": str} or {"status": "success", "read_only": True, "valid": bool, "problems": list[str], "request": dict}Two forms share this mode, and mixing them is an error. The single-field form sets field_id to tag_name, and its result keeps the keys it always had. tag_name is echoed exactly as you passed it, so for labels it can still be the comma separated string you gave, while Jira was sent the parsed list. In that form labels are parsed from a list, a JSON array or a comma separated string, a priority name becomes {"name": name}, and text for a description, environment or text area field becomes Atlassian Document Format. The newer form sets several fields at once with fields, which maps field ids or display names to values, and changes labels with add_labels and remove_labels, which keep the labels you do not name. Values in fields are converted from the field's type the way create_ticket converts them: an option name becomes {"value": name}, a list of names becomes a list of options, a cascading select takes {"value": parent, "child": child} or a list of two, a user takes an account ID, a number can be a numeric string, a date is YYYY-MM-DD, a date and time is ISO 8601 with an offset, and components, versions and priorities take names. A dict already in Jira's format is sent as it is, and None clears a field. Both forms read the ticket's edit metadata first, and a field that is not editable on this ticket with this credential is a problem, as is a value outside a field's allowed values. All problems are found before anything is sent. With read_only False they come back as one error, and with read_only True the result lists them in problems with the exact body in request. The call is PUT /rest/api/3/issue/{key}?returnIssue=true, and updated maps each field that was changed to the value Jira stored, read the way get_ticket reads it.
add_comment()Add a comment to a JIRA ticket.
result = qbash.integrations.jira.add_comment(
ticket_id="…",
comment_body={"key": "value"},
)
ticket_idstrrequiredJIRA ticket ID to comment on (e.g. PROJ-123).
comment_bodystr | dictrequiredText of the comment, or an Atlassian Document Format document as a dict.
is_internalbooldefaultFalseMark the comment as internal (visible to agents only). Works on Jira Service Management requests.
visibilitydict | NonedefaultNoneLimit the comment to a role or group, as {"type": "role" or "group", "value": name}.
read_onlybooldefaultFalseTrue runs every read and every check, sends nothing that writes, and returns the request it would send.
{"status": "success", "ticket_id": str, "comment_id": str, "comment_body": str, "is_internal": bool, "message": str} or {"status": "success", "read_only": True, "valid": bool, "problems": list[str], "request": dict}comment_id is the id Jira gave the new comment. ticket_id and comment_body are echoed from the request. comment_body is plain text, sent to Jira as Atlassian Document Format with a paragraph for each blank line and a line break for each single newline. A dict that is already an Atlassian Document Format document is sent as it is. visibility limits a comment to a role or a group, as {"type": "role", "value": "Developers"} or {"type": "group", "value": "jira-staff"}, and cannot be combined with is_internal. is_internal takes True or False, the strings true, false, 1, 0, yes and no in any case, or the integers 1 and 0, and any other value is an error with nothing sent. An internal comment goes to the Jira Service Management route, POST /rest/servicedeskapi/request/{key}/comment with public False, which takes plain text, so an Atlassian Document Format body is converted to text. A Sign in with Atlassian credential connected without Jira Service Management returns an error for is_internal True without sending anything. That route works on service requests only. On any other ticket the call is an error that says so, and no public comment is posted in its place. With read_only True the call checks that the ticket exists, or for an internal comment that it is a service request, and returns the body it would send in request.
transition_ticket()Transition a JIRA ticket to a new workflow status.
result = qbash.integrations.jira.transition_ticket(
ticket_id="…",
)
ticket_idstrrequiredJIRA ticket ID to transition (e.g. PROJ-123).
transition_namestrdefault"Done"Name of the transition to apply (e.g. In Progress, Done).
transition_idstr | NonedefaultNoneID of the transition to apply, from list_transitions. Do not combine with a transition_name.
resolution_namestr | NonedefaultNoneOptional resolution name to set when closing (e.g. Fixed, Won't Do).
fieldsdict | NonedefaultNoneScreen fields to set, by field id or display name.
commentstr | dict | NonedefaultNoneComment to add with the transition, as text or an Atlassian Document Format dict.
read_onlybooldefaultFalseTrue runs every read and every check, sends nothing that writes, and returns the request it would send.
{"status": "success", "ticket_id": str, "transition_name": str, "transition_id": str, "new_status": dict | None, "message": str, "resolution_name": str | None, "comment_added": bool | None, "comment_id": str | None} or {"status": "success", "read_only": True, "valid": bool, "problems": list[str], "request": dict}Name the transition with transition_name, matched without regard to case, or with transition_id from list_transitions. Passing a transition_id together with a transition_name other than the default is an error. transition_name comes back as Jira spells it, which can differ in case from the name you passed. transition_id is the id of the transition that ran. resolution_name is in the result only when you passed one, so read it with .get. fields sets the fields on the transition's screen, by field id or display name, converted the way create_ticket converts values. A field outside the screen is a problem. comment is text or an Atlassian Document Format dict. On a transition with a screen it is sent with the transition. On one without a screen, where Jira would drop it, it is posted as an ordinary comment right after the transition, and with read_only True the request then holds transition_request and comment_request, the two bodies in order. comment_added is in the result only when you passed a comment. It is False when the transition went through and the comment did not, and message says why. comment_id is set when the comment was posted on its own. Before anything is sent the call checks that the ticket offers the transition and that every required screen field without a default has a value, from this call or already on the ticket. All problems are found together. With read_only False they come back as one error, and with read_only True the result lists them in problems with the exact body in request. After the POST the ticket's status is read back into new_status, which holds name, id and category (new, indeterminate or done). It is None when that read fails, and the transition has happened either way.
list_comments()List the comments on a JIRA ticket, one page at a time.
result = qbash.integrations.jira.list_comments(
ticket_id="…",
)
ticket_idstrrequiredJIRA ticket ID (e.g. PROJ-123).
orderstrdefault"newest"newest or oldest, by creation time.
limitintdefault100Comments per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
{"status": "success", "ticket_id": str, "comments": list[dict], "total": int, "count": int, "next_cursor": str | None, "message": str}Reads GET /issue/{key}/comment from REST API v3. Each comment holds id, author (displayName, emailAddress and accountId), created, updated, body and is_internal, the same as the comments of get_ticket. body is plain text converted from Atlassian Document Format. is_internal is True for a Jira Service Management comment that is not public. total counts every comment on the ticket and count is the size of this page. order is newest or oldest, by creation time. Pass next_cursor back as cursor to read the next page, and it is None on the last page. A cursor works only with the ticket_id and order that produced it.
download_attachment()Download a JIRA attachment and return a file reference.
result = qbash.integrations.jira.download_attachment(
attachment_id="…",
)
attachment_idstrrequiredID of the attachment, as in the id of an entry in ticket.attachments from get_ticket.
{"type": "aisle_input_file", "aisle_input_file": dict} or {"type": "aisle_input_image", "aisle_input_image": dict}Reads the attachment's file name, type and size from GET /attachment/{id}, then streams the bytes from GET /attachment/content/{id} into qbash storage. It returns a file reference you can pass to another integration or to qbash.output. The key holding the reference matches the "type" value, so images arrive under "aisle_input_image" and everything else under "aisle_input_file". attachment_id is the id of an entry in ticket.attachments from get_ticket. A file over 5 GB is an error, and so is a download that could not be stored.
list_fields()List the fields on the JIRA site, with their ids and types.
result = qbash.integrations.jira.list_fields(
query="search terms",
custom_only=True,
)
querystr | NonedefaultNoneKeep fields whose name or id contains this text, ignoring case.
custom_onlybooldefaultFalseReturn only custom fields.
{"status": "success", "fields": list[dict], "count": int, "message": str}Reads GET /rest/api/3/field, which answers with every field of the site in one response, so there is no paging. Each field holds id, name, custom (bool), type (the schema type, such as string or array), items (the type of a list's entries, or None), custom_type (the schema's custom type, such as com.atlassian.jira.plugin.system.customfieldtypes:select, or None) and clause_names (the names JQL accepts for the field). Use an id in the custom_fields param of get_ticket or the fields param of search_tickets. query keeps the fields whose name or id contains the text, ignoring case, and custom_only keeps the custom fields.
search_tickets()Search JIRA tickets by JQL, or by text, project and recent updates.
result = qbash.integrations.jira.search_tickets(
jql="…",
text="…",
)
jqlstr | NonedefaultNoneA JQL query. Use it alone, without text, project or updated_within_days.
textstr | NonedefaultNoneWords to find in the text of tickets, matched after stemming.
projectstr | NonedefaultNoneProject key (e.g. PROJ) or numeric project ID to search in.
updated_within_daysint | NonedefaultNoneOnly tickets updated within this many days, from 1 to 3650.
fieldslist[str] | NonedefaultNoneExtra field ids or display names to return under custom_fields.
limitintdefault50Tickets per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
{"status": "success", "tickets": list[dict], "count": int, "next_cursor": str | None, "jql": str, "message": str}Reads POST /rest/api/3/search/jql. Pass jql, or build the query from text, project and updated_within_days, which are joined with AND. Passing jql with any of the others is an error. Jira refuses a query with no restriction in it, so give jql or at least one of the others. A raw jql is sent as written, and when Jira rejects it the error carries Jira's message. Results come newest updated first unless the jql has its own ORDER BY, and jql in the result is the query that ran. text finds tickets whose summary, description, comments or other text fields contain the words, using Jira's stemming. A ticket that describes the same problem in other words is not found, and text is cut at 300 characters. To look for duplicates or related tickets, search with a few key terms and then rank the candidates yourself, for example by passing their summaries and descriptions to a prompt. Each ticket holds key, id, summary, status, issuetype and priority (each name and id), assignee (displayName and accountId, or None), created, updated, labels, project (key and name) and custom_fields. tickets here are dicts, unlike the key strings of get_tickets_by_time_span. fields adds fields to custom_fields, as ids or display names, in the form get_ticket uses, with a value of None where a ticket has none. Jira sends no total, so page with next_cursor: pass it back as cursor with the same query, and it is None on the last page. A duplicate guard searches for a source label before creating a ticket, for example jql='labels = "src-zendesk-48213"', and creates one only when count is 0. Jira's search index can lag behind a new ticket, so a guard that runs right after a create can miss it. To watch for status changes, search with updated_within_days or a JQL such as updated >= -15m, then read the changes with get_ticket_history.
list_projects()List the JIRA projects the credential can see.
result = qbash.integrations.jira.list_projects(
query="search terms",
creatable_only=True,
)
querystr | NonedefaultNoneKeep projects whose key or name matches this text.
creatable_onlybooldefaultFalseOnly projects where the user can create tickets.
limitintdefault50Projects per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
{"status": "success", "projects": list[dict], "count": int, "next_cursor": str | None, "message": str}Reads GET /rest/api/3/project/search. Each project holds id, key, name, project_type (such as software, business or service_desk) and style, which is company-managed or team-managed, or None when Jira does not say. Use key as the project param of the other modes. query matches the project key or name. creatable_only keeps the projects where the user can create tickets. Pass next_cursor back as cursor to read the next page, and it is None on the last page.
list_issue_types()List the issue types of a JIRA project.
result = qbash.integrations.jira.list_issue_types(
project="…",
)
projectstrrequiredProject key (e.g. PROJ) or numeric project ID.
{"status": "success", "project": str, "issue_types": list[dict], "count": int, "message": str}Reads GET /rest/api/3/issue/createmeta/{project}/issuetypes and returns every page in one result. Each issue type holds id, name, subtask (bool) and description. Jira lists the issue types the user can create in the project. Pass an id or a name to get_create_fields.
get_create_fields()List the fields a new JIRA ticket takes for a project and issue type.
result = qbash.integrations.jira.get_create_fields(
project="…",
issue_type="…",
)
projectstrrequiredProject key (e.g. PROJ) or numeric project ID.
issue_typestrrequiredIssue type name (e.g. Bug) or numeric ID, from list_issue_types.
{"status": "success", "project": str, "issue_type": dict, "fields": list[dict], "count": int, "message": str}Reads GET /rest/api/3/issue/createmeta/{project}/issuetypes/{id} and returns every page. issue_type is an id or a name, and a name is looked up in the project's issue types without regard to case. The issue_type of the result holds id and name, and name is None when you passed an id. Each field holds field_id, name, required, has_default, type (the schema type), items (the type of a list's entries), custom_type, allowed_values and operations. allowed_values is a list of dicts holding id and either value (options) or name (versions, components, priorities and similar), with children for the options under a cascading select. It is None when Jira lists no allowed values, as for text, number and user fields. operations lists what Jira lets you do to the field, such as set. A field with required True and has_default False has to be supplied when a ticket is created.
get_edit_fields()List the fields the user can edit on a JIRA ticket.
result = qbash.integrations.jira.get_edit_fields(
ticket_id="…",
)
ticket_idstrrequiredJIRA ticket ID (e.g. PROJ-123).
{"status": "success", "ticket_id": str, "fields": list[dict], "count": int, "message": str}Reads GET /rest/api/3/issue/{key}/editmeta. Each field holds the same keys as in get_create_fields, sorted by field_id. has_default is False unless Jira reports a default value. Only fields the user may edit on this ticket are listed, so a field missing from the list cannot be changed with this credential.
list_transitions()List the workflow transitions a JIRA ticket can take now.
result = qbash.integrations.jira.list_transitions(
ticket_id="…",
)
ticket_idstrrequiredJIRA ticket ID (e.g. PROJ-123).
{"status": "success", "ticket_id": str, "transitions": list[dict], "count": int, "message": str}Reads GET /rest/api/3/issue/{key}/transitions with the screen fields expanded. Jira lists the transitions the user can run from the ticket's current status. Each holds id, name, to, has_screen, is_available, is_conditional and fields. to holds id, name and category, where category is the key of the status category, such as new, indeterminate or done. has_screen is True when the transition asks for fields, and is_conditional is True when a workflow condition decides whether it is offered. fields lists only the fields on the transition's screen, each with field_id, name, required, type and allowed_values. Pass a name to transition_ticket.
list_link_types()List the issue link types of the JIRA site.
result = qbash.integrations.jira.list_link_types()
{"status": "success", "link_types": list[dict], "count": int, "message": str}Reads GET /rest/api/3/issueLinkType. Each link type holds id, name, inward and outward. inward and outward are the wording shown on each end of a link, such as is blocked by and blocks. get_ticket reports each link of a ticket with the same wording in description.
get_ticket_history()Read the field changes on JIRA tickets. Only status changes unless field_ids names other fields or "all".
result = qbash.integrations.jira.get_ticket_history(
ticket_ids=[...],
)
ticket_idslist[str]requiredIssue keys or numeric issue IDs, from 1 to 1000.
field_idslist[str]default["status"]Field ids to read, at most 10. Pass "all" or an empty list for every field.
limitintdefault100Page size sent to Jira, from 1 to 1000.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
{"status": "success", "changes": list[dict], "count": int, "next_cursor": str | None, "message": str}Reads POST /rest/api/3/changelog/bulkfetch. changes is flat, with one entry per changed field, and each holds ticket_id, changed_at, author (displayName and accountId), field, field_id, from and to. from and to are the text Jira keeps for each value, such as the status name, or the raw value when there is no text. Jira answers with numeric issue ids. ticket_id is the key when the connector can tell it: it is known for a single ticket, and for several keys the connector looks up the ids on the page with one extra search per 100 ids. An id it cannot look up stays as the id. field_ids limits the fields read, with status as the default and at most 10. Pass "all" or an empty list for every field. limit is the page size sent to Jira, and Jira decides what a page counts, so a page can hold more or fewer changes than limit. Pass next_cursor back as cursor with the same ticket_ids and field_ids, and it is None on the last page. To detect status changes by polling, run search_tickets with updated_within_days or a JQL such as updated >= -15m, pass the keys it returns here, and keep the changes whose changed_at falls inside the window.
create_ticket()Create a JIRA ticket in a project, checked against the project's create screen.
result = qbash.integrations.jira.create_ticket(
project="…",
issue_type="…",
summary="…",
)
projectstrrequiredProject key (e.g. PROJ) or numeric project ID.
issue_typestrrequiredIssue type name (e.g. Bug) or numeric ID, from list_issue_types.
summarystrrequiredSummary of the ticket, up to 255 characters.
descriptionstr | dict | NonedefaultNoneDescription as text, or an Atlassian Document Format document as a dict.
labelslist[str] | NonedefaultNoneLabels to set. A label cannot contain a space.
prioritystr | NonedefaultNonePriority name (e.g. High).
assignee_account_idstr | NonedefaultNoneAccount ID of the assignee.
parentstr | NonedefaultNoneIssue key of the parent, for a subtask or a child of an epic.
fieldsdict | NonedefaultNoneOther fields, by field id or display name. See get_create_fields for what the project takes.
read_onlybooldefaultFalseTrue runs every read and every check, sends nothing that writes, and returns the request it would send.
{"status": "success", "ticket_id": str, "ticket": dict, "url": str | None, "message": str} or {"status": "success", "read_only": True, "valid": bool, "problems": list[str], "request": dict}Reads the create screen of the project and issue type, as get_create_fields does, checks that every required field without a default is supplied, converts each value to what Jira expects, and then calls POST /rest/api/3/issue. summary is required. description is text, converted to Atlassian Document Format with a paragraph for each blank line, or a dict that is already a document. labels is a list of strings without spaces, priority is a name, assignee_account_id is an account ID, and parent is an issue key. fields holds any other field by id or display name, such as {"customfield_10050": "Gold", "Fix versions": ["1.2"]}. Values are converted from the field's type. An option name becomes {"value": name}, a list of names becomes a list of options, a cascading select takes {"value": parent, "child": child} or a list of two, a user takes an account ID, a number can be a numeric string, a date is YYYY-MM-DD, a date and time is ISO 8601 with an offset, components, versions and priorities take names, and a sprint takes its integer ID. A dict already in Jira's format is sent as it is. A value outside a field's allowed values is a problem that lists the allowed ones, and so is a field that is not on the create screen. Jira fills in reporter, so it is never required here. All problems are found before anything is sent. With read_only False they come back as one error, and with read_only True the result lists them in problems with the exact body in request, so a task can draft a ticket, have it approved and then file it. On success the ticket is read back as get_ticket reads it, with comment_limit 0, and url links to it. url is None only for a Sign in with Atlassian credential saved without its site address. Jira has no idempotency key and a create is never retried, so a timeout can still leave a created ticket behind. To avoid duplicates, search for a source label before creating, for example search_tickets with jql='labels = "src-zendesk-48213"', and pass the same label in labels. Jira's search index can lag behind a new ticket, so a check run right after a create can miss it. To link the ticket back to the support record, call add_remote_link with the new key. Field errors from Jira are listed in the error, one entry for each field.
link_tickets()Link two JIRA tickets, such as one that duplicates or blocks another.
result = qbash.integrations.jira.link_tickets(
from_ticket="…",
to_ticket="…",
link_type="…",
)
from_ticketstrrequiredKey or numeric ID of the ticket the sentence starts with.
to_ticketstrrequiredKey or numeric ID of the ticket the sentence ends with.
link_typestrrequiredLink type name, outward wording or inward wording (e.g. Duplicate, duplicates, is duplicated by).
commentstr | NonedefaultNoneComment to add with the link.
read_onlybooldefaultFalseTrue runs every read and every check, sends nothing that writes, and returns the request it would send.
{"status": "success", "from_ticket": str, "to_ticket": str, "link": dict | None, "message": str} or {"status": "success", "read_only": True, "valid": bool, "problems": list[str], "request": dict}Calls POST /rest/api/3/issueLink. link_type is a link type name such as Duplicate, its outward wording such as duplicates, or its inward wording such as is duplicated by, matched without regard to case against list_link_types. The link reads from_ticket, then the wording, then to_ticket. Jira names the two ends the other way round from the English, and the connector handles that: with a name or an outward wording from_ticket is sent as the inwardIssue, and with an inward wording the two tickets swap. comment is text that Jira adds as a comment with the link. A link type that matches nothing is a problem that lists the available ones. Jira answers 201 with no id, so the new link is read back from from_ticket and returned in the shape of get_ticket's links, or None when it is not visible yet. The read-back also checks the direction: if the link reads the other way from from_ticket, the call is an error that names both tickets and the wording Jira shows, and the link has to be checked and removed. A 404 can mean a ticket does not exist or that issue linking is turned off on the site, and the error says so. With read_only True the call checks the link type and that both tickets exist, and returns the body it would send in request.
add_remote_link()Add a link to a page outside JIRA, such as a support record, to a ticket.
result = qbash.integrations.jira.add_remote_link(
ticket_id="…",
url="https://…",
title="…",
)
ticket_idstrrequiredJIRA ticket ID (e.g. PROJ-123).
urlstrrequiredhttp or https URL of the page to link to.
titlestrrequiredTitle shown for the link.
summarystr | NonedefaultNoneShort text shown under the title.
global_idstr | NonedefaultNoneStable ID of the linked record, up to 255 characters. A repeat call with the same ID updates the link.
relationshipstr | NonedefaultNoneHow the ticket relates to the page (e.g. causes, mentioned in).
application_namestr | NonedefaultNoneName of the application the page belongs to (e.g. Zendesk).
icon_urlstr | NonedefaultNonehttp or https URL of a 16 by 16 icon.
read_onlybooldefaultFalseTrue runs every read and every check, sends nothing that writes, and returns the request it would send.
{"status": "success", "ticket_id": str, "link_id": str, "created": bool, "global_id": str | None, "message": str} or {"status": "success", "read_only": True, "valid": bool, "problems": list[str], "request": dict}Calls POST /rest/api/3/issue/{key}/remotelink with the url, title and any of summary, relationship, application_name and icon_url. Jira upserts by global_id and sets what is left out to null, so every call sends the whole object. Pass the support record's stable id as global_id, for example zendesk:ticket:48213, up to 255 characters. A repeat call with the same global_id updates the link instead of adding a second one. created is True when Jira added a link (HTTP 201) and False when it updated one (HTTP 200). link_id is the id Jira gave the link. Jira cannot search tickets by the global_id of a remote link, because the JQL function for it matches Confluence links only. The duplicate guard for create_ticket therefore stays a source label that search_tickets can find. With read_only True the call checks that the ticket exists and returns the body it would send in request.
get_viewer()Get the authenticated Linear user.
result = qbash.integrations.linear.get_viewer()
{"status": "success", "viewer": dict}Returns the selected Linear fields with snake_case keys. Nullable fields remain None. Issues include team, state, assignee, project, cycle and label_ids. Missing or inaccessible resources return an error.
list_teams()List one page of accessible Linear teams.
result = qbash.integrations.linear.list_teams(
limit=10,
cursor="…",
)
limitintdefault50Results per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
include_archivedbooldefaultFalseInclude archived resources. Defaults to False.
{"status": "success", "teams": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Returns one page of selected fields with snake_case keys. Nullable fields remain None. count is this page's size. Pass next_cursor back as cursor until it is None. Archived resources are excluded unless include_archived is True. Issues include label_ids, team, state, assignee, project and cycle. Results reflect the key's permissions and team restrictions.
list_users()List one page of active workspace users.
result = qbash.integrations.linear.list_users(
limit=10,
cursor="…",
)
limitintdefault50Results per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
include_archivedbooldefaultFalseInclude archived resources. Defaults to False.
{"status": "success", "users": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Returns one page of selected fields with snake_case keys. Nullable fields remain None. count is this page's size. Pass next_cursor back as cursor until it is None. Archived resources are excluded unless include_archived is True. Issues include label_ids, team, state, assignee, project and cycle. Results reflect the key's permissions and team restrictions.
list_projects()List one page of projects, optionally restricted to a team.
result = qbash.integrations.linear.list_projects(
team_id="…",
limit=10,
)
team_idstr | NonedefaultNoneTeam UUID from list_teams. Omit to read accessible teams.
limitintdefault50Results per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
include_archivedbooldefaultFalseInclude archived resources. Defaults to False.
{"status": "success", "projects": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Returns one page of selected fields with snake_case keys. Nullable fields remain None. count is this page's size. Pass next_cursor back as cursor until it is None. Archived resources are excluded unless include_archived is True. Issues include label_ids, team, state, assignee, project and cycle. Results reflect the key's permissions and team restrictions.
list_cycles()List one page of cycles, optionally restricted to a team.
result = qbash.integrations.linear.list_cycles(
team_id="…",
limit=10,
)
team_idstr | NonedefaultNoneTeam UUID from list_teams. Omit to read accessible teams.
limitintdefault50Results per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
include_archivedbooldefaultFalseInclude archived resources. Defaults to False.
{"status": "success", "cycles": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Returns one page of selected fields with snake_case keys. Nullable fields remain None. count is this page's size. Pass next_cursor back as cursor until it is None. Archived resources are excluded unless include_archived is True. Issues include label_ids, team, state, assignee, project and cycle. Results reflect the key's permissions and team restrictions.
list_workflow_states()List one page of issue workflow states, optionally restricted to a team.
result = qbash.integrations.linear.list_workflow_states(
team_id="…",
limit=10,
)
team_idstr | NonedefaultNoneTeam UUID from list_teams. Omit to read accessible teams.
limitintdefault50Results per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
include_archivedbooldefaultFalseInclude archived resources. Defaults to False.
{"status": "success", "workflow_states": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Returns one page of selected fields with snake_case keys. Nullable fields remain None. count is this page's size. Pass next_cursor back as cursor until it is None. Archived resources are excluded unless include_archived is True. Issues include label_ids, team, state, assignee, project and cycle. Results reflect the key's permissions and team restrictions.
list_labels()List one page of issue labels. A team filter also includes workspace labels.
result = qbash.integrations.linear.list_labels(
team_id="…",
limit=10,
)
team_idstr | NonedefaultNoneTeam UUID from list_teams. Omit to read accessible teams.
limitintdefault50Results per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
include_archivedbooldefaultFalseInclude archived resources. Defaults to False.
{"status": "success", "labels": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Returns one page of selected fields with snake_case keys. Nullable fields remain None. count is this page's size. Pass next_cursor back as cursor until it is None. Archived resources are excluded unless include_archived is True. Issues include label_ids, team, state, assignee, project and cycle. Results reflect the key's permissions and team restrictions.
list_issues()List one page of issues with server-side filters, most recently updated first.
result = qbash.integrations.linear.list_issues(
team_id="…",
project_id="…",
)
team_idstr | NonedefaultNoneTeam UUID from list_teams. Omit to read accessible teams.
project_idstr | NonedefaultNoneProject UUID from list_projects.
assignee_idstr | NonedefaultNoneAssignee UUID from list_users or get_viewer.
state_typestr | NonedefaultNoneWorkflow category: triage, backlog, unstarted, started, completed or canceled.
priorityint | NonedefaultNonePriority: 0 none, 1 urgent, 2 high, 3 normal, 4 low.
updated_sincestr | NonedefaultNoneInclusive ISO 8601 timestamp with timezone, for example 2026-10-01T00:00:00Z.
querystr | NonedefaultNoneCase-insensitive substring of the issue title. Does not search descriptions or comments.
limitintdefault50Results per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
include_archivedbooldefaultFalseInclude archived resources. Defaults to False.
{"status": "success", "issues": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Returns one page of selected fields with snake_case keys. Nullable fields remain None. count is this page's size. Pass next_cursor back as cursor until it is None. Archived resources are excluded unless include_archived is True. Issues include label_ids, team, state, assignee, project and cycle. Results reflect the key's permissions and team restrictions.
get_issue()Get one issue by UUID or identifier such as ENG-123.
result = qbash.integrations.linear.get_issue(
issue_id="…",
)
issue_idstrrequiredIssue UUID or identifier such as ENG-123.
{"status": "success", "issue": dict}Returns the selected Linear fields with snake_case keys. Nullable fields remain None. Issues include team, state, assignee, project, cycle and label_ids. Missing or inaccessible resources return an error.
list_comments()List one page of comments on an issue.
result = qbash.integrations.linear.list_comments(
issue_id="…",
)
issue_idstrrequiredIssue UUID or identifier such as ENG-123.
limitintdefault50Results per page, from 1 to 100.
cursorstr | NonedefaultNonePass next_cursor from the previous page.
include_archivedbooldefaultFalseInclude archived resources. Defaults to False.
{"status": "success", "comments": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Returns one page of selected fields with snake_case keys. Nullable fields remain None. count is this page's size. Pass next_cursor back as cursor until it is None. Archived resources are excluded unless include_archived is True. Issues include label_ids, team, state, assignee, project and cycle. Results reflect the key's permissions and team restrictions.
create_issue()Create an issue in a team. Requires Create issues or Write permission.
result = qbash.integrations.linear.create_issue(
team_id="…",
title="…",
)
team_idstrrequiredTeam UUID from list_teams.
titlestrrequiredIssue title.
descriptionstr | NonedefaultNoneIssue description in Markdown.
assignee_idstr | NonedefaultNoneUser UUID to assign the issue to.
project_idstr | NonedefaultNoneProject UUID to attach the issue to.
cycle_idstr | NonedefaultNoneCycle UUID to attach the issue to.
state_idstr | NonedefaultNoneWorkflow state UUID from list_workflow_states for this team.
priorityint | NonedefaultNonePriority: 0 none, 1 urgent, 2 high, 3 normal, 4 low.
estimateint | NonedefaultNoneNonnegative estimate using the team's estimation scale.
due_datestr | NonedefaultNoneDue date as YYYY-MM-DD.
label_idslist[str] | NonedefaultNoneLabel UUIDs. Updates replace the complete label list. Pass [] to remove all labels.
{"status": "success", "issue": dict}Returns the created or updated issue with snake_case keys only after Linear confirms success. Writes are not retried. Check Linear before retrying an uncertain outcome. Omitted or None parameters do not change fields. Use update_issue clear_fields to remove nullable values.
update_issue()Update only supplied issue fields. Requires Write permission. label_ids replaces all labels.
result = qbash.integrations.linear.update_issue(
issue_id="…",
)
issue_idstrrequiredIssue UUID or identifier such as ENG-123.
titlestr | NonedefaultNoneReplacement issue title.
descriptionstr | NonedefaultNoneIssue description in Markdown.
assignee_idstr | NonedefaultNoneUser UUID to assign the issue to.
project_idstr | NonedefaultNoneProject UUID to attach the issue to.
cycle_idstr | NonedefaultNoneCycle UUID to attach the issue to.
state_idstr | NonedefaultNoneWorkflow state UUID from list_workflow_states for this team.
priorityint | NonedefaultNonePriority: 0 none, 1 urgent, 2 high, 3 normal, 4 low.
estimateint | NonedefaultNoneNonnegative estimate using the team's estimation scale.
due_datestr | NonedefaultNoneDue date as YYYY-MM-DD.
label_idslist[str] | NonedefaultNoneLabel UUIDs. Updates replace the complete label list. Pass [] to remove all labels.
clear_fieldslist[str]default[]Fields to clear: assignee_id, project_id, cycle_id, due_date, estimate or description. Cannot also supply a value for the same field.
{"status": "success", "issue": dict}Returns the created or updated issue with snake_case keys only after Linear confirms success. Writes are not retried. Check Linear before retrying an uncertain outcome. Omitted or None parameters do not change fields. Use update_issue clear_fields to remove nullable values.
create_comment()Add a Markdown comment to an issue. Requires Create comments or Write permission.
result = qbash.integrations.linear.create_comment(
issue_id="…",
body="…",
)
issue_idstrrequiredIssue UUID or identifier such as ENG-123.
bodystrrequiredComment text in Markdown.
{"status": "success", "comment": dict}Returns the created or updated comment with snake_case keys only after Linear confirms success. Writes are not retried. Check Linear before retrying an uncertain outcome. Omitted or None parameters do not change fields. Use update_issue clear_fields to remove nullable values.
get_profile()Get the authenticated user's LinkedIn profile.
result = qbash.integrations.linkedin.get_profile()
{"status": str, "profile": {"id": str, "name": str, "headline": str | None, "vanity_name": str | None}}id is the member URN, urn:li:person:... Reads /rest/me under r_basicprofile, which returns no email address, picture URL or locale.
get_organization()Get a LinkedIn company/organization page by numeric ID or vanity name.
result = qbash.integrations.linkedin.get_organization(
organization_id="…",
vanity_name="…",
)
organization_idstr | NonedefaultNoneNumeric LinkedIn organization ID (e.g. "1234567"). Provide this or vanity_name.
vanity_namestr | NonedefaultNoneLinkedIn vanity name / URL slug (e.g. "acme" from linkedin.com/company/acme). Provide this or organization_id.
{"status": str, "organization": {"id": int, "name": str, "description": str | None, "website": str | None, "vanity_name": str | None, "logo": str | None}}description, website and logo are admin-gated and come back None when the credential lacks rw_organization_admin on that organization. Prefix id with urn:li:organization: to build the URN the other modes take.
list_posts()List recent posts by a person or organization.
result = qbash.integrations.linkedin.list_posts(
author_urn="…",
count=10,
)
author_urnstr | NonedefaultNoneFull URN of the author (e.g. "urn:li:person:abc123" or "urn:li:organization:1234567"). Defaults to the authenticated user's person URN from the credential.
countintdefault10Maximum number of posts to return (max 100).
startintdefault0Pagination offset.
{"status": str, "posts": list[{"id": str, "commentary": str | None, "created_at": int, "visibility": str, "lifecycle_state": str}], "count": int}Organization authors only: a person author_urn fails before calling LinkedIn because r_member_social is closed. created_at is epoch milliseconds.
upload_image()Upload an image to LinkedIn and get back an image asset URN.
result = qbash.integrations.linkedin.upload_image(
file=qbash.inputs["file"],
file_url="https://…",
)
filefile | str | NonedefaultNoneA file from qbash.inputs or another integration's result, or a base64-encoded image string. Provide this or file_url. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
file_urlstr | NonedefaultNonePublic URL to download the image from. Provide this or file.
owner_urnstr | NonedefaultNoneURN that owns the asset. Defaults to the authenticated user's person URN. Pass an org URN to upload for a company page.
{"status": str, "image_urn": str, "owner_urn": str, "filename": str, "content_type": str, "byte_size": int}Media is resolved from an aisle_input_file reference, inline base64 or data URI, or file_url, and refused before upload when it exceeds 50 MB. Reuse image_urn as media_urn, in image_urns, or as article_thumbnail_urn in create_post.
upload_video()Upload a video to LinkedIn and get back a video asset URN.
result = qbash.integrations.linkedin.upload_video(
file=qbash.inputs["file"],
file_url="https://…",
)
filefile | str | NonedefaultNoneA file from qbash.inputs or another integration's result (up to 5 GB), or a base64-encoded MP4 string (up to 500 MB). Provide this or file_url. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
file_urlstr | NonedefaultNonePublic URL to download the video from, up to 5 GB. Provide this or file.
owner_urnstr | NonedefaultNoneURN that owns the asset. Defaults to the authenticated user's person URN. Pass an org URN to upload for a company page.
wait_for_processingbooldefaultTruePoll until LinkedIn finishes processing the video. A post cannot reference a video that is still processing.
max_wait_secondsintdefault60How long to wait for processing. On timeout the mode still succeeds and returns the URN plus the last-seen status.
{"status": str, "video_urn": str, "owner_urn": str, "processing_status": str, "ready": bool, "part_count": int, "filename": str, "content_type": str, "byte_size": int}Video runs 3 seconds to 30 minutes. LinkedIn's docs list both 500 MB and 5 GB as the size limit. A stored file or a file_url video may be up to 5 GB and is streamed in parts. A file_url is stored as a qbash file first. Base64 video is refused over 500 MB. A long upload needs a run whose expected runtime is over 15 minutes. After the parts go up, LinkedIn processes the asset. By default the mode polls every 3 seconds for up to max_wait_seconds, clamped to 300. ready is true only when processing_status is AVAILABLE. When the wait elapses the mode still succeeds with the last-seen status, so check ready before create_post. A member credential may not be allowed to read the status, and then processing_status is UNKNOWN. PROCESSING_FAILED is returned as an error. With wait_for_processing false, processing_status is always PROCESSING.
upload_document()Upload a document (e.g. PDF) to LinkedIn and get back a document asset URN.
result = qbash.integrations.linkedin.upload_document(
file=qbash.inputs["file"],
file_url="https://…",
)
filefile | str | NonedefaultNoneA file from qbash.inputs or another integration's result, or a base64-encoded document string. Provide this or file_url. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
file_urlstr | NonedefaultNonePublic URL to download the document from. Provide this or file.
owner_urnstr | NonedefaultNoneURN that owns the asset. Defaults to the authenticated user's person URN. Pass an org URN to upload for a company page.
{"status": str, "document_urn": str, "owner_urn": str, "filename": str, "content_type": str, "byte_size": int}Documents over 100 MB are refused before upload. Reuse document_urn as media_urn in create_post, with media_type document.
create_post()Publish a post on LinkedIn - text, media, or an article card.
result = qbash.integrations.linkedin.create_post(
text="…",
)
textstrrequiredPost content.
visibilitystrdefault"PUBLIC"Visibility: "PUBLIC" or "CONNECTIONS".
author_urnstr | NonedefaultNoneURN of the author. Defaults to the authenticated user's person URN. Pass an org URN to post as an organization.
media_urnstr | NonedefaultNoneAsset URN from upload_image or upload_video (e.g. "urn:li:image:..." or "urn:li:video:..."). Cannot be combined with the article_* params.
media_titlestr | NonedefaultNoneTitle shown with the media (mainly used for video).
media_typestr | NonedefaultNone"image", "video" or "document". Inferred from media_urn when omitted; validated against it when supplied.
media_alt_textstr | NonedefaultNoneAlt text for screen readers. Images only.
article_urlstr | NonedefaultNoneURL to share as a rich article card. Required if any other article_* param is set. Cannot be combined with the media_* params.
article_titlestr | NonedefaultNoneHeadline on the article card. LinkedIn does not scrape the URL, so without this the card renders without a title.
article_descriptionstr | NonedefaultNoneDescription text on the article card.
article_thumbnail_urnstr | NonedefaultNoneImage URN from upload_image to use as the card thumbnail (e.g. "urn:li:image:...").
reshare_urnstr | NonedefaultNoneURN of an existing post to reshare (e.g. "urn:li:share:123" or "urn:li:ugcPost:123"). Cannot be combined with media_urn or the article_* params.
image_urnslist[str] | NonedefaultNone2-20 image URNs from upload_image, rendered as a multi-image carousel post. Cannot be combined with media_urn, article_* params, reshare_urn, or the poll params.
multi_image_alt_textstr | NonedefaultNoneAlt text covering the whole image_urns carousel (up to 4086 characters). LinkedIn also supports per-image alt text, not exposed here for simplicity.
poll_questionstr | NonedefaultNonePoll question (max 140 characters). Setting this creates a poll instead of a normal post - polls cannot be edited after creation and are organic-only (no sponsored polls via this API). Cannot be combined with media_urn, article_* params, reshare_urn, or image_urns.
poll_optionslist[str] | NonedefaultNone2-4 poll answer options, each max 30 characters. Required with poll_question.
poll_durationstr | NonedefaultNoneOne of "ONE_DAY", "THREE_DAYS", "SEVEN_DAYS", "FOURTEEN_DAYS". Required with poll_question.
target_geo_urnslist[str] | NonedefaultNoneRestrict distribution to these geography URNs. Facets OR within themselves and AND across different target_* facets; LinkedIn requires the resulting audience to be at least 300 members. Works alongside any content type.
target_senioritieslist[str] | NonedefaultNoneRestrict distribution to these seniority URNs. See target_geo_urns for facet semantics.
target_industrieslist[str] | NonedefaultNoneRestrict distribution to these industry URNs. See target_geo_urns for facet semantics.
target_job_functionslist[str] | NonedefaultNoneRestrict distribution to these job function URNs. See target_geo_urns for facet semantics.
target_organizationslist[str] | NonedefaultNoneRestrict distribution to followers of these organization URNs. See target_geo_urns for facet semantics.
target_staff_count_rangeslist[str] | NonedefaultNoneRestrict distribution to these staff count range URNs. See target_geo_urns for facet semantics.
{"status": str, "post_urn": str, "message": str}Keep post_urn. LinkedIn will not list a member's own posts back, so it is the only handle for update_post, delete_post, comments, reactions and member analytics.
delete_post()Delete a LinkedIn post.
result = qbash.integrations.linkedin.delete_post(
post_urn="…",
)
post_urnstrrequiredURN of the post to delete (e.g. "urn:li:share:123").
{"status": str, "post_urn": str, "message": str}update_post()Update an existing LinkedIn post's text or call-to-action.
result = qbash.integrations.linkedin.update_post(
post_urn="…",
)
post_urnstrrequiredURN of the post to update (e.g. "urn:li:share:123").
textstr | NonedefaultNoneNew post text (maps to LinkedIn's commentary field).
call_to_action_labelstr | NonedefaultNoneNew call-to-action label shown on the post.
landing_page_urlstr | NonedefaultNoneNew landing page URL for the call-to-action.
{"status": str, "post_urn": str, "updated_fields": list[str]}At least one of text, call_to_action_label or landing_page_url is required. updated_fields lists the LinkedIn field names patched: commentary, contentCallToActionLabel, contentLandingPage.
get_post_analytics()Get engagement analytics for an organization's posts.
result = qbash.integrations.linkedin.get_post_analytics(
organization_urn="…",
)
organization_urnstrrequiredOrganization URN (e.g. "urn:li:organization:1234567").
countintdefault10Maximum number of results to return.
share_urnslist[str] | NonedefaultNoneShare URNs (e.g. "urn:li:share:123") to get per-post stats for. Without this the API returns one lifetime org-wide aggregate row.
ugc_post_urnslist[str] | NonedefaultNoneUGC post URNs (e.g. "urn:li:ugcPost:123") to get per-post stats for.
{"status": str, "analytics": list[{"organizational_entity": str, "share_urn": str | None, "impression_count": int, "unique_impressions_count": int, "click_count": int, "engagement": float, "like_count": int, "comment_count": int, "share_count": int}], "count": int}Reads /rest/organizationalEntityShareStatistics, which needs r_organization_social on that organization. Without share_urns or ugc_post_urns the single row is the lifetime org-wide aggregate and share_urn is None. engagement is LinkedIn's ratio of engagements to impressions.
get_follower_count()Get follower count for a LinkedIn organization.
result = qbash.integrations.linkedin.get_follower_count(
organization_id="…",
)
organization_idstrrequiredNumeric LinkedIn organization ID (e.g. "1234567").
{"status": str, "first_degree_size": int}react_to_post()React to a LinkedIn post or comment (like, celebrate, love, etc).
result = qbash.integrations.linkedin.react_to_post(
post_urn="…",
)
post_urnstrrequiredURN of the post or comment to react to (e.g. "urn:li:share:123").
reaction_typestrdefault"LIKE"One of: LIKE, PRAISE (Celebrate), EMPATHY (Love), INTEREST (Insightful), APPRECIATION (Support), ENTERTAINMENT (Funny).
actor_urnstr | NonedefaultNoneURN of the reacting actor. Defaults to the authenticated user's person URN.
{"status": str, "reaction_type": str, "actor_urn": str, "post_urn": str, "id": str}Requires w_member_social_feed, so a credential connected before that scope was added to the app must be reconnected. id is present only when LinkedIn returns the reaction id.
remove_reaction()Remove your reaction from a LinkedIn post or comment.
result = qbash.integrations.linkedin.remove_reaction(
post_urn="…",
)
post_urnstrrequiredURN of the post or comment to remove the reaction from.
actor_urnstr | NonedefaultNoneURN of the reacting actor. Defaults to the authenticated user's person URN.
{"status": str, "actor_urn": str, "post_urn": str}get_reactions()List reactions on a LinkedIn post or comment.
result = qbash.integrations.linkedin.get_reactions(
post_urn="…",
)
post_urnstrrequiredURN of the post or comment to list reactions for.
sortstrdefault"REVERSE_CHRONOLOGICAL"One of: CHRONOLOGICAL, REVERSE_CHRONOLOGICAL, RELEVANCE.
{"status": str, "reactions": list[{"reaction_type": str, "actor": str, "created_at": int}], "count": int}Returns the first page LinkedIn sends. There is no pagination parameter. actor is a person or organization URN and created_at is epoch milliseconds.
get_media_status()Check the processing status of an uploaded image, video, or document.
result = qbash.integrations.linkedin.get_media_status(
media_urn="…",
)
media_urnstrrequiredAsset URN from upload_image, upload_video, or upload_document (e.g. "urn:li:video:...").
{"status": str, "media_urn": str, "processing_status": str, "processing_failure_reason": str}processing_status is LinkedIn's asset status: WAITING_UPLOAD, PROCESSING, AVAILABLE or PROCESSING_FAILED. processing_failure_reason is present only for PROCESSING_FAILED. The media type is inferred from the URN prefix. w_member_social is write-only, so a member-owned asset may 403 here even though the upload succeeded.
create_comment()Comment on a LinkedIn post, or reply to an existing comment (thread the reply via parent_comment_urn).
result = qbash.integrations.linkedin.create_comment(
post_urn="…",
text="…",
)
post_urnstrrequiredURN of the post, ugcPost, or comment to comment on (e.g. "urn:li:share:123").
textstrrequiredComment text.
parent_comment_urnstr | NonedefaultNoneComposite URN of an existing comment to reply to (as returned by create_comment or list_comments). post_urn should still be the original post's URN, not the parent comment's.
actor_urnstr | NonedefaultNoneURN of the commenting actor. Defaults to the authenticated user's person URN.
{"status": str, "post_urn": str, "comment_id": str}comment_id is the id LinkedIn returns for the new comment and is absent when LinkedIn omits it. LinkedIn throttles comment creation per member over a rolling one-minute window and a 429 here says so.
get_comment()Get a single comment on a LinkedIn post.
result = qbash.integrations.linkedin.get_comment(
post_urn="…",
comment_id="…",
)
post_urnstrrequiredURN of the post, ugcPost, or comment the comment belongs to.
comment_idstrrequiredID of the comment to fetch.
{"status": str, "comment": {"id": str, "text": str, "actor": str, "created_at": int, "parent_comment_urn": str | None}}created_at is epoch milliseconds. parent_comment_urn is None for a top-level comment.
list_comments()List comments on a LinkedIn post. Org-authored posts only - a member-authored post's comments cannot be listed (r_member_social_feed is a closed permission).
result = qbash.integrations.linkedin.list_comments(
post_urn="…",
)
post_urnstrrequiredURN of the post or ugcPost to list comments for.
countintdefault10Maximum number of comments to return.
startintdefault0Pagination offset.
{"status": str, "comments": list[{"id": str, "text": str, "actor": str, "created_at": int, "parent_comment_urn": str | None}], "count": int}A 403 on a member-authored post is rewritten to explain the r_member_social_feed limit. Organization posts read fully.
get_comment_replies()List replies to a LinkedIn comment.
result = qbash.integrations.linkedin.get_comment_replies(
post_urn="…",
comment_id="…",
)
post_urnstrrequiredURN of the post the parent comment sits on (e.g. "urn:li:share:123" or "urn:li:ugcPost:123"), as returned by create_post or create_comment.
comment_idstrrequiredID of the parent comment. Accepts either the bare id or the composite urn:li:comment:(...) URN, as returned by create_comment or list_comments. Also read from comment_urn for scripts written against the earlier interface.
{"status": str, "replies": list[{"id": str, "text": str, "actor": str, "created_at": int, "parent_comment_urn": str | None}], "count": int}Builds LinkedIn's composite comment URN from post_urn and comment_id, rewriting a urn:li:share: post URN to the urn:li:ugcPost: form the socialActions path needs. A comment_id that is already a composite comment URN is used as the target unchanged. A 403 on a member-authored thread is rewritten to explain the r_member_social_feed limit. Organization threads read fully.
update_comment()Update the text of an existing LinkedIn comment.
result = qbash.integrations.linkedin.update_comment(
post_urn="…",
comment_id="…",
text="…",
)
post_urnstrrequiredURN of the post, ugcPost, or comment the comment belongs to.
comment_idstrrequiredID of the comment to update.
textstrrequiredNew comment text.
actor_urnstr | NonedefaultNoneURN of the acting identity. LinkedIn requires this even though it cannot change the comment's actor - defaults to the authenticated user's person URN.
{"status": str, "post_urn": str, "comment_id": str, "updated_fields": list[str]}Only the text can change, so updated_fields is always the single LinkedIn field name message.
delete_comment()Delete a LinkedIn comment.
result = qbash.integrations.linkedin.delete_comment(
post_urn="…",
comment_id="…",
)
post_urnstrrequiredURN of the post, ugcPost, or comment the comment belongs to.
comment_idstrrequiredID of the comment to delete.
actor_urnstr | NonedefaultNoneURN of the acting identity LinkedIn requires for this call. Defaults to the authenticated user's person URN.
{"status": str, "post_urn": str, "comment_id": str, "message": str}get_engagement_summary()Get reaction and comment counts for a LinkedIn post or comment. The comment counts are org-authored content only - same r_member_social_feed restriction as list_comments.
result = qbash.integrations.linkedin.get_engagement_summary(
post_urn="…",
)
post_urnstrrequiredURN of the post or comment to summarize.
{"status": str, "post_urn": str, "reactions": {"LIKE": int, "PRAISE": int, "EMPATHY": int, "INTEREST": int, "APPRECIATION": int, "ENTERTAINMENT": int}, "comment_count": int, "top_level_comment_count": int, "comments_state": str}Reads /rest/socialMetadata. Every reaction type is present, 0 when absent. comment_count is aggregatedTotalComments with replies included, top_level_comment_count excludes replies. comments_state is LinkedIn's commentsState, OPEN or CLOSED.
set_comments_enabled()Open or close comments on a LinkedIn post. Disabling comments deletes existing comments on the thread - re-enabling does not restore them.
result = qbash.integrations.linkedin.set_comments_enabled(
post_urn="…",
enabled=True,
)
post_urnstrrequiredURN of the post or comment to open or close comments on.
enabledboolrequiredTrue to open comments, false to close them (destructive - see summary).
actor_urnstr | NonedefaultNoneURN of the acting identity LinkedIn requires for this call. Defaults to the authenticated user's person URN.
{"status": str, "post_urn": str, "enabled": bool, "message": str}enabled must be a real boolean or the strings true or false. Other truthy values are rejected because disabling is destructive.
list_organizations()Batch-look-up LinkedIn organizations by numeric ID. Returns a limited field set (no admin role required), unlike the single-org get_organization lookup.
result = qbash.integrations.linkedin.list_organizations(
organization_ids=[...],
)
organization_idslist[str]requiredNumeric LinkedIn organization IDs to look up (e.g. ["1234567", "7654321"]).
{"status": str, "organizations": list[{"id": int, "name": str, "description": str | None, "website": str | None, "vanity_name": str | None, "logo": str | None}], "count": int}Reads /rest/organizationsLookup, a Rest.li batch get. IDs LinkedIn cannot resolve are dropped from the list rather than failing the call. Same field set as get_organization, but the admin-gated fields are typically None here.
list_my_organization_roles()List the LinkedIn organizations the authenticated credential administers.
result = qbash.integrations.linkedin.list_my_organization_roles()
{"status": str, "roles": list[{"organization_urn": str, "role": str, "state": str, "role_assignee": str}], "count": int}Reads /rest/organizationAcls?q=roleAssignee, scoped by LinkedIn to the connected member. role is a LinkedIn organization role such as ADMINISTRATOR, state is the ACL state such as APPROVED, role_assignee is a person URN.
list_organization_admins()List everyone who administers a given LinkedIn organization.
result = qbash.integrations.linkedin.list_organization_admins(
organization_urn="…",
)
organization_urnstrrequiredOrganization URN (e.g. "urn:li:organization:1234567").
{"status": str, "organization_urn": str, "admins": list[{"organization_urn": str, "role": str, "state": str, "role_assignee": str}], "count": int}Reads /rest/organizationAcls?q=organization, which LinkedIn only answers for an organization the credential administers. Same element shape as list_my_organization_roles.
search_organization_members()Search a LinkedIn organization's followers by name for @mention suggestions (create_comment/create_post). Org-scoped only - searches followers of a page, not the connecting member's own network.
result = qbash.integrations.linkedin.search_organization_members(
organization_urn="…",
)
organization_urnstrrequiredOrganization URN whose followers to search (e.g. "urn:li:organization:1234567").
keywordsstr | NonedefaultNoneName/keyword to search for among the organization's followers. Minimum 3 characters (6 if it contains a space or a period). Provide this or vanity_url.
vanity_urlstr | NonedefaultNoneFull LinkedIn profile URL (e.g. "https://www.linkedin.com/in/janedoe") to resolve to a person URN. Provide this or keywords.
{"status": str, "members": list[{"member": str, "first_name": str | None, "last_name": str | None, "headline": str | None, "photo": str}]}Reads /rest/peopleTypeahead. member is the person URN to @mention. photo is present only when LinkedIn returns a profile picture. No count or pagination.
get_connections_count()Get the authenticated member's first-degree connection count.
result = qbash.integrations.linkedin.get_connections_count()
{"status": str, "connections_count": int}Requires r_1st_connections_size and is always the connected member, taken from the credential's person_urn.
get_organization_follower_stats()Get LinkedIn organization follower statistics - a demographic breakdown (lifetime) or follower gains over time (time-bound).
result = qbash.integrations.linkedin.get_organization_follower_stats(
organization_urn="…",
)
organization_urnstrrequiredOrganization URN (e.g. "urn:li:organization:1234567").
time_granularitystr | NonedefaultNoneOne of "DAY", "WEEK", "MONTH". Provide together with start_time and end_time for the time-bound query (follower gains per interval); omit all three for the lifetime query (demographic breakdown). This mode never returns a total follower count - use get_follower_count for that.
start_timeint | NonedefaultNoneStart of the time range, epoch milliseconds. Required together with time_granularity and end_time.
end_timeint | NonedefaultNoneEnd of the time range, epoch milliseconds. Required together with time_granularity and start_time.
{"status": str, "organization_urn": str, "follower_counts_by_facet": list[{"geo": str | None, "geo_country": str | None, "function": str | None, "industry": str | None, "seniority": str | None, "association_type": str | None, "staff_count_range": str | None, "organic_follower_count": int}]} for the lifetime query, or {"status": str, "organization_urn": str, "follower_gains": list[{"time_range": {"start": int, "end": int}, "organic_follower_gain": int, "paid_follower_gain": int}]} for the time-bound queryReads /rest/organizationalEntityFollowerStatistics. Each lifetime row carries one facet URN with the other facet fields None. time_range bounds are epoch milliseconds.
get_page_statistics()Get view and click statistics for a LinkedIn organization's company page.
result = qbash.integrations.linkedin.get_page_statistics(
organization_urn="…",
)
organization_urnstrrequiredOrganization URN (e.g. "urn:li:organization:1234567").
time_granularitystr | NonedefaultNoneOne of "DAY", "MONTH" (no "WEEK" on this endpoint, unlike get_organization_follower_stats). Provide together with start_time and end_time for the time-bound query; omit all three for the lifetime query.
start_timeint | NonedefaultNoneStart of the time range, epoch milliseconds. Required together with time_granularity and end_time.
end_timeint | NonedefaultNoneEnd of the time range, epoch milliseconds. Required together with time_granularity and start_time.
{"status": str, "organization_urn": str, "statistics": list[{"organizational_entity": str, "time_range": {"start": int, "end": int} | None, "views": dict, "clicks": dict}], "count": int}Requires rw_organization_admin. views and clicks are LinkedIn's totalPageStatistics sub-objects with keys snake_cased, e.g. views.all_page_views.page_views. The lifetime query returns one row with time_range None, the time-bound query one row per interval. Career-page metrics are absent without the paid Career Pages product.
get_video_analytics()Get view and watch-time analytics for an organization's video post.
result = qbash.integrations.linkedin.get_video_analytics(
ugc_post_urn="…",
metric="…",
)
ugc_post_urnstrrequiredUGC post URN of the video (e.g. "urn:li:ugcPost:123").
metricstrrequiredOne of "VIDEO_VIEW", "VIEWER", "TIME_WATCHED", "TIME_WATCHED_FOR_VIDEO_VIEWS". Likes/comments are not in this API - use get_engagement_summary for those. Data becomes unavailable roughly 6 months after post creation (resets if the video goes viral again later).
aggregationstrdefault"ALL"One of "DAY", "WEEK", "ALL". Without an explicit start_time/end_time, DAY/WEEK aggregation silently truncates to the 10 most recent records - always pass a time range if more are needed.
start_timeint | NonedefaultNoneStart of the time range, epoch milliseconds. Provide together with end_time.
end_timeint | NonedefaultNoneEnd of the time range, epoch milliseconds. Provide together with start_time.
{"status": str, "ugc_post_urn": str, "metric": str, "aggregation": str, "analytics": list[{"time_range": {"start": int, "end": int} | None, "value": int}], "count": int}Requires rw_organization_admin and organization-authored video only. ALL aggregation returns one row. DAY and WEEK return one row per bucket, capped at the 10 most recent buckets unless start_time and end_time are passed.
get_member_follower_stats()Get the authenticated member's own follower count - a lifetime total or a daily time series.
result = qbash.integrations.linkedin.get_member_follower_stats(
start_date={"key": "value"},
end_date={"key": "value"},
)
start_datestr | dict | NonedefaultNoneStart of the date range (inclusive), as an ISO "YYYY-MM-DD" string or a {"year": Y, "month": M, "day": D} dict. Provide together with end_date for the daily time series; omit both for the lifetime total.
yearintrequiredFour-digit year (e.g. 2026).
monthintrequiredMonth of the year, 1-12.
dayintrequiredDay of the month, 1-31.
end_datestr | dict | NonedefaultNoneEnd of the date range (exclusive), same format as start_date. Provide together with start_date.
yearintrequiredFour-digit year (e.g. 2026).
monthintrequiredMonth of the year, 1-12.
dayintrequiredDay of the month, 1-31.
{"status": str, "member_followers_count": int} for the lifetime query, or {"status": str, "follower_counts_by_day": list[{"member_followers_count": int, "date_range": {"start": {"year": int, "month": int, "day": int}, "end": {"year": int, "month": int, "day": int}}}]} for a date rangeRequires r_member_profileAnalytics and is always the connected member. The date range is start inclusive, end exclusive, with one element per day.
get_member_post_analytics()Get analytics for the authenticated member's own posts - a single post, or aggregated across all of them.
result = qbash.integrations.linkedin.get_member_post_analytics(
metric="…",
)
post_urnstr | NonedefaultNoneA "urn:li:share:..." or "urn:li:ugcPost:..." post URN. Omit to aggregate across every post the member has authored (LinkedIn notes the RESHARE/REACTION/COMMENT metrics are not always consistent with the UI in this aggregate mode).
metricstrrequiredOne of "IMPRESSION", "MEMBERS_REACHED", "RESHARE", "REACTION", "COMMENT", "POST_SAVE", "POST_SEND", "LINK_CLICKS", "PREMIUM_CTA_CLICKS", "FOLLOWER_GAINED_FROM_CONTENT", "PROFILE_VIEW_FROM_CONTENT". The last 6 require a newer LinkedIn API version than this integration may be sending and are rejected with a clear error when unsupported.
aggregationstrdefault"TOTAL""TOTAL" or "DAILY". "DAILY" is rejected for MEMBERS_REACHED, and for LINK_CLICKS/FOLLOWER_GAINED_FROM_CONTENT/PROFILE_VIEW_FROM_CONTENT - use "TOTAL" for those metrics.
start_datestr | dict | NonedefaultNoneStart of the date range for a DAILY query, as an ISO "YYYY-MM-DD" string or a {"year": Y, "month": M, "day": D} dict. Provide together with end_date.
yearintrequiredFour-digit year (e.g. 2026).
monthintrequiredMonth of the year, 1-12.
dayintrequiredDay of the month, 1-31.
end_datestr | dict | NonedefaultNoneEnd of the date range for a DAILY query, same format as start_date. Provide together with start_date.
yearintrequiredFour-digit year (e.g. 2026).
monthintrequiredMonth of the year, 1-12.
dayintrequiredDay of the month, 1-31.
{"status": str, "metric": str, "aggregation": str, "post_urn": str | None, "analytics": list[{"value": int, "date_range": {"start": {"year": int, "month": int, "day": int}, "end": {"year": int, "month": int, "day": int}} | None, "metric_type": str}], "count": int}Requires r_member_postAnalytics. TOTAL returns one row with date_range None. DAILY returns one row per day between start_date and end_date. metric_type echoes the metric.
get_member_video_analytics()Get view and watch-time analytics for a video in the authenticated member's own posts.
result = qbash.integrations.linkedin.get_member_video_analytics(
metric="…",
)
post_urnstr | NonedefaultNoneA "urn:li:share:..." or "urn:li:ugcPost:..." post URN containing a video. Omit to aggregate across every video post the member has authored. Querying a non-video post returns a LinkedIn 500 ("Unsupported video metric type ... for non-video post"), not a 400/404.
metricstrrequiredOne of "VIDEO_PLAY", "VIDEO_VIEWER", "VIDEO_WATCH_TIME".
aggregationstrdefault"TOTAL""TOTAL" or "DAILY". "DAILY" is only supported for VIDEO_PLAY - VIDEO_VIEWER and VIDEO_WATCH_TIME are TOTAL only.
start_datestr | dict | NonedefaultNoneStart of the date range for a DAILY query, as an ISO "YYYY-MM-DD" string or a {"year": Y, "month": M, "day": D} dict. Provide together with end_date.
yearintrequiredFour-digit year (e.g. 2026).
monthintrequiredMonth of the year, 1-12.
dayintrequiredDay of the month, 1-31.
end_datestr | dict | NonedefaultNoneEnd of the date range for a DAILY query, same format as start_date. Provide together with start_date.
yearintrequiredFour-digit year (e.g. 2026).
monthintrequiredMonth of the year, 1-12.
dayintrequiredDay of the month, 1-31.
{"status": str, "metric": str, "aggregation": str, "post_urn": str | None, "analytics": list[{"value": int, "date_range": {"start": {"year": int, "month": int, "day": int}, "end": {"year": int, "month": int, "day": int}} | None, "metric_type": str}], "count": int}Requires r_member_postAnalytics. Same row shape as get_member_post_analytics. When post_urn is omitted and the member has never posted a video, LinkedIn 404s and the mode returns success with analytics empty and count 0. A per-post 404 stays an error, as does the 500 LinkedIn sends for a non-video post_urn.
aggregates()OHLCV aggregate bars for a ticker over a date range. Multiplier + timespan (e.g. 5/minute, 1/day). `from`/`to` accept YYYY-MM-DD or unix-millis.
result = qbash.integrations.massive.aggregates(
ticker="…",
multiplier=0,
timespan="…",
from_="…",
to="…",
)
tickerstrrequiredTicker symbol (e.g. "AAPL").
multiplierintrequiredSize of the timespan multiplier (e.g. 5).
timespanstrrequiredWindow size: second/minute/hour/day/week/month/quarter/year.
from_strrequiredStart date (YYYY-MM-DD) or unix-millis.
tostrrequiredEnd date (YYYY-MM-DD) or unix-millis.
adjustedbooldefaultTrueWhether bars are split-adjusted.
sortstrdefaultNoneasc or desc by timestamp.
limitintdefaultNoneMax results per page (≤50000).
{"status": "success", "results": list[dict] | dict | str | int | float | bool | None, "ticker": str | None, "query_count": int | None, "results_count": int | None, "adjusted": bool | None, "next_url": str | None, "request_id": str | None, "upstream_status": str | None} or {}results holds one dict per bar, forwarded unchanged and never inspected here, so read the bar keys with .get() and check the type of results before indexing it. results is absent whenever the Massive body carried no results key, so reach for it with .get and default to an empty list. next_url is Massive's cursor for the next page. It is surfaced but cannot be followed from here, because no mode accepts it and no cursor param is declared, so raise limit, which tops out at 50000, or narrow the date range instead. query_count and results_count are Massive's own counts renamed from queryCount and resultsCount and are not recomputed here, so compare them against the length of results rather than trusting either. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
previous_close()Previous-day OHLCV bar for a ticker.
result = qbash.integrations.massive.previous_close(
ticker="…",
)
tickerstrrequiredTicker symbol.
adjustedbooldefaultTrueSplit-adjusted bar.
{"status": "success", "results": list[dict] | dict | str | int | float | bool | None, "ticker": str | None, "query_count": int | None, "results_count": int | None, "adjusted": bool | None, "request_id": str | None, "upstream_status": str | None} or {}results carries the bar as a list rather than as a single dict, and it is forwarded unchanged and never inspected here, so check the type and the length of results before reading results[0]. results is absent whenever the Massive body carried no results key, so reach for it with .get and default to an empty list. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
last_quote()Most recent NBBO quote (bid/ask) for a ticker. Requires a paid plan for real-time.
result = qbash.integrations.massive.last_quote(
ticker="…",
)
tickerstrrequiredTicker symbol.
{"status": "success", "results": dict | list | str | int | float | bool | None, "request_id": str | None, "upstream_status": str | None} or {}results is the quote as Massive sends it, forwarded unchanged and never inspected here, so read its keys with .get(). results is absent whenever the Massive body carried no results key. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so a body that reports delayed or otherwise limited data arrives as a success and only upstream_status says so. A plan without real-time entitlement gives a 403, which comes back as an error rather than as a result. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
last_trade()Most recent trade for a ticker.
result = qbash.integrations.massive.last_trade(
ticker="…",
)
tickerstrrequiredTicker symbol.
{"status": "success", "results": dict | list | str | int | float | bool | None, "request_id": str | None, "upstream_status": str | None} or {}results is the trade as Massive sends it, forwarded unchanged and never inspected here, so read its keys with .get(). results is absent whenever the Massive body carried no results key. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so a body that reports delayed or otherwise limited data arrives as a success and only upstream_status says so. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
ticker_details()Descriptive metadata for a ticker (name, market cap, description, branding, etc.).
result = qbash.integrations.massive.ticker_details(
ticker="…",
)
tickerstrrequiredTicker symbol.
datestrdefaultNonePoint-in-time date as YYYY-MM-DD. Returns the details valid on that date.
{"status": "success", "results": dict | list | str | int | float | bool | None, "request_id": str | None, "upstream_status": str | None} or {}results is the ticker record as Massive sends it, forwarded unchanged and never inspected here, so read its keys with .get(). results is absent whenever the Massive body carried no results key, which is what an unknown ticker can give instead of an error. Setting date asks for the record as it stood on that day rather than the current one. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
list_tickers()Paginated reference list of tickers. Filter by exchange, type, market, CIK/CUSIP, or search.
result = qbash.integrations.massive.list_tickers(
ticker="…",
ticker_gte="…",
)
tickerstrdefaultNoneExact ticker filter.
ticker_gtestrdefaultNoneTicker >= value (paginate).
ticker_gtstrdefaultNoneTicker > value.
ticker_ltestrdefaultNoneTicker <= value.
ticker_ltstrdefaultNoneTicker < value.
typestrdefaultNoneCS, ETF, ADRC, FUND, ... see Massive docs.
marketstrdefaultNonestocks, crypto, fx, otc, indices.
exchangestrdefaultNoneMIC code (e.g. XNAS).
cusipstrdefaultNoneCUSIP identifier.
cikstrdefaultNoneSEC CIK identifier.
datestrdefaultNonePoint-in-time YYYY-MM-DD.
searchstrdefaultNoneSubstring search across ticker + name.
activebooldefaultTrueFilter active vs delisted.
orderstrdefaultNoneasc or desc.
limitintdefaultNonePage size (≤1000).
sortstrdefaultNoneField to sort by.
{"status": "success", "results": list[dict] | dict | str | int | float | bool | None, "count": int | None, "next_url": str | None, "request_id": str | None, "upstream_status": str | None} or {}results holds one dict per ticker, forwarded unchanged and never inspected here, so read the record keys with .get() and default results to an empty list, since the key is absent whenever the Massive body carried none. count is Massive's own count field for this response and is not recomputed here. Only one page is fetched. next_url is Massive's cursor and it is surfaced but cannot be followed from here, because no mode accepts it and no cursor param is declared, so walk the list with limit, which tops out at 1000, together with ticker_gte or ticker_gt and a sort on ticker. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
ticker_news()News articles index. Filter by ticker and publish-time range.
result = qbash.integrations.massive.ticker_news(
ticker="…",
published_utc_gte="…",
)
tickerstrdefaultNoneTicker filter.
published_utc_gtestrdefaultNonePublished >= ISO timestamp.
published_utc_gtstrdefaultNonePublished > ISO timestamp.
published_utc_ltestrdefaultNonePublished <= ISO timestamp.
published_utc_ltstrdefaultNonePublished < ISO timestamp.
orderstrdefaultNoneasc or desc.
limitintdefaultNonePage size (≤1000).
sortstrdefaultNoneField to sort by (e.g. published_utc).
{"status": "success", "results": list[dict] | dict | str | int | float | bool | None, "count": int | None, "next_url": str | None, "request_id": str | None, "upstream_status": str | None} or {}results holds one dict per article, forwarded unchanged and never inspected here, so read the article keys with .get() and default results to an empty list, since the key is absent whenever the Massive body carried none. count is Massive's own count field for this response and is not recomputed here. Only one page is fetched. next_url is Massive's cursor and it is surfaced but cannot be followed from here, because no mode accepts it and no cursor param is declared, so narrow with the published_utc filters and raise limit, which tops out at 1000. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
options_chain()Snapshot of all options contracts for an underlying asset, with greeks and last quote. Use strike_price_gte/lte and expiration_date_gte/lte to filter.
result = qbash.integrations.massive.options_chain(
underlying_asset="…",
)
underlying_assetstrrequiredUnderlying ticker (e.g. "AAPL").
strike_price_gtestrdefaultNoneStrike >= value.
strike_price_gtstrdefaultNoneStrike > value.
strike_price_ltestrdefaultNoneStrike <= value.
strike_price_ltstrdefaultNoneStrike < value.
expiration_date_gtestrdefaultNoneExpiration >= YYYY-MM-DD.
expiration_date_gtstrdefaultNoneExpiration > YYYY-MM-DD.
expiration_date_ltestrdefaultNoneExpiration <= YYYY-MM-DD.
expiration_date_ltstrdefaultNoneExpiration < YYYY-MM-DD.
contract_typestrdefaultNonecall or put.
orderstrdefaultNoneasc or desc.
limitintdefaultNonePage size (≤250).
sortstrdefaultNoneSort field.
{"status": "success", "results": list[dict] | dict | str | int | float | bool | None, "next_url": str | None, "request_id": str | None, "upstream_status": str | None} or {}results holds one dict per contract, forwarded unchanged and never inspected here, so read the contract keys with .get() and default results to an empty list, since the key is absent whenever the Massive body carried none. Only one page is fetched and limit tops out at 250, so the chain of a liquid underlying does not fit in one call. next_url is Massive's cursor and it is surfaced but cannot be followed from here, because no mode accepts it and no cursor param is declared, so split the request with the strike_price and expiration_date filters rather than expecting the whole chain. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
option_contract_snapshot()Snapshot of one specific options contract (e.g. O:AAPL250620C00200000).
result = qbash.integrations.massive.option_contract_snapshot(
underlying_asset="…",
option_contract="…",
)
underlying_assetstrrequiredUnderlying ticker.
option_contractstrrequiredOptions ticker (e.g. "O:AAPL250620C00200000").
{"status": "success", "results": dict | list | str | int | float | bool | None, "request_id": str | None, "upstream_status": str | None} or {}results is the contract snapshot as Massive sends it, forwarded unchanged and never inspected here, so read its keys, including the greeks and the last quote, with .get(). results is absent whenever the Massive body carried no results key. A contract that does not exist comes back as a 404, which is turned into an error. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
list_option_contracts()Reference list of historical and active option contracts.
result = qbash.integrations.massive.list_option_contracts(
underlying_ticker="…",
contract_type="…",
)
underlying_tickerstrdefaultNoneUnderlying ticker filter.
contract_typestrdefaultNonecall or put.
expiration_datestrdefaultNoneExact expiration YYYY-MM-DD.
expiration_date_gtestrdefaultNoneExpiration >= YYYY-MM-DD.
expiration_date_gtstrdefaultNoneExpiration > YYYY-MM-DD.
expiration_date_ltestrdefaultNoneExpiration <= YYYY-MM-DD.
expiration_date_ltstrdefaultNoneExpiration < YYYY-MM-DD.
strike_price_gtestrdefaultNoneStrike >= value.
strike_price_gtstrdefaultNoneStrike > value.
strike_price_ltestrdefaultNoneStrike <= value.
strike_price_ltstrdefaultNoneStrike < value.
as_ofstrdefaultNonePoint-in-time YYYY-MM-DD.
expiredbooldefaultFalseInclude expired contracts.
orderstrdefaultNoneasc or desc.
limitintdefaultNonePage size (≤1000).
sortstrdefaultNoneSort field.
{"status": "success", "results": list[dict] | dict | str | int | float | bool | None, "count": int | None, "next_url": str | None, "request_id": str | None, "upstream_status": str | None} or {}results holds one dict per contract, forwarded unchanged and never inspected here, so read the contract keys with .get() and default results to an empty list, since the key is absent whenever the Massive body carried none. count is Massive's own count field for this response and is not recomputed here. Only one page is fetched. next_url is Massive's cursor and it is surfaced but cannot be followed from here, because no mode accepts it and no cursor param is declared, so narrow with the expiration_date and strike_price filters and raise limit, which tops out at 1000. Expired contracts are left out unless expired is set. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
market_status()Current US market status (open/closed, after-hours, exchanges).
result = qbash.integrations.massive.market_status()
{"status": "success", "results": list[dict] | dict | str | int | float | bool | None, "market": str | None, "server_time": str | None, "exchanges": dict | None, "currencies": dict | None, "after_hours": bool | None, "early_hours": bool | None, "upstream_status": str | None} or {}status is this integration's own envelope flag and is always success on a 2xx, so read market for whether the market is open. server_time, after_hours and early_hours are renamed from the serverTime, afterHours and earlyHours keys Massive sends. exchanges and currencies are Massive's own maps of per-venue and per-currency state, forwarded unchanged and never inspected here, so read them with .get(). upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
exchanges()Reference list of exchanges Massive tracks.
result = qbash.integrations.massive.exchanges(
asset_class="…",
locale="…",
)
asset_classstrdefaultNonestocks, options, crypto, fx, indices.
localestrdefaultNoneus or global.
{"status": "success", "results": list[dict] | dict | str | int | float | bool | None, "count": int | None, "request_id": str | None, "upstream_status": str | None} or {}results holds one dict per exchange, forwarded unchanged and never inspected here, so read the exchange keys with .get() and default results to an empty list, since the key is absent whenever the Massive body carried none. count is Massive's own count field for this response and is not recomputed here. The request carries no limit or cursor, so this is the single response Massive returned for the asset_class and locale filters that were set. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
grouped_daily()Daily OHLCV bars for the entire US equities market on a single date. One request covers the whole market for that date, typically several thousand rows.
result = qbash.integrations.massive.grouped_daily(
date="…",
)
datestrrequiredTrading date YYYY-MM-DD.
adjustedbooldefaultTrueWhether bars are split-adjusted.
include_otcbooldefaultFalseInclude OTC securities.
{"status": "success", "results": list[dict] | dict | str | int | float | bool | None, "query_count": int | None, "results_count": int | None, "adjusted": bool | None, "request_id": str | None, "upstream_status": str | None} or {}results holds one bar per ticker for the date, forwarded unchanged and never inspected here, so read the bar keys with .get(). results is absent whenever the Massive body carried no results key, which is what a date the market did not trade on can give, so reach for it with .get and default to an empty list rather than treating a missing key as zero bars. query_count and results_count are Massive's own counts renamed from queryCount and resultsCount and are not recomputed here, so compare them against the length of results rather than trusting either. include_otc has to be set for OTC securities to appear. upstream_status carries Massive's own status field, and only ERROR and NOT_AUTHORIZED are turned into errors, so any other value arrives as a success. Every key except status is set only when the Massive body carried it, and the response builder copies only status, request_id, results, next_url, count, ticker, query_count, results_count, adjusted, market, server_time, exchanges, currencies, after_hours, early_hours and upstream_status, dropping every other key in the body. A 2xx with an empty body arrives as an empty dict with no keys at all, and a 2xx whose body is a bare JSON string, number or boolean arrives with that value under results, so check the type of results before indexing it.
list_files()List files in a folder.
result = qbash.integrations.microsoft_onedrive.list_files(
folder_path="my-folder",
top=10,
)
folder_pathstr | NonedefaultNoneFolder path relative to the drive root, e.g. Documents (defaults to root).
topint | NonedefaultNoneItems per page, sent as Graph's $top (Graph's default is 200).
page_tokenstr | NonedefaultNoneAn opaque token from a previous response, the next_page_token it returned. When set, the other paging and filter params are ignored.
{"value": list[dict], "@odata.nextLink": str | None, "next_page_token": str | None}Each entry in value is a Microsoft Graph driveItem. A folder carries a folder key and a file carries a file key, and Graph sends only the one that applies, so read both with .get() to tell them apart. One call returns one Graph page, 200 items by default or top when set. When Graph has more, the result also carries next_page_token, an opaque token that holds @odata.nextLink. Pass it back as page_token to get the next page, and stop when next_page_token is absent. A page_token has to come from a previous response of this mode and expires after 24 hours, so after an expired or refused token start again from the first page without it.
search_files()Search for files by name or content.
result = qbash.integrations.microsoft_onedrive.search_files(
query="search terms",
)
querystrrequiredSearch query string.
{"value": list[dict], "@odata.nextLink": str | None}Searches the whole drive from the root, across file names and indexed content, rather than one folder. Each entry in value is a Microsoft Graph driveItem. Only the first page of matches comes back. Graph adds @odata.nextLink when more matches exist, and this mode takes no continuation parameter, so the pages after the first cannot be reached through it.
upload_file()Upload a new file to OneDrive.
result = qbash.integrations.microsoft_onedrive.upload_file(
file_path="…",
)
file_pathstrrequiredDestination path relative to the drive root, with no leading slash (e.g. Documents/report.txt).
contentstr | NonedefaultNoneFile content to upload: plain text for text files, base64 for binary files. Pass content or file.
filefile | NonedefaultNoneA qbash file reference to upload. Pass content or file. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
content_typestr | NonedefaultNoneMIME type (defaults to text/plain).
{"id": str, "name": str, "size": int, "webUrl": str} or {"status": "success"} when Graph answers with an empty bodyThe response is the Microsoft Graph driveItem for the uploaded file, passed through untouched, so it carries the rest of the driveItem fields as well as the four named here. An existing file at the same path is replaced. Pass content for text or small base64 data, or file for a qbash file reference. A file reference goes up through a Graph upload session in 10 MB pieces read from storage, up to qbash's 5 GB file limit, and OneDrive sets its type from the file name.
download_file()Download a file from OneDrive.
result = qbash.integrations.microsoft_onedrive.download_file(
item_id="…",
file_path="…",
)
item_idstr | NonedefaultNoneOneDrive item ID (item_id or file_path is required).
file_pathstr | NonedefaultNoneFile path in OneDrive (item_id or file_path is required).
{"stored_file": file, "content_base64": str | None, "content_omitted": str | None, "id": str, "name": str, "size": int}The file is stored as a qbash file and stored_file is its reference, which another integration or qbash.files can use. For a file of 10 MB or less the bytes are also base64 encoded into content_base64, so decode it with base64.b64decode before use. content_base64 is deprecated. A larger file streams into storage up to qbash's 5 GB file limit, has no content_base64, and carries content_omitted instead. The other keys come from the Microsoft Graph driveItem for the file, which is returned untouched. If the file cannot be stored the call returns an error. A multi-GB download needs a run whose expected runtime is over 15 minutes.
delete_file()Delete a file from OneDrive.
result = qbash.integrations.microsoft_onedrive.delete_file(
item_id="…",
file_path="…",
)
item_idstr | NonedefaultNoneOneDrive item ID (item_id or file_path is required).
file_pathstr | NonedefaultNoneFile path in OneDrive (item_id or file_path is required).
{"status": "success"}Graph answers a delete with an empty 204 response, so the result holds the status key and nothing about the item that was removed. Record the item id or path before calling if you need it afterwards.
copy_file()Copy a file to a new location.
result = qbash.integrations.microsoft_onedrive.copy_file(
item_id="…",
file_path="…",
)
item_idstr | NonedefaultNoneOneDrive item ID (item_id or file_path is required).
file_pathstr | NonedefaultNoneSource file path (item_id or file_path is required).
destination_pathstr | NonedefaultNoneDestination folder path (destination_path or destination_folder_id is required).
destination_folder_idstr | NonedefaultNoneDestination folder ID, used in place of destination_path when given.
new_namestr | NonedefaultNoneName for the copy (defaults to the source name).
{"status": "success"}Graph queues the copy and answers with an empty 202 response before the copy has finished, so the result holds the status key and no id for the new item. The monitor URL that Graph returns in the Location header is not exposed. Call get-metadata on the destination path to confirm the copy landed.
move_file()Move a file to a new location.
result = qbash.integrations.microsoft_onedrive.move_file(
item_id="…",
file_path="…",
)
item_idstr | NonedefaultNoneOneDrive item ID (item_id or file_path is required).
file_pathstr | NonedefaultNoneSource file path (item_id or file_path is required).
new_namestr | NonedefaultNoneNew filename (new_name, destination_folder_id, or destination_path is required).
destination_folder_idstr | NonedefaultNoneDestination folder ID, used in place of destination_path when given.
destination_pathstr | NonedefaultNoneDestination path (new_name, destination_folder_id, or destination_path is required).
{"id": str, "name": str, "parentReference": dict, "webUrl": str}The response is the Microsoft Graph driveItem as it stands after the move, passed through untouched, so it carries the rest of the driveItem fields as well. parentReference holds the folder the item now sits in. Renaming and moving run through the same call, so a request carrying only new_name renames the item where it is.
create_folder()Create a new folder in OneDrive.
result = qbash.integrations.microsoft_onedrive.create_folder(
folder_name="my-folder",
)
folder_namestrrequiredName of the folder to create.
parent_pathstr | NonedefaultNoneParent folder path relative to the drive root, e.g. Documents (defaults to root).
{"id": str, "name": str, "webUrl": str}The request asks Graph to rename on conflict, so a folder that already holds the requested name results in a second folder with a suffixed name rather than an error. Read name from the response to learn the name the new folder actually has. The response is the Microsoft Graph driveItem, passed through untouched, so it carries the rest of the driveItem fields as well.
get_metadata()Get file or folder metadata.
result = qbash.integrations.microsoft_onedrive.get_metadata(
item_id="…",
file_path="…",
)
item_idstr | NonedefaultNoneOneDrive item ID (item_id or file_path is required).
file_pathstr | NonedefaultNoneFile or folder path (item_id or file_path is required).
{"id": str, "name": str, "size": int, "webUrl": str, "file": dict | None, "folder": dict | None}Graph marks a file with a file key and a folder with a folder key, and sends only the one that applies, so read both with .get() to tell the two apart. The response is the Microsoft Graph driveItem, passed through untouched, so it carries the rest of the driveItem fields as well.
create_sharing_link()Create a sharing link for a file.
result = qbash.integrations.microsoft_onedrive.create_sharing_link(
item_id="…",
file_path="…",
)
item_idstr | NonedefaultNoneOneDrive item ID (item_id or file_path is required).
file_pathstr | NonedefaultNoneFile path (item_id or file_path is required).
link_typestrdefault"view"Link permission, one of view, edit, or embed (defaults to view).
scopestrdefault"anonymous"Who the link works for, either anonymous or organization.
{"id": str, "link": {"webUrl": str, "type": str, "scope": str}}The shareable URL sits at link.webUrl rather than at the top level, and the link facet echoes back the type and scope that were asked for. The response is the Microsoft Graph permission object, passed through untouched, so it carries the rest of the permission fields as well.
create_document()Create a document from text content.
result = qbash.integrations.microsoft_onedrive.create_document(
file_name="…",
content="…",
)
file_namestrrequiredFile name for the new document.
contentstrrequiredDocument content.
folder_pathstr | NonedefaultNoneFolder path relative to the drive root to create the document in, e.g. Documents (defaults to root).
{"id": str, "name": str, "size": int, "webUrl": str} or {"status": "success"} when Graph answers with an empty bodyThe extension on file_name decides what gets stored. A .docx name is packed into a minimal Word document whose only content is the text split into paragraphs on blank lines, with no styling. A .html name is stored as text/html. Every other extension, including .txt, is stored as plain text under the name given, so a name such as report.pdf holds text rather than a PDF. The response is the Microsoft Graph driveItem, passed through untouched.
list_worksheets()List all worksheets in a workbook.
result = qbash.integrations.microsoft_onedrive.list_worksheets(
workbook_id="…",
workbook_path="…",
)
workbook_idstr | NonedefaultNoneWorkbook item ID (workbook_id or workbook_path is required).
workbook_pathstr | NonedefaultNoneWorkbook file path (workbook_id or workbook_path is required).
{"value": list[dict], "@odata.nextLink": str | None}Each entry in value is a Microsoft Graph worksheet object, and its name is the value the other workbook modes take as worksheet_name.
read_range()Read a range of cells from a worksheet.
result = qbash.integrations.microsoft_onedrive.read_range(
worksheet_name="…",
range="…",
)
workbook_idstr | NonedefaultNoneWorkbook item ID (workbook_id or workbook_path is required).
workbook_pathstr | NonedefaultNoneWorkbook file path (workbook_id or workbook_path is required).
worksheet_namestrrequiredWorksheet name.
rangestrrequiredCell range to read (e.g. A1:D10).
{"address": str, "values": list[list], "text": list[list], "formulas": list[list]}values is row major and always nested one level, so a single row of cells arrives as a list holding one list. values holds the typed cell values and text holds what Excel displays for them. The response is the Microsoft Graph range object, passed through untouched, so it carries the rest of the range fields as well.
write_range()Write values to a range of cells.
result = qbash.integrations.microsoft_onedrive.write_range(
worksheet_name="…",
range="…",
values=[...],
)
workbook_idstr | NonedefaultNoneWorkbook item ID (workbook_id or workbook_path is required).
workbook_pathstr | NonedefaultNoneWorkbook file path (workbook_id or workbook_path is required).
worksheet_namestrrequiredWorksheet name.
rangestrrequiredCell range to write (e.g. A1:B2).
valueslistrequired2-D array of values to write.
{"address": str, "values": list[list]}The response is the Microsoft Graph range object for the address after the write, passed through untouched. The values sent must be nested one level per row and must match the size of the range named in address.
add_table_row()Add a row to an existing table.
result = qbash.integrations.microsoft_onedrive.add_table_row(
table_name="…",
values=[...],
)
workbook_idstr | NonedefaultNoneWorkbook item ID (workbook_id or workbook_path is required).
workbook_pathstr | NonedefaultNoneWorkbook file path (workbook_id or workbook_path is required).
table_namestrrequiredTable name.
valueslistrequiredRow values to append.
worksheet_namestr | NonedefaultNoneWorksheet name (required for some workbook layouts).
{"index": int, "values": list[list]}One row is appended per call, so pass values as a flat list of cell values for that row. A list of rows is sent as a single nested row and Graph rejects it. index is the position the new row took within the table body.
list_tables()List all tables in a workbook.
result = qbash.integrations.microsoft_onedrive.list_tables(
workbook_id="…",
workbook_path="…",
)
workbook_idstr | NonedefaultNoneWorkbook item ID (workbook_id or workbook_path is required).
workbook_pathstr | NonedefaultNoneWorkbook file path (workbook_id or workbook_path is required).
worksheet_namestr | NonedefaultNoneWorksheet to list tables from (all worksheets when omitted).
{"value": list[dict], "@odata.nextLink": str | None}Without worksheet_name, tables from every worksheet in the workbook come back together. Each entry in value is a Microsoft Graph table object whose name is the value read-table and add-table-row take as table_name.
read_table()Read all rows from a table.
result = qbash.integrations.microsoft_onedrive.read_table(
table_name="…",
)
workbook_idstr | NonedefaultNoneWorkbook item ID (workbook_id or workbook_path is required).
workbook_pathstr | NonedefaultNoneWorkbook file path (workbook_id or workbook_path is required).
table_namestrrequiredTable name.
worksheet_namestr | NonedefaultNoneWorksheet holding the table (required for some workbook layouts).
topint | NonedefaultNoneMaximum number of rows to return.
skipint | NonedefaultNoneNumber of rows to skip before returning results.
{"value": list[dict], "@odata.nextLink": str | None}Rows come back one Graph page at a time rather than as the whole table, so walk a long table with top and skip. Each entry in value is a table row whose values key is nested one level, holding a single list of cell values for that row.
get_cell_value()Read a single cell value.
result = qbash.integrations.microsoft_onedrive.get_cell_value(
worksheet_name="…",
cell="…",
)
workbook_idstr | NonedefaultNoneWorkbook item ID (workbook_id or workbook_path is required).
workbook_pathstr | NonedefaultNoneWorkbook file path (workbook_id or workbook_path is required).
worksheet_namestrrequiredWorksheet name.
cellstrrequiredCell address (e.g. A1).
{"address": str, "values": list[list], "text": list[list]}Graph answers with a range object even for one cell, so the value sits at values[0][0] and the string Excel displays at text[0][0]. The response is the Microsoft Graph range object, passed through untouched, so it carries the rest of the range fields as well.
create_workbook()Create a new Excel workbook.
result = qbash.integrations.microsoft_onedrive.create_workbook(
file_path="…",
)
file_pathstrrequiredPath (including filename) for the new workbook.
{"id": str, "name": str, "size": int, "webUrl": str} or {"status": "success"} when Graph answers with an empty bodyThe workbook is written by uploading a fixed empty template to the path given, and an existing file at that path is replaced. The response is the Microsoft Graph driveItem, passed through untouched.
get_message()Get a specific Outlook message by ID.
result = qbash.integrations.outlook_mail.get_message(
message_id="…",
)
message_idstrrequiredOutlook message ID to retrieve.
{"id": str, "subject": str | None, "body": dict | None, "bodyPreview": str | None, "from": dict | None, "toRecipients": list[dict] | None, "ccRecipients": list[dict] | None, "bccRecipients": list[dict] | None, "hasAttachments": bool | None, "isRead": bool | None, "importance": str | None, "conversationId": str | None, "receivedDateTime": str | None, "sentDateTime": str | None, "attachments": list[dict] | None}The result is the Graph message itself, with no status wrapper around it. Those keys are the ones this mode asks Graph for, and a value the message does not carry comes back as None or is left out, so read them with .get rather than indexing. body holds contentType and content, and from and every recipient entry hold emailAddress with address and name. attachments is expanded with only id, name, contentType, size and isInline, so it carries attachment metadata and never the bytes. Pass an attachment id to download-attachment to get the content.
list_messages()List messages in the user's Outlook mailbox.
result = qbash.integrations.outlook_mail.list_messages(
folder_id="my-folder",
filter="…",
)
folder_idstr | Nonedefault"inbox"Mail folder ID to list messages from (defaults to inbox).
filterstr | NonedefaultNoneOData filter expression to filter messages.
topint | Nonedefault50Maximum number of messages to return.
skipint | Nonedefault0Number of messages to skip before returning results.
has_attachmentsstr | NonedefaultNoneFilter by attachment presence. Pass with or without.
read_statusstr | NonedefaultNoneFilter by read state. Pass read or unread.
{"value": list[dict], "@odata.nextLink": str | None}The result is the Graph response itself, with no status wrapper. value holds one page of at most top messages from the folder, starting after skip, and nothing in the result counts how many messages match. Each entry carries only id, subject, from, receivedDateTime, hasAttachments, isRead and bodyPreview, so call get-message when you need the body or the attachment list. Raise skip by top to read the next page. Graph adds @odata.nextLink while more messages remain, so read it with .get as a sign that another page exists. No parameter accepts it, so it cannot be passed back in.
send_message()Send an email via Outlook.
result = qbash.integrations.outlook_mail.send_message(
to_recipients=[...],
subject="…",
)
to_recipientslist | strrequiredRecipient email addresses, as a list or a comma-separated string.
subjectstrrequiredEmail subject line.
bodystr | NonedefaultNoneEmail body content (HTML or plain text).
body_typestrdefault"HTML"Body content type, either HTML or Text (defaults to HTML).
cc_recipientslist | str | NonedefaultNoneCC recipient email addresses, as a list or a comma-separated string.
bcc_recipientslist | str | NonedefaultNoneBCC recipient email addresses, as a list or a comma-separated string.
attachmentslist[dict] | list[file] | NonedefaultNoneFile attachments to send with the message. Each entry is a dict with content_bytes or file, or a bare file reference. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
namestr | NonedefaultNoneFile name shown on the attachment. Defaults to the stored file's name, then "attachment".
content_typestr | NonedefaultNoneMIME type of the file. Defaults to the stored file's type, then application/octet-stream.
content_bytesstr | NonedefaultNoneBase64-encoded file contents. Pass content_bytes or file.
filefile | NonedefaultNoneA qbash file reference to attach. Pass content_bytes or file. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
{"status": "success", "message": str}Sends through the Graph /me/sendMail endpoint and saves a copy to Sent Items. Each attachment carries its base64 contents as content_bytes or a qbash file reference as file. Attachments go inline, which Graph limits to files under 3 MB. Graph returns no body for a send, so message is a fixed confirmation string and the id of the sent mail is not available here. Look it up with list-messages on the sentitems folder if you need it.
create_draft()Create a draft email in Outlook.
result = qbash.integrations.outlook_mail.create_draft(
to_recipients=[...],
subject="…",
)
to_recipientslist | strrequiredRecipient email addresses, as a list or a comma-separated string.
subjectstrrequiredEmail subject line.
bodystr | NonedefaultNoneEmail body content (HTML or plain text).
body_typestrdefault"HTML"Body content type, either HTML or Text (defaults to HTML).
cc_recipientslist | str | NonedefaultNoneCC recipient email addresses, as a list or a comma-separated string.
bcc_recipientslist | str | NonedefaultNoneBCC recipient email addresses, as a list or a comma-separated string.
attachmentslist[dict] | list[file] | NonedefaultNoneFile attachments to save on the draft. Each entry is a dict with content_bytes or file, or a bare file reference. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
namestr | NonedefaultNoneFile name shown on the attachment. Defaults to the stored file's name, then "attachment".
content_typestr | NonedefaultNoneMIME type of the file. Defaults to the stored file's type, then application/octet-stream.
content_bytesstr | NonedefaultNoneBase64-encoded file contents. Pass content_bytes or file.
filefile | NonedefaultNoneA qbash file reference to attach. Pass content_bytes or file. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
{"id": str}The result is the draft Graph created, passed through unchanged, so it carries the rest of the Graph message properties alongside id. id is the draft id to pass to update-message, move-message or delete-message. Attachments take content_bytes or file and go inline the way send-message sends them, which Graph limits to files under 3 MB. reply_to is not exposed on this mode even though Graph accepts it on a draft, so set it with update-message once the draft exists.
delete_message()Delete an Outlook message by ID.
result = qbash.integrations.outlook_mail.delete_message(
message_id="…",
)
message_idstrrequiredOutlook message ID to delete.
{"status": "success", "message_id": str, "deleted": True}Graph returns no body for a delete, so message_id is echoed from the request rather than read back and deleted is always True on success. Nothing in this integration restores a deleted message.
download_attachment()Download an attachment from an Outlook message.
result = qbash.integrations.outlook_mail.download_attachment(
message_id="…",
attachment_id="…",
)
message_idstrrequiredOutlook message ID containing the attachment.
attachment_idstrrequiredAttachment ID to download.
fileThe result is a qbash file reference rather than the attachment bytes. The attachment is stored as a qbash file and the reference is {"type": "aisle_input_file", "aisle_input_file": dict}, or {"type": "aisle_input_image", "aisle_input_image": dict} for an image. The inner dict holds file_upload_id, filename, content_type and byte_size. Pass the reference to send-message, another integration or qbash.files. If the file cannot be stored the call returns an error. Only file attachments can be downloaded. An item attachment or a reference attachment has no content bytes on Graph, and asking for one returns an error naming which kind it was.
list_attachments()List attachments on an Outlook message.
result = qbash.integrations.outlook_mail.list_attachments(
message_id="…",
)
message_idstrrequiredOutlook message ID to list attachments for.
{"value": list[dict]}The result is the Graph response itself, with no status wrapper. Each entry carries the attachment metadata Graph sends, including id, name, contentType, size and the @odata.type that says whether it is a file, item or reference attachment. Pass id to download-attachment. This mode sends no page size and takes no continuation token, so only what Graph returns in one response is available.
move_message()Move an Outlook message to a different folder.
result = qbash.integrations.outlook_mail.move_message(
message_id="…",
destination_folder_id="my-folder",
)
message_idstrrequiredOutlook message ID to move.
destination_folder_idstrrequiredTarget mail folder ID.
{"id": str} or {"status": "success"}The result is the message as it exists in the destination folder, passed through from Graph, so it carries the rest of the Graph message properties alongside id. Use the id it carries for later calls, because a moved message does not keep the id it had in the folder it came from. When Graph answers with no content the result is {"status": "success"} instead, so read id with .get.
mark_as_read()Mark an Outlook message as read or unread.
result = qbash.integrations.outlook_mail.mark_as_read(
message_id="…",
is_read=True,
)
message_idstrrequiredOutlook message ID to update.
is_readboolrequiredTrue to mark as read, False to mark as unread.
{"id": str, "isRead": bool} or {"status": "success"}The result is the updated message from Graph, passed through, so it carries the rest of the Graph message properties alongside id and isRead. When Graph answers with no content the result is {"status": "success"} instead, so read isRead with .get.
update_message()Update properties of an Outlook message.
result = qbash.integrations.outlook_mail.update_message(
message_id="…",
)
message_idstrrequiredOutlook message ID to update.
categorieslist | NonedefaultNoneList of category names to assign to the message.
subjectstr | NonedefaultNoneNew subject line (draft only).
bodystr | NonedefaultNoneNew body content (draft only).
body_typestrdefault"HTML"Body content type used when body is given, either HTML or Text (defaults to HTML).
importancestr | NonedefaultNoneMessage importance, either low, normal, or high.
is_readbool | NonedefaultNoneTrue to mark the message as read, False to mark it as unread.
is_delivery_receipt_requestedbool | NonedefaultNoneWhether a delivery receipt is requested.
is_read_receipt_requestedbool | NonedefaultNoneWhether a read receipt is requested.
inference_classificationstr | NonedefaultNoneFocused inbox classification, either focused or other.
to_recipientslist | str | NonedefaultNoneRecipient email addresses to replace the current ones, as a list or a comma-separated string (draft only).
cc_recipientslist | str | NonedefaultNoneCC recipient email addresses to replace the current ones, as a list or a comma-separated string (draft only).
bcc_recipientslist | str | NonedefaultNoneBCC recipient email addresses to replace the current ones, as a list or a comma-separated string (draft only).
reply_tolist | str | NonedefaultNoneReply-to email addresses, as a list or a comma-separated string (draft only).
{"id": str} or {"status": "success"}Only the fields you pass are sent to Graph. Recipient and body edits work on drafts, and Graph rejects them once a message has been sent. The result is the updated message from Graph, passed through unchanged, so the fields you did not send come back as they already were. When Graph answers with no content the result is {"status": "success"} instead, so read the message fields with .get.
list_categories()List available Outlook message categories.
result = qbash.integrations.outlook_mail.list_categories(
filter="…",
)
filterstr | NonedefaultNoneCategory display name to match exactly, ignoring case.
{"value": list[dict]}The result is the Graph response itself, with no status wrapper. Each entry holds id, displayName and color, where color is a preset name such as preset0. filter is applied after the call rather than by Graph, as a whole match on displayName that ignores case, so a partial name leaves value empty. Passing filter also drops every other top level key Graph sent, leaving value on its own.
list_folders()List mail folders in the user's Outlook mailbox.
result = qbash.integrations.outlook_mail.list_folders(
parent_folder_id="my-folder",
filter="…",
)
parent_folder_idstr | NonedefaultNoneParent folder ID to list child folders from.
filterstr | NonedefaultNoneFolder display name to match, or a full OData filter expression.
topint | NonedefaultNoneFolders per page, sent as Graph's $top (Graph's default is 10).
page_tokenstr | NonedefaultNoneAn opaque token from a previous response, the next_page_token it returned. When set, the other paging and filter params are ignored.
{"value": list[dict], "@odata.nextLink": str | None, "next_page_token": str | None}The result is the Graph response itself, with no status wrapper. value holds the child folders of parent_folder_id, or the top level folders when it is left out, and each entry carries at least id and displayName. Pass an id as folder_id to list-messages or as destination_folder_id to move-message. One call returns one Graph page, 10 folders by default or top when set. When Graph has more, the result also carries next_page_token, an opaque token that holds @odata.nextLink. Pass it back as page_token to get the next page, and stop when next_page_token is absent. A page_token has to come from a previous response of this mode and expires after 24 hours, so after an expired or refused token start again from the first page without it. A plain name in filter has to match a folder's displayName in full.
get_initial_delta()Get initial delta state for tracking Outlook message changes.
result = qbash.integrations.outlook_mail.get_initial_delta(
folder_id="my-folder",
)
folder_idstrrequiredMail folder ID to get the initial delta for.
{"delta_link": str}This walks every delta page for the folder to reach the folder's current state and returns the link on its own. The message ids read along the way are dropped on purpose, so the messages already in the folder become the baseline instead of results. Store delta_link and pass it to poll-delta to see what changes after this call.
poll_delta()Poll for Outlook message changes using a delta link.
result = qbash.integrations.outlook_mail.poll_delta(
delta_link="…",
)
delta_linkstrrequiredDelta link from a previous get-initial-delta or poll-delta response.
{"message_ids": list[str], "delta_link": str}message_ids holds the ids of the messages added or changed since the delta link you passed, with removed items left out, and it is an empty list when nothing changed. Only ids come back, so call get-message for the content. Every page behind the link is followed before the result is returned. delta_link is a fresh link, so store it and pass that one next time. A link Graph no longer recognises fails the call, and get-initial-delta has to run again to start a new baseline. A delta_link that is not an https://graph.microsoft.com/v1.0/ URL fails before any request is sent.
list_teams()List teams you have joined.
result = qbash.integrations.microsoft_teams.list_teams()
{"value": list[dict], "@odata.nextLink": str | None} or {"status": "success"}Microsoft Graph's response body is returned unchanged. Nothing here unwraps "value", projects a field or fills a default, so team records carry Graph's own camelCase names and Graph omits a property it has no value for rather than sending null. Read record fields with .get(). One request is made. Graph does not page the joined teams list, so value holds every team you have joined and @odata.nextLink is not expected. A 204 or an empty response body comes back as {"status": "success"} with no "value" key at all.
list_channels()List channels in a team.
result = qbash.integrations.microsoft_teams.list_channels(
team_id="…",
team_name="…",
)
team_idstr | NonedefaultNoneTeam ID (team_id or team_name is required).
team_namestr | NonedefaultNoneTeam name, resolved to ID automatically (team_id or team_name is required).
page_tokenstr | NonedefaultNoneAn opaque token from a previous response, the next_page_token it returned. When set, team_id and team_name are not needed and are ignored.
{"value": list[dict], "@odata.nextLink": str | None, "next_page_token": str | None} or {"status": "success"}Microsoft Graph's response body is returned unchanged, so channel records keep Graph's camelCase names and an absent property is missing rather than null. Read record fields with .get(). One call returns one Graph page. When Graph has more, the result also carries next_page_token, an opaque token that holds @odata.nextLink. Pass it back as page_token to get the next page, and stop when next_page_token is absent. A page_token has to come from a previous response of this mode and expires after 24 hours, so after an expired or refused token start again from the first page without it. A 204 or an empty response body comes back as {"status": "success"} with no "value" key.
send_channel_message()Send a message to a team channel.
result = qbash.integrations.microsoft_teams.send_channel_message(
content="…",
)
team_idstr | NonedefaultNoneTeam ID (team_id or team_name is required).
team_namestr | NonedefaultNoneTeam name (team_id or team_name is required).
channel_idstr | NonedefaultNoneChannel ID (channel_id or channel_name is required).
channel_namestr | NonedefaultNoneChannel name (channel_id or channel_name is required).
contentstrrequiredMessage content (supports HTML).
content_typestrdefault"html"Content type of the message, either html or text.
dict or {"status": "success"}The result is Microsoft Graph's body for the created message, forwarded unchanged. No key is projected, renamed or defaulted here, so the shape is Graph's and cannot be guaranteed from this code. Read everything with .get(), including the new message id as result.get("id"). A 204 or an empty response body comes back as {"status": "success"} instead, which carries no message fields.
send_chat_message()Send a message to a 1:1 or group chat.
result = qbash.integrations.microsoft_teams.send_chat_message(
chat_id="…",
content="…",
)
chat_idstrrequiredChat ID.
contentstrrequiredMessage content (supports HTML).
content_typestrdefault"html"Content type of the message, either html or text.
dict or {"status": "success"}The result is Microsoft Graph's body for the created message, forwarded unchanged. No key is projected, renamed or defaulted here, so the shape is Graph's and cannot be guaranteed from this code. Read everything with .get(), including the new message id as result.get("id"). A 204 or an empty response body comes back as {"status": "success"} instead.
list_chats()List the first page of your chats.
result = qbash.integrations.microsoft_teams.list_chats(
top=10,
)
topintdefault50Number of chats to return, up to 50.
{"value": list[dict], "@odata.nextLink": str | None} or {"status": "success"}Microsoft Graph's response body is returned unchanged, so chat records keep Graph's camelCase names and an absent property is missing rather than null. Read record fields with .get(). The top parameter is passed straight through as Graph's $top, which is a page size rather than a total, and the 50 named in its description is not checked by this code. One request is made and @odata.nextLink is never followed, and this mode takes no continuation parameter, so only that first page is reachable. @odata.nextLink is absent when there is no further page. A 204 or an empty response body comes back as {"status": "success"} with no "value" key.
create_channel()Create a new channel in a team.
result = qbash.integrations.microsoft_teams.create_channel(
display_name="…",
)
team_idstr | NonedefaultNoneTeam ID (team_id or team_name is required).
team_namestr | NonedefaultNoneTeam name (team_id or team_name is required).
display_namestrrequiredChannel display name.
descriptionstr | NonedefaultNoneChannel description.
membership_typestrdefault"standard"Channel membership type, either standard or private.
dict or {"status": "success"}The result is Microsoft Graph's body for the created channel, forwarded unchanged. No key is projected, renamed or defaulted here, so the shape is Graph's and cannot be guaranteed from this code. Read everything with .get(), including the new channel id as result.get("id"). description is only sent when it is a non-empty string, so a channel created without one simply has no description. A 204 or an empty response body comes back as {"status": "success"} instead, which carries no channel fields.
list_channel_messages()List the first page of messages in a channel.
result = qbash.integrations.microsoft_teams.list_channel_messages(
team_id="…",
team_name="…",
)
team_idstr | NonedefaultNoneTeam ID (team_id or team_name is required).
team_namestr | NonedefaultNoneTeam name (team_id or team_name is required).
channel_idstr | NonedefaultNoneChannel ID (channel_id or channel_name is required).
channel_namestr | NonedefaultNoneChannel name (channel_id or channel_name is required).
topintdefault50Number of messages to return.
{"value": list[dict], "@odata.nextLink": str | None} or {"status": "success"}Microsoft Graph's response body is returned unchanged, so message records keep Graph's camelCase names and an absent property is missing rather than null. Read record fields with .get(). The top parameter is passed straight through as Graph's $top, which is a page size rather than a total. One request is made and @odata.nextLink is never followed, and this mode takes no continuation parameter, so only that first page is reachable and older messages cannot be walked from here. @odata.nextLink is absent when there is no further page. Replies are not included, since Graph keeps them on a separate replies collection that no mode reads. A 204 or an empty response body comes back as {"status": "success"} with no "value" key.
reply_to_message()Reply to a message in a channel.
result = qbash.integrations.microsoft_teams.reply_to_message(
message_id="…",
content="…",
)
team_idstr | NonedefaultNoneTeam ID (team_id or team_name is required).
team_namestr | NonedefaultNoneTeam name (team_id or team_name is required).
channel_idstr | NonedefaultNoneChannel ID (channel_id or channel_name is required).
channel_namestr | NonedefaultNoneChannel name (channel_id or channel_name is required).
message_idstrrequiredMessage ID to reply to.
contentstrrequiredReply content (supports HTML).
content_typestrdefault"html"Content type of the reply, either html or text.
dict or {"status": "success"}The result is Microsoft Graph's body for the created reply, forwarded unchanged. No key is projected, renamed or defaulted here, so the shape is Graph's and cannot be guaranteed from this code. Read everything with .get(), including the new reply id as result.get("id"). A 204 or an empty response body comes back as {"status": "success"} instead.
list_events()List all event names in a Mixpanel project.
result = qbash.integrations.mixpanel.list_events(
projectId=0,
)
projectIdintdefaultNoneMixpanel project ID. Defaults to the Project ID stored on the credential.
{"status": "success", "events": list[str], "event_count": int}The events list holds event name strings rather than objects, and event_count is its length. Only the project id is sent, so Mixpanel decides how many names come back and this mode exposes no filter, limit or cursor for reaching any beyond them.
list_event_properties()List properties for a specific Mixpanel event.
result = qbash.integrations.mixpanel.list_event_properties(
eventName="…",
propertyName="…",
fromDate="…",
toDate="…",
)
projectIdintdefaultNoneMixpanel project ID. Defaults to the Project ID stored on the credential.
eventNamestrrequiredName of the event to list properties for.
propertyNamestrrequiredProperty name to analyze.
fromDatestrrequiredStart date for the query (YYYY-MM-DD).
toDatestrrequiredEnd date for the query (YYYY-MM-DD).
typestrdefault"general"Property type: general or histogram.
unitstrdefault"day"Time unit for grouping: day, week, or month.
limitintdefault255Max number of property values to return.
{"status": "success", "properties": dict}The properties value is the Mixpanel events properties response forwarded without reshaping or validation, so its keys are Mixpanel's own and nothing here guarantees them. Run try_call once and read the real keys before indexing. limit caps how many property values Mixpanel includes and defaults to 255.
get_event_property_values()Get unique values for a Mixpanel event property.
result = qbash.integrations.mixpanel.get_event_property_values(
eventName="…",
propertyName="…",
)
projectIdintdefaultNoneMixpanel project ID. Defaults to the Project ID stored on the credential.
eventNamestrrequiredEvent name to query.
propertyNamestrrequiredProperty name to get values for.
limitintdefault255Max number of values to return.
{"status": "success", "values": list[str], "value_count": int}The values list holds distinct property values as strings and value_count is its length. limit caps how many values Mixpanel returns and defaults to 255, so a value_count equal to limit usually means there are more, and this mode exposes no cursor for reaching them.
run_segmentation_query()Run a segmentation query to get event counts and unique users.
result = qbash.integrations.mixpanel.run_segmentation_query(
event="…",
fromDate="…",
toDate="…",
)
projectIdintdefaultNoneMixpanel project ID. Defaults to the Project ID stored on the credential.
eventstrrequiredEvent name to segment on.
fromDatestrrequiredStart date for the query (YYYY-MM-DD).
toDatestrrequiredEnd date for the query (YYYY-MM-DD).
typestrdefault"unique"Measurement type: unique, general, or average.
unitstrdefault"day"Time granularity: day, week, or month.
wherestr | NonedefaultNoneFilter expression (e.g. properties[\"$browser\"] == \"Chrome\").
onstr | NonedefaultNoneProperty expression to segment on.
{"status": "success", "data": dict}The data value is the Mixpanel segmentation response forwarded without reshaping or validation, so its keys are Mixpanel's own and nothing here guarantees them. Run try_call once and read the real keys before indexing.
run_funnel_query()Run a funnel query to analyze conversion across user journeys.
result = qbash.integrations.mixpanel.run_funnel_query(
fromDate="…",
toDate="…",
)
projectIdintdefaultNoneMixpanel project ID. Defaults to the Project ID stored on the credential.
fromDatestrrequiredStart date for the query (YYYY-MM-DD).
toDatestrrequiredEnd date for the query (YYYY-MM-DD).
funnelIdint | NonedefaultNoneMixpanel funnel ID to query.
unitstrdefault"day"Time granularity: day, week, or month.
intervalint | NonedefaultNoneConversion window length in days. Mixpanel uses 14 days when omitted.
wherestr | NonedefaultNoneFilter expression applied to the funnel.
{"status": "success", "data": dict}The data value is the Mixpanel funnels response forwarded without reshaping or validation, so its keys are Mixpanel's own and nothing here guarantees them. Run try_call once and read the real keys before indexing. funnelId refers to a funnel saved in Mixpanel and there is no mode for listing saved funnels, so take the id from the Mixpanel UI.
run_retention_query()Run a retention query to track user engagement over time.
result = qbash.integrations.mixpanel.run_retention_query(
fromDate="…",
toDate="…",
)
projectIdintdefaultNoneMixpanel project ID. Defaults to the Project ID stored on the credential.
fromDatestrrequiredStart date for the query (YYYY-MM-DD).
toDatestrrequiredEnd date for the query (YYYY-MM-DD).
retention_typestrdefault"birth"Retention type: birth or compactness.
unitstrdefault"day"Time granularity: day, week, or month.
born_eventstr | NonedefaultNoneQualifying event for birth retention.
eventstr | NonedefaultNoneReturn event name for the retention query.
wherestr | NonedefaultNoneFilter expression applied to the retention query.
{"status": "success", "data": dict}The data value is the Mixpanel retention response forwarded without reshaping or validation, so its keys are Mixpanel's own and nothing here guarantees them. Run try_call once and read the real keys before indexing.
query_profiles()Query Mixpanel user profiles using the Engage API.
result = qbash.integrations.mixpanel.query_profiles(
projectId=0,
where="…",
)
projectIdintdefaultNoneMixpanel project ID. Defaults to the Project ID stored on the credential.
wherestr | NonedefaultNoneFilter expression for profiles (e.g. properties[\"$country\"] == \"US\").
page_sizeintdefault1000Number of profiles per page.
session_idstr | NonedefaultNoneSession ID for paginating through results.
pageint | NonedefaultNonePage number for pagination.
{"status": "success", "data": dict}The data value is the Engage query response forwarded without reshaping or validation, so the profile records and the pagination fields inside it are Mixpanel's own. Run try_call once and read the real keys before indexing. One page comes back per call, holding page_size profiles and 1000 by default. To continue, read the session id and page number out of data and pass them back as session_id and page.
track_event()Track a single event in Mixpanel.
result = qbash.integrations.mixpanel.track_event(
eventName="…",
)
eventNamestrrequiredName of the event to track.
distinctIdstrdefault"anonymous"Unique identifier for the user associated with this event.
propertiesdictdefault{}Map of event properties to include.
{"status": "success", "result": {"success": True}}Mixpanel answers a tracking call with an acceptance flag and nothing else, so result holds success and never an event id or a copy of the event. A call Mixpanel rejects comes back as an error rather than as success False.
track_batch_events()Track multiple events in a single Mixpanel API call.
result = qbash.integrations.mixpanel.track_batch_events(
events=[...],
)
eventslistrequiredList of event objects, each with event and properties keys.
{"status": "success", "result": {"success": True, "count": int}}count is the length of the events list that was sent rather than a number Mixpanel reported, so it confirms how many events were submitted and nothing about how many were stored. Mixpanel answers for the batch as a whole, and a rejected batch comes back as an error rather than as a per event report.
set_user_profile()Update Mixpanel user profile properties.
result = qbash.integrations.mixpanel.set_user_profile(
distinctId="…",
properties={"key": "value"},
)
distinctIdstrrequiredUnique identifier for the user profile to update.
propertiesdictrequiredMap of profile properties to set.
operationstrdefault"set"Profile operation: set, set_once, add, append, or union.
{"status": "success", "result": {"success": True}}Mixpanel answers a profile update with an acceptance flag and nothing else, so result holds success and never the updated profile. Read the profile back with query_profiles if the new values matter. An operation value outside set, set_once, add, append and union falls back to set.
increment_user_property()Increment numeric properties on a Mixpanel user profile.
result = qbash.integrations.mixpanel.increment_user_property(
distinctId="…",
properties={"key": "value"},
)
distinctIdstrrequiredUnique identifier for the user profile to update.
propertiesdictrequiredMap of property names to increment amounts (numeric values).
{"status": "success", "result": {"success": True}}Sends the Mixpanel add operation, so each value in properties is an amount to add to the current value rather than a replacement. result holds an acceptance flag only and never the resulting totals, so read them back with query_profiles if they matter.
execute_query()Run a SQL statement against a MySQL database. It runs with the rights of the configured account, so point the credential at a read only account to keep it to reads.
result = qbash.integrations.mysql.execute_query(
query="search terms",
)
querystrrequiredOne SQL statement. Use ? placeholders for parameterised values.
output_formatstr | Nonedefault"json_rows"Output format: json_rows, single_value, or columns_and_rows. Any other value falls back to json_rows without an error.
query_timeoutint | str | Nonedefault30000Query timeout in milliseconds. A string of digits is parsed, and anything else falls back to 30000.
query_paramslist | NonedefaultNoneValues bound to the ? placeholders, in order. Anything that is not a list is dropped and the query runs with no bound values, which fails if it holds placeholders.
read_onlybooldefaultFalseRun the statement in a read only transaction that is always rolled back. Only the boolean True enables it, so the string 'true' leaves the statement writable.
retry_attemptsint | str | Nonedefault1Attempts to make when the connection is refused or times out, from 1 to 4. Values outside that range are clamped rather than rejected. Retries wait 15 to 60 seconds each, and only connection errors are retried, never a failing query.
{"status": "success", "result": list[dict], "row_count": int} when output_format is json_rows, or {"status": "success", "result": {"columns": list[str], "rows": list[list], "row_count": int}, "row_count": int} when columns_and_rows, or {"status": "success", "result": str | int | float | bool | list | dict | None, "row_count": 1} when single_valueOne statement per call: the statement is prepared on the server, which refuses a second statement after a semicolon. "status" is always "success" on the ok path. What "result" holds depends on output_format. Under json_rows it is a list of row dicts keyed by column name, and a statement that returns no rows (DDL, INSERT, UPDATE or DELETE) gives an empty list, so the top level "row_count" is 0 for those statements even when rows changed. Only columns_and_rows carries MySQL's own count of fetched or affected rows, in both result["row_count"] and the top level one, so use that format when the affected row count is what you need. Under single_value the result is the first column of the first row as a bare value, None when nothing matched, and the top level "row_count" is 1 either way. Values are coerced to JSON types: DECIMAL becomes str to keep its precision, DATE, DATETIME, TIMESTAMP and TIME become ISO 8601 str (a TIME that is negative or 24 hours or more fails the call, so cast it with CAST(col AS CHAR)), JSON columns become dicts or lists (on MariaDB, where JSON is an alias for LONGTEXT, a str), BIT(n) becomes int when n is not a multiple of 8 and a str of raw bytes (base64 when not UTF-8) otherwise, so select col + 0 to get an int, TINYINT(1) stays an int (0 or 1) rather than a bool, and a binary value that is not valid UTF-8 becomes a base64 str. TIMESTAMP values come back in the session time zone with no offset. With read_only True, MySQL refuses changes to permanent tables and the transaction is rolled back. The account's privileges still bound what runs: a stored procedure that commits inside itself, SET GLOBAL, KILL or SELECT ... INTO OUTFILE are not stopped by a read-only transaction, so use an account without those rights. When query_timeout passes, the call ends with an error and is not retried, but a write the server already started may still finish.
list_schemas()List the databases the configured account can see.
result = qbash.integrations.mysql.list_schemas()
{"status": "success", "schemas": list[dict]}A MySQL schema is a database. Each entry in "schemas" holds "schema_name" and "table_count", where the count covers base tables and leaves out views. The system databases mysql, information_schema, performance_schema and sys are left out. MySQL lists only the databases the account has some privilege on, so a database the account cannot use is absent rather than an error.
list_tables()List the tables and views in a database.
result = qbash.integrations.mysql.list_tables(
schema="…",
)
schemastr | NonedefaultNoneDatabase to list. Omitting it, or passing an empty string, reads the credential's database.
{"status": "success", "schema": str, "tables": list[dict]}"schema" repeats the database that was read, including the default. Each entry in "tables" holds "table_name" and "table_type", where the type is BASE TABLE or VIEW, ordered by type and then name. A database that does not exist, or one the account cannot read, answers with an empty list rather than an error.
describe_table()Describe the columns, indexes, foreign keys and check constraints of a table.
result = qbash.integrations.mysql.describe_table(
table="…",
)
tablestrrequiredTable or view to describe. Missing or empty fails before any query runs.
schemastr | NonedefaultNoneDatabase holding the table. Omitting it, or passing an empty string, reads the credential's database.
{"status": "success", "schema": str, "table": str, "columns": list[dict], "indexes": list[dict], "foreign_keys": list[dict], "check_constraints": list[dict]}Four information_schema queries run one after another and the first error ends the call. A column entry holds "column_name", "data_type", "column_type" (the full MySQL type, such as int unsigned or enum('a','b')), "character_maximum_length", "numeric_precision", "numeric_scale", "is_nullable", "column_default", "is_primary_key" and "column_comment", with is_nullable and is_primary_key spelled YES or NO. An index entry holds "index_name", "type_desc" (BTREE, HASH, FULLTEXT or SPATIAL), "is_unique" and "is_primary_key" as booleans, and a "columns" string of comma separated column names. A foreign key entry holds "fk_name", "column_name", "references_table" (database.table) and "references_column", where a composite key lists its columns comma separated in matching order. A check constraint entry holds "constraint_name" and "check_clause". Servers older than MySQL 8.0.16 do not enforce check constraints, and the list is empty there. A table that does not exist answers with four empty lists rather than an error, so read an empty "columns" as not found.
check_availability()Check availability of up to 50 domain names at once.
result = qbash.integrations.namecheap.check_availability(
domains=[...],
)
domainslistrequiredList of domain names to check availability for (max 50).
{"status": "success", "domains": list[dict], "count": int}Namecheap answers in XML and the service parses it into records, so nothing vendor shaped reaches the script. Every record carries all eleven keys whatever the XML held: domain, available, is_premium, premium_registration_price, premium_renewal_price, premium_restore_price, premium_transfer_price, icann_fee, eap_fee, error_no and description. The four premium price keys plus icann_fee, eap_fee, error_no and description are None when the attribute was missing, empty or not a number, so the keys are present with a null value rather than absent. "available" and "is_premium" are True only when the attribute is exactly "true", so a missing attribute reads as False. A domain Namecheap could not check comes back as a row with error_no set rather than as an error, so check error_no before trusting available. "count" is the number of rows Namecheap returned, which can differ from the number of domains sent. Namecheap caps one check at 50 domains and nothing here splits a longer list.
get_tld_pricing()Get registration pricing for all TLDs or a specific category.
result = qbash.integrations.namecheap.get_tld_pricing(
action="…",
product_category="…",
)
actionstr | Nonedefault"REGISTER"Pricing action type: REGISTER, RENEW, REACTIVATE, or TRANSFER.
product_categorystr | NonedefaultNoneTLD category filter, e.g. "DOMAINS".
{"status": "success", "pricing": list[dict], "count": int, "action": str}"action" echoes the action that was requested, defaulting to REGISTER, rather than anything Namecheap sends back. One row is emitted per price tier, not per TLD, because Namecheap prices each duration separately, so a TLD appears once for every duration it sells and "count" is the number of price rows. Every row carries all nine keys: tld, duration, duration_type, price, regular_price, your_price, promotion_price, additional_cost and currency, with None where the XML attribute was missing or not a number. The request is fixed to ProductType DOMAIN, so hosting and SSL pricing are not reachable through this mode.
get_domain_info()Get registration details for a domain in your account.
result = qbash.integrations.namecheap.get_domain_info(
domain="…",
)
domainstrrequiredDomain name to get info for (must be registered in your account).
{"status": "success", "domain": str, "domain_id": str | None, "owner_name": str | None, "is_owner": bool, "is_premium": bool, "created_date": str | None, "expires_date": str | None, "nameservers": list[str], "whois_guard": str | None, "whois_guard_id": str | None, "whois_guard_expires_date": str | None, "dns_provider": str | None, "is_using_namecheap_dns": bool}The parsed record is merged into the top level rather than nested, so index the fields directly. Every key above is always present and the ones marked None are null when the XML attribute was missing or empty. "status" is the literal "success" and overwrites the domain's own registration status, which Namecheap returns as the Status attribute, so the registration status is not reachable through this mode. "is_owner", "is_premium" and "is_using_namecheap_dns" are True only when their attribute is exactly "true", so a missing attribute reads as False. "nameservers" is a list of host names and is empty when the response carries none. The dates are Namecheap's own strings and are not converted here. The domain has to be registered in the account the credential belongs to.
Namecheap Aftermarket
qbash.integrations.namecheap_aftermarket4 methods · Namecheap Aftermarket connectorlist_sales()List auction sales with cursor-based pagination and rich filtering (price range, TLD, age, backlinks, rankings).
result = qbash.integrations.namecheap_aftermarket.list_sales(
cursor="…",
order_by="…",
)
cursorstr | NonedefaultNonePagination cursor returned by a prior call (use the previous response's next_cursor).
order_bystr | NonedefaultNoneSort field: domain_name, current_price, end_time, start_time, or start_price.
directionstr | NonedefaultNoneSort direction: asc or desc.
idsstr | NonedefaultNoneComma-separated list of sale IDs to fetch (e.g. "abc,def").
namestr | NonedefaultNoneFilter by domain name (e.g. "example.com").
pricestr | NonedefaultNonePrice range as "min_max", "min_", or "_max" (USD, e.g. "100_1000").
tldstr | NonedefaultNoneComma-separated TLD filter (e.g. "com,net,org").
start_datestr | NonedefaultNoneAuction start-date range as Unix-seconds (e.g. "1700000000_1710000000").
end_datestr | NonedefaultNoneAuction end-date range as Unix-seconds (e.g. "1700000000_1710000000").
keywordsstr | NonedefaultNoneComma-separated keyword filter.
agestr | NonedefaultNoneDomain age (years) range, e.g. "2_10".
backlinks_countstr | NonedefaultNoneBacklinks count range, e.g. "100_".
bid_countstr | NonedefaultNoneBid count range, e.g. "1_".
extensions_takenstr | NonedefaultNoneRange for the number of other TLD extensions registered for the same SLD.
name_lengthstr | NonedefaultNoneSLD (second-level domain) character-length range, e.g. "3_6".
cloudflare_rankingstr | NonedefaultNoneCloudflare Radar ranking range.
no_hyphensbool | NonedefaultNoneExclude hyphenated domains.
no_numbersbool | NonedefaultNoneExclude domains containing numbers.
only_numbersbool | NonedefaultNoneOnly return numeric-only domains.
nsfwbool | NonedefaultNoneInclude NSFW domains (API default: true).
{"status": "success", "items": list[dict], "count": int, "next_cursor": str | None, "has_more": bool}One page comes back per call. Pass the "next_cursor" value back as "cursor" for the next page. "next_cursor" is None once Namecheap stops sending one, and "has_more" is False both on the last page and whenever Namecheap omits the flag, so treat False as no known next page rather than as proof the search is exhausted. "count" is the length of "items" on this page, not the size of the result set. No page size parameter is exposed, so the page size is Namecheap's default. Each sale is re-keyed from Namecheap's camelCase to id, name, tld, status, sale_type, price, start_price, renew_price, bid_count, min_bid, start_date, end_date, end_date_extended_ms, created_date, sold_date, registered_date, auction_type, ahrefs_domain_rating, alexa_ranking, umbrella_ranking, backlinks_count, cloudflare_ranking, estibot_value, godaddy_value, extensions_taken, keyword_search_count, keyword_search_query, last_sold_price and last_sold_year. Any key whose value is null is dropped from the record, so read sales with .get(). The values are Namecheap's own with only the key renamed, so cast a price or a date before comparing it.
get_sale()Fetch a single auction sale by its opaque sale ID (returned by list_sales).
result = qbash.integrations.namecheap_aftermarket.get_sale(
sale_id="…",
)
sale_idstrrequiredThe sale's opaque string ID.
{"status": "success", "id": str | None, "name": str | None, "tld": str | None, "sale_type": str | None, "price": int | float | None, "start_price": int | float | None, "renew_price": int | float | None, "bid_count": int | None, "min_bid": int | float | None, "start_date": int | str | None, "end_date": int | str | None, "end_date_extended_ms": int | None, "created_date": int | str | None, "sold_date": int | str | None, "registered_date": int | str | None, "auction_type": str | None, "ahrefs_domain_rating": int | None, "alexa_ranking": int | None, "umbrella_ranking": int | None, "backlinks_count": int | None, "cloudflare_ranking": int | None, "estibot_value": int | float | None, "godaddy_value": int | float | None, "extensions_taken": int | None, "keyword_search_count": int | None, "keyword_search_query": str | None, "last_sold_price": int | float | None, "last_sold_year": int | None}The sale is merged into the top level rather than nested under a key, so index its fields directly. Every field except "status" is dropped when Namecheap returns null for it, so read them with .get() rather than assuming the key is there. "status" is the literal "success" and overwrites the sale's own status field, which the same records carry intact inside list_sales, so the auction status is not reachable through this mode. The values are Namecheap's own with only the key renamed, so cast a price or a date before comparing it. A sale ID that does not exist is an error rather than an empty result.
list_my_bids()List the authenticated user's bids with page-based pagination.
result = qbash.integrations.namecheap_aftermarket.list_my_bids(
page=0,
page_size=10,
)
pageint | NonedefaultNonePage number (1-indexed, default 1).
page_sizeint | NonedefaultNoneItems per page (default 100).
salestr | NonedefaultNoneFilter to bids on a single sale ID.
{"status": "success", "items": list[dict], "count": int, "pages": dict | None}This endpoint pages by number rather than by cursor. "pages" is Namecheap's page block re-keyed to total, per_page, page and last_page, each of those keys dropped when its value is null, and the whole of "pages" is None when Namecheap omits the block, so read it with .get(). There is no has_more here, so compare "page" with "last_page" to decide whether to ask for another page and treat a missing "last_page" as unknown rather than as the end. "count" is the length of "items" on this page. Each bid is re-keyed to id, sale_id, name, is_proxy, amount, max_amount, status, index, created_date and is_leading_bid, with any null valued key dropped, so read bids with .get().
place_bid()Place a proxy bid on a sale. The system automatically bids the minimum needed to win, up to max_amount.
result = qbash.integrations.namecheap_aftermarket.place_bid(
sale_id="…",
max_amount=0.7,
)
sale_idstrrequiredSale ID to bid on (from list_sales).
max_amountfloatrequiredMaximum willing bid in dollars.
{"status": "success", "id": str | None, "sale_id": str | None, "name": str | None, "is_proxy": bool | None, "amount": int | float | None, "max_amount": int | float | None, "index": int | None, "created_date": int | str | None, "is_leading_bid": bool | None}The created bid is merged into the top level rather than nested under a key, so index its fields directly. Every field except "status" is dropped when Namecheap returns null for it, so read them with .get(). "status" is the literal "success" and means Namecheap accepted the bid, not that the bid is winning. It also overwrites the bid's own status field, which the same records carry intact inside list_my_bids, so use "is_leading_bid" here instead. This is a proxy bid: Namecheap bids the minimum needed to win up to max_amount, so "amount" can come back below the value that was sent. The values are Namecheap's own with only the key renamed, so cast an amount before comparing it.
run_suiteql()Run a SuiteQL query (NetSuite's SQL) and return one page of rows. Use it for lists, joins and totals across customers, items, prices, transactions, transaction lines, GL lines and custom records. Columns come back lowercase and checkboxes as 'T' or 'F'.
result = qbash.integrations.netsuite.run_suiteql(
query="search terms",
)
querystrrequiredSuiteQL SELECT statement. Put values in query_params as ? placeholders instead of formatting them into the text.
query_paramslist | NonedefaultNoneValues for the ? placeholders, in order.
limitintdefault1000Rows per page, 1 to 1000. Keep it the same while paging.
cursorstr | NonedefaultNonenext_cursor from the previous page.
{"status": "success", "items": list[dict], "count": int, "total_results": int | None, "has_more": bool, "next_cursor": str | None, "warnings": list[str] | None}Dates come back in the account's date format unless the query converts them, for example TO_CHAR(t.trandate, 'YYYY-MM-DD'). BUILTIN.DF(column) returns the display name of a list or record field instead of its internal ID. Tables that cover most work: transaction (one row per transaction, with id, tranid, trandate, type, status, entity, currency and foreigntotal), transactionline (one row per line, joined on transactionline.transaction = transaction.id, where mainline = 'T' is the header row), transactionaccountingline (GL postings per line, with account, debit, credit and amount in the base currency), previoustransactionlinelink (which line a line came from, such as a receipt or bill line and its purchase order line), customer, item, subsidiary, currency, and custom record tables named by script ID (customrecord_...). Transaction type codes include SalesOrd, PurchOrd, VendBill, ItemRcpt, ItemShip, RtnAuth, CustCred, CustRfnd, CustInvc, CustPymt and Journal. Columns depend on the account's features and customizations, so run SELECT * FROM <table> with limit=1 to see what a table has. Pricing tables depend on the Multiple Prices, Quantity Pricing and Multi-Currency features. Check those tables with the same SELECT * query, or read the item with get_record and expand_sub_resources=True. Pass next_cursor back as cursor with the same limit. Paging stops at 100,000 rows, so for more rows order by id and filter on the last id seen (WHERE id > ? ORDER BY id).
get_record()Get one NetSuite record by internal ID or external ID. With expand_sub_resources it includes sublist lines, and each transaction line keeps its line value, the key update_record uses to change that line.
result = qbash.integrations.netsuite.get_record(
record_type="…",
)
record_typestrrequiredREST record type ID, for example salesOrder, vendorBill, itemFulfillment, customer or customrecord_item_alias.
record_idstr | NonedefaultNoneInternal ID of the record. Pass this or external_id.
external_idstr | NonedefaultNoneExternal ID of the record (letters, numbers, underscores and hyphens). Pass this or record_id.
fieldslist[str] | NonedefaultNoneOnly return these body fields, by field ID. Omit for all.
expand_sub_resourcesbooldefaultFalseInclude sublists (transaction lines, addresses, packages) and subrecords.
{"status": "success", "record": dict}list_record_types lists every record type the role can reach. Items are typed (inventoryItem, assemblyItem, kitItem and others), and SuiteQL's item.itemtype column tells which one an item is. Reference fields come back as {"id": ..., "refName": ...}. Sublists come back as {"items": [...]} with expand_sub_resources=True and as empty objects without it. Transaction lines are in the item sublist, and an item fulfillment's packages and tracking numbers are in its package sublists. lastModifiedDate (lastModified on custom records) is the value update_record takes as if_last_modified. A 404 means no record of this type has that ID.
list_record_types()List the record type IDs the credential's role can reach, including custom record types (customrecord_...). Use search to find one, for example alias or carton.
result = qbash.integrations.netsuite.list_record_types(
search="…",
)
searchstr | NonedefaultNoneOnly return record types whose ID contains this text, ignoring case.
{"status": "success", "record_types": list[dict], "count": int}Each entry has name (the record type ID that get_record and the other modes take) and custom (True for custom record types). Reads NetSuite's metadata catalog, which reflects this account's enabled features and customizations.
describe_record_type()Describe a record type's fields from NetSuite's metadata catalog: IDs, labels, types, custom fields and allowed values. Use it to find custom field IDs and the field names create_record and update_record expect.
result = qbash.integrations.netsuite.describe_record_type(
record_type="…",
)
record_typestrrequiredREST record type ID, for example salesOrder, vendorBill, itemFulfillment, customer or customrecord_item_alias.
searchstr | NonedefaultNoneOnly return fields whose ID or label contains this text, ignoring case.
{"status": "success", "record_type": str, "fields": list[dict], "required": list[str], "count": int}Each field has id, label and type, plus format, nullable, read_only, custom (True for custom fields such as custbody_ and custcol_), enum (allowed values) and reference (the record type a reference or sublist field points to) when NetSuite reports them. required lists what the metadata marks required. A custom form can make more fields mandatory, and a create that misses one fails with an error that names it.
create_record()Create a NetSuite record from field values. Pass external_id (a stable key from the source, such as the customer's PO number) and a repeat call returns the existing record instead of creating a second one. Without external_id, every call creates a new record. Reads the record back after the write. Payments, refunds, deposits and journal entries are refused.
result = qbash.integrations.netsuite.create_record(
record_type="…",
values={"key": "value"},
)
record_typestrrequiredREST record type ID, for example salesOrder, vendorBill, itemFulfillment, customer or customrecord_item_alias.
valuesdictrequiredField IDs and values for the new record, with sublists as {"items": [...]}.
external_idstr | NonedefaultNoneStable key from the source system (letters, numbers, underscores and hyphens). Stored as the record's externalId and used to skip duplicates.
verifybooldefaultTrueRead the record back with its sublists after the write and return it as record.
{"status": "success", "created": bool, "id": str | None, "external_id": str | None, "record": dict | None, "warnings": list[str] | None}values uses the field IDs get_record returns. Reference fields take an internal ID as {"id": "123"}, for example entity, subsidiary, currency, location and item. Dates are "YYYY-MM-DD". A sublist is {"items": [...]}, for example "item": {"items": [{"item": {"id": "77"}, "quantity": 5, "rate": 12.5}]}. Custom fields go in by script ID. NetSuite uses the role's preferred form unless values sets customForm, and the form decides which fields are mandatory. In a OneWorld account, entities and transactions need a subsidiary. With external_id the record is looked up by that ID first. When it exists, nothing is written: created is False and record is the existing record. When the create call fails but a record with that ID exists afterwards (a parallel run, or a timeout after NetSuite saved it), the result is the same with a warning. Without external_id every call creates a new record. After the write the record is read back with its sublists, so the task can check what NetSuite stored, including sourced prices, tax and defaults. If that read fails the create still stands, and warnings says why. Record types that move money or post to the general ledger are refused: payments, refunds, deposits, checks, fund transfers, cash sales, credit card transactions and journal entries.
update_record()Update fields and sublist lines on an existing NetSuite record. A line sent with its line value from get_record changes that line. A line without one is added, and lines left out stay. Pass if_last_modified to refuse the update when the record's lastModifiedDate no longer matches an earlier read. Payments, refunds, deposits and journal entries are refused.
result = qbash.integrations.netsuite.update_record(
record_type="…",
values={"key": "value"},
)
record_typestrrequiredREST record type ID, for example salesOrder, vendorBill, itemFulfillment, customer or customrecord_item_alias.
record_idstr | NonedefaultNoneInternal ID of the record. Pass this or external_id.
external_idstr | NonedefaultNoneExternal ID of the record (letters, numbers, underscores and hyphens). Pass this or record_id.
valuesdictrequiredFields to change, with sublist lines as {"items": [...]}. Include line to change an existing line.
replace_sublistslist[str] | NonedefaultNoneSublists to rewrite in full, for example ["item"]. Lines not in the request are removed.
if_last_modifiedstr | NonedefaultNonelastModifiedDate from an earlier read. The update is refused when the record's value differs.
verifybooldefaultTrueRead the record back with its sublists after the write and return it as record.
{"status": "success", "id": str | None, "record": dict | None, "warnings": list[str] | None}values holds only the fields to change, in the same shape as create_record. To change a transaction line, read the record with get_record(expand_sub_resources=True) and send that line's line value and item with the fields to change, for example "item": {"items": [{"line": 3, "item": {"id": "77"}, "quantity": 8}]}. A line sent without line is added, so sending it twice adds it twice. To apply an approved amendment once, read the record, compare it with the amendment, send only what still differs, and pass the record's lastModifiedDate as if_last_modified. replace_sublists rewrites the named sublists. Request lines with a matching line are updated and the rest are added. Every line not in the request is removed. After the write the record is read back with its sublists. if_last_modified is compared just before the update, so an edit made between that check and the update goes through unseen.
search()Search the pages and databases shared with the integration by title. Results are pages and data sources (a data source holds a database's rows and schema, and its id is what query_data_source takes).
result = qbash.integrations.notion.search(
query="search terms",
object_type="…",
)
querystr | NonedefaultNoneText to match against titles. Omit to list everything shared with the integration.
object_typestr | NonedefaultNone"page" or "data_source" to return only that kind of result.
sort_directionstr | NonedefaultNone"descending" or "ascending" to sort by last_edited_time. Omit for relevance order.
cursorstr | NonedefaultNoneThe next_cursor of a previous call, to read the page after it. Omit to start at the first page.
page_sizeint | NonedefaultNoneResults per page, from 1 to 100. Notion's default is 100.
{"status": "success", "results": list[dict], "count": int, "next_cursor": str | None, "has_more": bool}POST /v1/search. Each result has id, object ("page" or "data_source"), title, url, parent, created_time, last_edited_time and in_trash. A data source result also has database_id. A personal access token searches everything its owner can see. A connection token only sees pages and databases added to it from their Connections menu in Notion. Search matches titles only. One page of results is returned per call. Pass next_cursor back as cursor to read the next page. next_cursor is None and has_more is False on the last page.
get_database()Get a database's title, URL and the data sources it contains. Use a data source id from it with query_data_source and create_page.
result = qbash.integrations.notion.get_database(
database_id="…",
)
database_idstrrequiredDatabase ID or the database's notion.so URL.
{"status": "success", "database": {"id": str, "title": str, "description": str, "url": str, "parent": dict, "is_inline": bool, "in_trash": bool, "created_time": str, "last_edited_time": str, "icon": str | None, "data_sources": list[{"id": str, "name": str}]}}GET /v1/databases/{id}. The database id is the 32 character id in the database's URL, before any ?v= view id. The schema is on the data source, so call get_data_source for property names and types.
get_data_source()Get a data source's schema: each property's id, type and, for select, multi_select and status, the option names.
result = qbash.integrations.notion.get_data_source(
data_source_id="…",
database_id="…",
)
data_source_idstr | NonedefaultNoneData source ID. Pass this or database_id. Get it from get_database or search.
database_idstr | NonedefaultNoneDatabase ID or the database's notion.so URL, used when data_source_id is not set. The database must hold exactly one data source.
{"status": "success", "data_source": {"id": str, "title": str, "description": str, "url": str | None, "database_id": str, "in_trash": bool, "created_time": str, "last_edited_time": str, "properties": dict}}GET /v1/data_sources/{id}. properties maps each property name to {"id", "type"}, plus "options" (list of names) for select, multi_select and status, "data_source_id" for relation and "format" for number. Use the names and types to build filters for query_data_source and values for create_page and update_page.
query_data_source()Query the rows of a Notion database (a data source), with optional Notion filter and sorts dicts. Each row comes back with simple property values keyed by property name.
result = qbash.integrations.notion.query_data_source(
data_source_id="…",
database_id="…",
)
data_source_idstr | NonedefaultNoneData source ID. Pass this or database_id. Get it from get_database or search.
database_idstr | NonedefaultNoneDatabase ID or the database's notion.so URL, used when data_source_id is not set. The database must hold exactly one data source.
filterdict | NonedefaultNoneNotion filter object, such as {"property": "Status", "status": {"equals": "Ready"}}.
sortslist[dict] | NonedefaultNoneNotion sort objects, such as [{"property": "Due", "direction": "ascending"}] or [{"timestamp": "created_time", "direction": "descending"}].
filter_propertieslist[str] | NonedefaultNoneProperty ids to include on each page. Omit for all properties.
cursorstr | NonedefaultNoneThe next_cursor of a previous call, to read the page after it. Omit to start at the first page.
page_sizeint | NonedefaultNoneResults per page, from 1 to 100. Notion's default is 100.
{"status": "success", "data_source_id": str, "pages": list[dict], "count": int, "next_cursor": str | None, "has_more": bool}POST /v1/data_sources/{id}/query. filter and sorts are passed to Notion as given, in Notion's query format, for example filter={"property": "Status", "status": {"equals": "Ready"}} and sorts=[{"property": "Due", "direction": "ascending"}]. Combine conditions with {"and": [...]} or {"or": [...]}. Each page has id, url, title, parent, created_time, last_edited_time, in_trash, icon and properties. properties maps each property name to a simple value: text for title and rich_text, a number, an option name for select and status, a list of names for multi_select, {"start", "end"} for date, a bool for checkbox, a list of page ids for relation, a list of {"id", "name", "email"} for people, a list of {"name", "url"} for files, and the computed value for formula and rollup. Passing database_id costs one extra request to look up the data source. Notion stops a query at 10,000 results. One page of results is returned per call. Pass next_cursor back as cursor to read the next page. next_cursor is None and has_more is False on the last page.
get_page()Get a page's title, URL, parent and properties as simple values keyed by property name.
result = qbash.integrations.notion.get_page(
page_id="…",
)
page_idstrrequiredPage ID, with or without dashes, or the page's notion.so URL.
{"status": "success", "page": {"id": str, "url": str, "title": str | None, "parent": dict, "created_time": str, "last_edited_time": str, "in_trash": bool, "icon": str | None, "properties": dict}}GET /v1/pages/{id}. properties uses the same simple values as query_data_source. The page body is not included, so call get_page_content for it. parent is {"type", "id"} where type is data_source, database, page, block or workspace.
get_page_content()Get a page's body as Notion-flavored markdown, ready to pass to a model or write into a report.
result = qbash.integrations.notion.get_page_content(
page_id="…",
)
page_idstrrequiredPage ID, with or without dashes, or the page's notion.so URL. A block id from unknown_block_ids also works.
{"status": "success", "page_id": str, "markdown": str, "truncated": bool, "unknown_block_ids": list[str]}GET /v1/pages/{id}/markdown. Nested blocks, headings, lists, to-dos, tables and code are all rendered. Blocks Notion cannot render as markdown (bookmarks, embeds, link previews) or that the integration cannot see appear as <unknown> tags and their ids are listed in unknown_block_ids. Pages over about 20,000 blocks come back with truncated True. Pass an id from unknown_block_ids as page_id to read the rest.
get_block_children()List the direct child blocks of a page or block, each with its type and plain text. Use it to find block ids or walk nested blocks.
result = qbash.integrations.notion.get_block_children(
block_id="…",
)
block_idstrrequiredPage or block ID, or a notion.so page URL.
cursorstr | NonedefaultNoneThe next_cursor of a previous call, to read the page after it. Omit to start at the first page.
page_sizeint | NonedefaultNoneResults per page, from 1 to 100. Notion's default is 100.
{"status": "success", "blocks": list[dict], "count": int, "next_cursor": str | None, "has_more": bool}GET /v1/blocks/{id}/children. Each block has id, type, text, has_children, in_trash, created_time and last_edited_time, plus checked for to_do, language for code, and url for image, file, pdf, video, audio, bookmark and embed blocks. Only direct children are returned. Call again with a block's id when has_children is True. For the whole page as text, get_page_content is one call. One page of results is returned per call. Pass next_cursor back as cursor to read the next page. next_cursor is None and has_more is False on the last page.
list_users()List the people and bots in the Notion workspace, with ids to use in people properties. Needs a connection token with user information turned on. Personal access tokens cannot list users.
result = qbash.integrations.notion.list_users(
cursor="…",
page_size=10,
)
cursorstr | NonedefaultNoneThe next_cursor of a previous call, to read the page after it. Omit to start at the first page.
page_sizeint | NonedefaultNoneResults per page, from 1 to 100. Notion's default is 100.
{"status": "success", "users": list[{"id": str, "type": str, "name": str, "email": str | None, "avatar_url": str | None}], "count": int, "next_cursor": str | None, "has_more": bool}GET /v1/users. type is "person" or "bot". email is only filled when the integration has the user information with email capability. Guests are not listed. One page of results is returned per call. Pass next_cursor back as cursor to read the next page. next_cursor is None and has_more is False on the last page.
create_page()Create a page as a new row in a database (pass data_source_id or database_id) or as a child of a page (pass parent_page_id), with a title, simple property values and a markdown body.
result = qbash.integrations.notion.create_page(
data_source_id="…",
database_id="…",
)
data_source_idstr | NonedefaultNoneData source ID. Pass this or database_id. Get it from get_database or search.
database_idstr | NonedefaultNoneDatabase ID or the database's notion.so URL, used when data_source_id is not set. The database must hold exactly one data source.
parent_page_idstr | NonedefaultNoneCreate the page under this page instead of in a database.
titlestr | NonedefaultNonePage title. For a database row it fills the title property.
propertiesdict | NonedefaultNoneProperty name to simple value, such as {"Status": "Ready", "Due": "2026-10-01", "Tags": ["blog"]}.
contentstr | NonedefaultNonePage body as markdown.
iconstr | NonedefaultNoneAn emoji, or an https image URL.
{"status": "success", "page": dict}POST /v1/pages. Pass exactly one parent: data_source_id, database_id or parent_page_id. properties maps property names (or ids) to simple values, converted using the data source schema: str for title, rich_text, url, email, phone_number, select and status, a number, a list of names for multi_select, an ISO date str or {"start", "end", "time_zone"} for date, a bool for checkbox, page ids for relation, user ids for people, file urls for files. None clears a property. A dict keyed by the property type, such as {"select": {"name": "Done"}}, is sent unchanged. Formula, rollup and timestamp properties cannot be set. A page under another page only has a title. content is Notion-flavored markdown (headings, lists, - [ ] to-dos, code fences, tables, > quotes) and becomes the page body. A database parent costs one or two extra requests to read the schema. The page dict has the same keys as get_page.
update_page()Update a page's title, property values or icon. Properties take the same simple values as create_page and unlisted properties are left alone.
result = qbash.integrations.notion.update_page(
page_id="…",
)
page_idstrrequiredPage ID, with or without dashes, or the page's notion.so URL.
propertiesdict | NonedefaultNoneProperty name to simple value, such as {"Status": "Done", "Score": 8}.
titlestr | NonedefaultNoneNew page title.
iconstr | NonedefaultNoneAn emoji, or an https image URL.
{"status": "success", "page": dict}PATCH /v1/pages/{id}. Reads the page first to learn each property's type, then converts properties the way create_page does. None clears a property. The page body is not changed, so use append_content for that. The page dict has the same keys as get_page.
append_content()Append markdown to the end of a page's body, such as a report section or a run log entry.
result = qbash.integrations.notion.append_content(
page_id="…",
content="…",
)
page_idstrrequiredPage ID, with or without dashes, or the page's notion.so URL.
contentstrrequiredMarkdown to add at the end of the page.
{"status": "success", "page_id": str}PATCH /v1/pages/{id}/markdown with an insert at the end of the page. content is Notion-flavored markdown (headings, lists, - [ ] to-dos, code fences, tables, > quotes). Notion refuses one write that would create more than about 5,000 blocks, so split very long content over several calls.
append_blocks()Append Notion block objects to the end of a page or block. A plain str in the list becomes a paragraph.
result = qbash.integrations.notion.append_blocks(
block_id="…",
children=[...],
)
block_idstrrequiredPage or block ID to append to, or a notion.so page URL.
childrenlistrequiredNotion block dicts, or strs for plain paragraphs.
{"status": "success", "block_ids": list[str], "count": int}PATCH /v1/blocks/{id}/children. children are Notion block objects, such as {"type": "heading_2", "heading_2": {"rich_text": [{"text": {"content": "Summary"}}]}}. Lists longer than 100 are sent in batches of 100 in order. Notion accepts two levels of nested children per block. Use it for block types markdown cannot express, and append_content for everything else. block_ids are the ids of the new top-level blocks.
trash_page()Move a page or database row to the Notion trash, or restore it with restore=True.
result = qbash.integrations.notion.trash_page(
page_id="…",
)
page_idstrrequiredPage ID, with or without dashes, or the page's notion.so URL.
restorebooldefaultFalseTrue takes the page out of the trash instead.
{"status": "success", "page_id": str, "in_trash": bool}PATCH /v1/pages/{id} with in_trash. Pages in the trash stay restorable in Notion for 30 days and drop out of search and query_data_source results.
search()Execute a search query against an OpenSearch index.
result = qbash.integrations.opensearch.search(
index="…",
body={"key": "value"},
)
indexstrrequiredIndex name or pattern
bodydictrequiredFull search request body, e.g. {"query": {"match_all": {}}}. May also include 'aggs', 'sort', '_source'. Set size to 0 for an aggregation without hits.
sizeint | Nonedefault10Max results to return
request_timeoutint | Nonedefault300000Request timeout (ms). For search it is also applied as the server-side query timeout.
connect_timeoutint | Nonedefault10000Timeout (ms) for opening the connection to the cluster, counted on top of the request timeout.
{"took": int, "timed_out": False, "_shards": dict, "hits": {"total": dict | None, "hits": list[dict]}, "aggregations": dict | None} or str when the response is larger than 500KBThe matching documents are nested at hits.hits, each with its own _source, and the count is at hits.total.value. The body is forwarded as given, so a body carrying track_total_hits false comes back with no total inside hits at all, and hits.total needs .get() rather than indexing whenever total tracking may be off. aggregations is present only when the request body asks for aggs, so read it with .get(). timed_out is always false on success because a cluster-side timeout is turned into an error rather than returned as partial results. A response larger than 500KB is not decoded at all and comes back as a JSON string cut off at 500KB with a TRUNCATED marker appended, so narrow the query or lower size rather than indexing the result. The size parameter overwrites any size already set in body. There is no scroll or point-in-time support, so page by putting from or search_after in body.
list_indices()List all indices in the OpenSearch cluster.
result = qbash.integrations.opensearch.list_indices(
request_timeout=0,
connect_timeout=0,
)
request_timeoutint | Nonedefault300000Request timeout (ms). For search it is also applied as the server-side query timeout.
connect_timeoutint | Nonedefault10000Timeout (ms) for opening the connection to the cluster, counted on top of the request timeout.
list[dict], each {"index": str, "health": str | None, "status": str, "docs.count": str | None, "store.size": str | None}, or str when the response is larger than 500KBThe result is a bare list rather than a map, so index it with result[0] and not by a key. The columns are fixed to the five named here, and the _cat endpoint reports every value as a string, so docs.count needs int() before arithmetic and its key holds a dot. A closed index, or one whose shards are unavailable, reports null for health, docs.count and store.size, and those values are passed through as they arrive, so read the three metrics with .get() and check for None before converting. Only index and status are there on every row. A response larger than 500KB comes back as a JSON string cut off at 500KB with a TRUNCATED marker appended.
get_mapping()Get the field mapping for an OpenSearch index.
result = qbash.integrations.opensearch.get_mapping(
index="…",
)
indexstrrequiredIndex name
request_timeoutint | Nonedefault300000Request timeout (ms). For search it is also applied as the server-side query timeout.
connect_timeoutint | Nonedefault10000Timeout (ms) for opening the connection to the cluster, counted on top of the request timeout.
dict keyed by concrete index name, each {"mappings": dict}, or str when the response is larger than 500KBThe top level is keyed by the concrete index names the request matched, not by the index value that was sent, so an alias or a wildcard such as logs-* returns one entry per underlying index and result[index] can raise a KeyError. Read the keys off the result instead. Field definitions sit under mappings.properties, but an index with no explicit mappings answers with mappings as an empty dict and no properties key at all, so reach it with .get('properties', {}) rather than indexing. A mapping for a wide index can pass 500KB, in which case it comes back as a JSON string cut off at 500KB with a TRUNCATED marker appended.
count()Count the documents in an OpenSearch index that match a query.
result = qbash.integrations.opensearch.count(
index="…",
)
indexstrrequiredIndex name or pattern, e.g. logs-*
bodydict | NonedefaultNoneQuery DSL body limiting what is counted, e.g. {"query": {"match": {"status": "error"}}}. Leave it out to count every document in the index.
request_timeoutint | Nonedefault300000Request timeout (ms). For search it is also applied as the server-side query timeout.
connect_timeoutint | Nonedefault10000Timeout (ms) for opening the connection to the cluster, counted on top of the request timeout.
{"count": int, "_shards": dict}count is the number of matching documents, and it covers every document in the index when body is left out. No documents come back, so reach for search when the matches themselves are needed. _shards carries total, successful, skipped and failed, and a count is still returned when some shards fail, so read _shards.failed before treating the number as complete. body takes the same query DSL as search, but the count API rejects keys that only make sense for a search, such as size, sort and aggs, with an error rather than ignoring them. An index pattern such as logs-* counts across every index it matches.
cluster_health()Get the health of the OpenSearch cluster.
result = qbash.integrations.opensearch.cluster_health(
request_timeout=0,
connect_timeout=0,
)
request_timeoutint | Nonedefault300000Request timeout (ms). For search it is also applied as the server-side query timeout.
connect_timeoutint | Nonedefault10000Timeout (ms) for opening the connection to the cluster, counted on top of the request timeout.
dict carrying at least {"cluster_name": str, "status": "green" | "yellow" | "red", "number_of_nodes": int, "number_of_data_nodes": int, "active_shards": int, "unassigned_shards": int}status is green when every shard is assigned, yellow when replicas are missing and red when a primary shard is unassigned, so a red cluster can still answer searches against the indices it kept. The response is the cluster's own, and which keys it carries varies by OpenSearch version, so read anything past cluster_name and status with .get(). The shard counts cover the whole cluster rather than one index, so pair a yellow or red status with list_indices to find which index is degraded. There is no index parameter, so this mode cannot report on a single index.
list_models()List the Pangram detection models this API key can use, in Pangram's order.
result = qbash.integrations.pangram.list_models()
{"status": "success", "models": list[str]}GET /models. The list depends on the account's plan and on which models are up, so read it rather than hard-coding a name. default follows Pangram's current default model and pangram-4 pins Pangram 4. Pass one as model to detect_ai or submit_bulk.
detect_ai()Check whether a text was written by AI, edited with AI or written by a person. Returns the share of each and a label for every segment. Pangram bills it per 100 words.
result = qbash.integrations.pangram.detect_ai(
text="…",
)
textstrrequiredThe text to check.
modelstrdefault"default"A model selector from list_models. default follows Pangram's current model, and pangram-4 pins one generation.
max_wait_secondsintdefault60How long to wait for Pangram to finish before returning, 0 to 300 seconds.
{"status": "success", "task_id": str, "stage": str | None, "complete": bool, "result": dict | None}Creates a task with POST /task and polls GET /task/{task_id} until it finishes or max_wait_seconds pass. When it finishes, complete is True and result has prediction_short (AI, Human or Mixed), headline, prediction (one sentence), fraction_ai, fraction_ai_assisted and fraction_human (each 0 to 1, together 1), num_ai_segments, num_ai_assisted_segments, num_human_segments, version (the model version, such as 4.1), text (the text as analyzed, which Pangram 4 may normalize) and windows. Each window is one segment of the text, with text, label (AI-Generated, AI-Assisted or Human Written), ai_assistance_score (0 to 1), confidence (High, Medium or Low), start_index and end_index (character offsets into result's text, end exclusive), word_count and token_length. Pangram 4 windows also carry is_humanized and humanizer_score, which say whether a humanizer tool reworded AI text. When the wait runs out first, complete is False, result is None and stage is the stage Pangram last reported (None when no status check got an answer), so pass task_id to get_detection later. A text Pangram cannot analyze, such as one with no words, returns an error with Pangram's reason. Pangram bills each started block of 100 words, at least one block per call, and limits how many requests each key can make. detect_ai_with_dashboard_link runs the same check and publishes the result as a dashboard page.
get_detection()Read a detection task that detect_ai or detect_ai_with_dashboard_link returned before it finished.
result = qbash.integrations.pangram.get_detection(
task_id="…",
)
task_idstrrequiredThe task_id a detection mode returned.
max_wait_secondsintdefault0How long to wait for the task to finish before returning, 0 to 300 seconds. 0 reads it once.
{"status": "success", "task_id": str, "stage": str | None, "complete": bool, "result": dict | None}GET /task/{task_id}, with the same result as detect_ai. max_wait_seconds above 0 keeps polling until the task finishes or the wait runs out. A task created with another API key answers 403. A task Pangram could not analyze returns an error with Pangram's reason.
detect_ai_with_dashboard_link()Run detect_ai and publish the result as a Pangram dashboard page that anyone with the link can open.
result = qbash.integrations.pangram.detect_ai_with_dashboard_link(
text="…",
)
textstrrequiredThe text to check.
modelstrdefault"default"A model selector from list_models. default follows Pangram's current model, and pangram-4 pins one generation.
max_wait_secondsintdefault60How long to wait for Pangram to finish before returning, 0 to 300 seconds.
{"status": "success", "task_id": str, "stage": str | None, "complete": bool, "result": dict | None}The same call as detect_ai with Pangram's public_dashboard_link option, so the finished result also has dashboard_link, a pangram.com page that shows the analyzed text and its labels to anyone with the URL. That makes this mode a write: a Test run skips it unless writes are turned on. When the wait runs out first, get_detection returns the link once the task finishes. Pangram bills each started block of 100 words, at least one block per call, and limits how many requests each key can make.
submit_bulk()Start a bulk AI detection job for many texts, billed 20% below detect_ai. Returns a bulk_id for get_bulk and list_bulk_results.
result = qbash.integrations.pangram.submit_bulk(
texts=[...],
items={"key": "value"},
)
textslist[str] | NonedefaultNoneThe texts to check, when no IDs of your own are needed.
itemslist[dict] | NonedefaultNoneThe texts to check with an ID of your own for each, such as a row or ticket ID.
textstrrequiredThe text to check.
idstr | NonedefaultNoneYour ID for this text, unique within the job.
modelstrdefault"default"A model selector from list_models. default follows Pangram's current model, and pangram-4 pins one generation.
idempotency_keystr | NonedefaultNoneA key of up to 255 characters that makes a resubmit of the same job safe. Omit it and one is generated.
{"status": "success", "bulk": dict}POST /bulk. Pass texts as a list of strings, or items as a list of dicts that each hold text and an optional id of your own, which comes back with the item's result. Pass one of the two. One model applies to every item. bulk has bulk_id, status (queued, or failed when every item failed validation), total_items, accepted_items (index, id and task_id for each) and failed_items (index, id, stage and error for each). A job is capped at 1,000 billable units, counted per item as one unit for each started 100 words (1,000 words on Pangram 3), so a bigger job returns an error and has to be split. Call get_bulk with max_wait_seconds to wait for the job, then list_bulk_results. Bulk jobs and their results are kept for 48 hours after the job finishes. Every submission sends an idempotency key, so when the connection drops before Pangram answers, the error names the key and resubmitting with it returns the same job instead of billing a second one.
get_bulk()Check a bulk job's status and item counts, optionally waiting for it to finish.
result = qbash.integrations.pangram.get_bulk(
bulk_id="…",
)
bulk_idstrrequiredThe bulk_id submit_bulk returned.
max_wait_secondsintdefault0How long to wait for the job to finish before returning, 0 to 300 seconds. 0 reads it once.
{"status": "success", "bulk": dict, "complete": bool}GET /bulk/{bulk_id}. bulk has bulk_id, status (queued, running, succeeded, failed or partial), total_items, accepted, succeeded, failed, created_at and completed_at. The two times are Unix epoch seconds sent as a string, such as "1760000000.0", and completed_at is None until the job finishes. complete is True once status is succeeded, failed or partial. max_wait_seconds above 0 keeps polling until then or until the wait runs out. A long job can take several calls, with qbash.sleep between them.
list_bulk_results()Page through a bulk job's results: each item's detection result, and the items that failed.
result = qbash.integrations.pangram.list_bulk_results(
bulk_id="…",
)
bulk_idstrrequiredThe bulk_id submit_bulk returned.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault100Submitted items per page, from 1 through 1000.
{"status": "success", "bulk_id": str, "total_items": int, "items": list[dict], "failed_items": list[dict], "next_cursor": str | None}GET /bulk/{bulk_id}/results. A page covers limit submitted items in order. items holds the items that succeeded or are still running, each with index (its position in the submitted list), id (yours, when given), task_id, stage, error and result. result has the same keys as detect_ai's result and is None while the item runs. failed_items holds index, id, task_id, stage and error for each item on the page that failed. Pass next_cursor back as cursor until it is None. Bulk jobs and their results are kept for 48 hours after the job finishes.
list_bulk_items()Page through a bulk job's items with each one's stage and error, without the results.
result = qbash.integrations.pangram.list_bulk_items(
bulk_id="…",
)
bulk_idstrrequiredThe bulk_id submit_bulk returned.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault100Submitted items per page, from 1 through 1000.
{"status": "success", "bulk_id": str, "total_items": int, "items": list[dict], "next_cursor": str | None}GET /bulk/{bulk_id}/items. Each item has index, id (yours, when given), task_id, stage (STAGE_SUCCESS, STAGE_FAILED or a stage still running) and error. Lighter than list_bulk_results when a task only needs to know which items failed. Pass next_cursor back as cursor until it is None. Bulk jobs and their results are kept for 48 hours after the job finishes.
detect_ai_file()Check a stored document, such as a PDF or Word file, for AI writing. Pangram reads the text out of the file.
result = qbash.integrations.pangram.detect_ai_file(
file=qbash.inputs["file"],
)
filefilerequiredThe document to check: a file from qbash.inputs, another integration's file result, or its file_upload_id. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
{"status": "success", "filename": str, "result": dict}Sends the file to Pangram's file API, which extracts the text and runs Pangram's default model. The model cannot be chosen. result has the same keys as detect_ai's result, so text holds the text Pangram extracted. Pangram answers 415 for a file type it does not read and 413 for a file over its size limit. The file is read into memory, up to 100 MB. Pangram bills each started block of 100 words, at least one block per call, and limits how many requests each key can make.
detect_ai_file_with_dashboard_link()Check a stored document for AI writing and publish the result as a Pangram dashboard page that anyone with the link can open.
result = qbash.integrations.pangram.detect_ai_file_with_dashboard_link(
file=qbash.inputs["file"],
)
filefilerequiredThe document to check: a file from qbash.inputs, another integration's file result, or its file_upload_id. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
{"status": "success", "filename": str, "result": dict}The same call as detect_ai_file with Pangram's public_dashboard_link option, so result has dashboard_link, a pangram.com page that shows the document's result to anyone with the URL. That makes this mode a write: a Test run skips it unless writes are turned on.
check_plagiarism()Check a text against online sources for copied passages. Returns each copied passage with the source URL it matched.
result = qbash.integrations.pangram.check_plagiarism(
text="…",
)
textstrrequiredThe text to check.
{"status": "success", "result": dict}POST to Pangram's plagiarism API, which answers once the search is done. result has plagiarism_detected (bool), percent_plagiarized (0 to 100, the share of sentences that matched), total_sentences, plagiarized_sentences and plagiarized_content. plagiarized_content lists each copied passage with text, source_url, and start_index and end_index (character offsets into result's text). result also has text, the checked text.
get_deal()Get a single deal by ID.
result = qbash.integrations.pipedrive.get_deal(
deal_id=0,
)
deal_idstr | intrequiredDeal ID.
{"status": "success", "deal": dict}"deal" holds id, title, value, currency, status, stage_id, pipeline_id, person_id, org_id, owner_id, expected_close_date, add_time, update_time, won_time and lost_time, and nothing else. Pipedrive custom fields and any column not in that list are dropped. A key Pipedrive has no value for at all is left out of the dict rather than set to null, so read every field with .get() and never index one directly. Only an unset field is dropped, so an empty string, an empty list, False and 0 are all present as real values and .get() with a fallback will not replace them. A deal id that does not exist comes back as an error rather than an empty result.
search_deals()Search deals by term.
result = qbash.integrations.pipedrive.search_deals(
term="…",
limit=10,
)
termstr | Nonedefault""Search term matched against deal titles and notes. Pipedrive rejects a term shorter than two characters.
limitint | str | Nonedefault10Maximum deals to return in the single page fetched. A string of digits is parsed, and anything else falls back to 10.
{"status": "success", "deals": list[dict], "count": int}Each deal carries the same fixed field set as get_deal, with unset fields left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read them with .get(). "count" is the length of "deals" and not the total Pipedrive holds, so it cannot tell you whether more matches exist. One page is returned, capped by limit, and no cursor comes back, so raising limit is the only way to see more. A term that matches nothing gives an empty list and a count of 0 rather than an error, and so does a response Pipedrive returns in an unexpected shape.
get_deals()List deals with filtering.
result = qbash.integrations.pipedrive.get_deals(
limit=10,
cursor="…",
)
limitint | str | NonedefaultNoneMaximum deals to return in the page fetched, at most 500. Omit for Pipedrive's default of 100. A value that is not a whole number is ignored.
cursorstr | NonedefaultNoneThe next_cursor of a previous call, to read the page after it. Omit to start at the first page.
statusstr | NonedefaultNoneFilter by deal status. Only open, won, lost and deleted are sent to Pipedrive, and any other value is ignored, returning deals of every status.
sort_bystr | NonedefaultNoneField to sort by. Pipedrive accepts id, update_time and add_time. Sorting by deal value is not supported server side, so fetch the deals and sort them in the script, for example sorted(deals, key=lambda d: d.get('value', 0), reverse=True).
sort_directionstr | NonedefaultNoneSort direction, either asc or desc. Any other value is ignored, and the direction only applies when sort_by is set to a field Pipedrive accepts.
{"status": "success", "deals": list[dict], "count": int, "next_cursor": str | None}Each deal carries the same fixed field set as get_deal, with unset fields left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read them with .get(). One page is returned per call. "next_cursor" is Pipedrive's cursor for the next page: pass it back as cursor, with the same filters, to read on. It is None on the last page. "count" is the length of "deals" on this page rather than the total Pipedrive holds. Leaving limit unset means Pipedrive's own default page size applies rather than an unlimited fetch. Sorting by deal value is not supported server side, so fetch the deals and sort in the script.
create_deal()Create a new deal.
result = qbash.integrations.pipedrive.create_deal(
title="…",
)
titlestrrequiredDeal title.
person_idstr | int | NonedefaultNonePerson ID to associate with the deal. A value that is not a whole number is dropped and the deal is created without the link.
org_idstr | int | NonedefaultNoneOrganisation ID to associate with the deal. A value that is not a whole number is dropped and the deal is created without the link.
valuestr | int | float | NonedefaultNoneDeal value as a number. A string is parsed, and a value that is not fully numeric is dropped.
currencystr | NonedefaultNoneThree letter currency code (e.g. USD). Not sent when omitted, leaving the value to Pipedrive, so read it back from the returned deal rather than assuming what it became.
stage_idstr | int | NonedefaultNonePipeline stage ID. A value that is not a whole number is dropped, and when no stage is sent Pipedrive places the deal itself.
statusstr | NonedefaultNoneDeal status: open, won or lost. Not sent when omitted, leaving the value to Pipedrive, so read it back from the returned deal rather than assuming what it became.
{"status": "success", "deal": dict}"deal" is Pipedrive's created record put through the same fixed field set as get_deal, so read it with .get() and take the new deal id from deal["id"]. A field Pipedrive returns no value for at all is left out of the dict rather than set to null. That is about what Pipedrive returns, not about what was sent: a field the create call omitted still comes back, and is kept, whenever Pipedrive fills it in itself. Only the params declared here reach Pipedrive, so a custom field cannot be set at creation and needs a direct qbash.http call to the Pipedrive API.
update_deal()Update an existing deal.
result = qbash.integrations.pipedrive.update_deal(
deal_id=0,
)
deal_idstr | intrequiredDeal ID.
titlestr | NonedefaultNoneNew deal title.
person_idstr | int | NonedefaultNonePerson ID to link. A value that is not a whole number is dropped and the existing link is left alone.
org_idstr | int | NonedefaultNoneOrganisation ID to link. A value that is not a whole number is dropped and the existing link is left alone.
valuestr | int | float | NonedefaultNoneDeal value as a number. A string is parsed, and a value that is not fully numeric is dropped.
currencystr | NonedefaultNoneThree letter currency code (e.g. USD).
stage_idstr | int | NonedefaultNonePipeline stage ID to move the deal to. A value that is not a whole number is dropped.
statusstr | NonedefaultNoneDeal status: open, won or lost.
{"status": "success", "deal": dict}"deal" is Pipedrive's updated record put through the same fixed field set as get_deal, so read it with .get(). Only the params given are sent, so an omitted one leaves the existing value alone rather than clearing it, and there is no way to clear a field through this mode. Fields Pipedrive holds no value for at all are left out of the returned dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value. Only the params declared here reach Pipedrive, so a custom field cannot be updated and needs a direct qbash.http call to the Pipedrive API.
get_person()Get a single person by ID.
result = qbash.integrations.pipedrive.get_person(
person_id=0,
)
person_idstr | intrequiredPerson ID.
{"status": "success", "person": dict}"person" holds id, name, first_name, last_name, email, phone, org_id, owner_id, add_time and update_time, and nothing else. Pipedrive custom fields and any column not in that list are dropped. "email" and "phone" are flattened to the single value Pipedrive marks primary, or the first one when none is marked, so a person's other addresses and numbers are not returned. A key Pipedrive has no value for at all is left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read every field with .get(). A person id that does not exist comes back as an error.
search_persons()Search persons by term.
result = qbash.integrations.pipedrive.search_persons(
term="…",
limit=10,
)
termstr | Nonedefault""Search term matched against person names, emails and phone numbers. Pipedrive rejects a term shorter than two characters.
limitint | str | Nonedefault10Maximum persons to return in the single page fetched. A string of digits is parsed, and anything else falls back to 10.
{"status": "success", "persons": list[dict], "count": int}Each person carries the same fixed field set as get_person, with email and phone flattened to one primary value and unset fields left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read them with .get(). "count" is the length of "persons" and not the total Pipedrive holds. One page is returned, capped by limit, and no cursor comes back, so raising limit is the only way to see more. A term that matches nothing gives an empty list and a count of 0 rather than an error.
create_person()Create a new person.
result = qbash.integrations.pipedrive.create_person(
name="…",
)
namestrrequiredPerson name.
emailstr | NonedefaultNoneOne email address, stored as the primary entry labelled work. A list is not accepted and is dropped.
phonestr | NonedefaultNoneOne phone number, stored as the primary entry labelled work. A list is not accepted and is dropped.
org_idstr | int | NonedefaultNoneOrganisation ID to link the person to. A value that is not a whole number is dropped and the person is created without the link.
{"status": "success", "person": dict}"person" is Pipedrive's created record put through the same fixed field set as get_person, so read it with .get() and take the new person id from person["id"]. Fields Pipedrive did not set are left out of the dict rather than set to null. Only one email address and one phone number can be set here, each stored as the primary entry labelled work. Only the params declared here reach Pipedrive, so a custom field cannot be set at creation and needs a direct qbash.http call to the Pipedrive API.
get_organization()Get a single organization by ID.
result = qbash.integrations.pipedrive.get_organization(
organization_id=0,
)
organization_idstr | intrequiredOrganisation ID.
{"status": "success", "organization": dict}"organization" holds id, name, address, owner_id, people_count, add_time and update_time, and nothing else. Pipedrive custom fields and any column not in that list are dropped. A key Pipedrive has no value for at all is left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read every field with .get(). An organisation id that does not exist comes back as an error.
search_organizations()Search organizations by term.
result = qbash.integrations.pipedrive.search_organizations(
term="…",
limit=10,
)
termstr | Nonedefault""Search term matched against organisation names and addresses. Pipedrive rejects a term shorter than two characters.
limitint | str | Nonedefault10Maximum organisations to return in the single page fetched. A string of digits is parsed, and anything else falls back to 10.
{"status": "success", "organizations": list[dict], "count": int}Each organisation carries the same fixed field set as get_organization, with unset fields left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read them with .get(). "count" is the length of "organizations" and not the total Pipedrive holds. One page is returned, capped by limit, and no cursor comes back, so raising limit is the only way to see more. A term that matches nothing gives an empty list and a count of 0 rather than an error.
create_organization()Create a new organization.
result = qbash.integrations.pipedrive.create_organization(
name="…",
)
namestrrequiredOrganisation name.
{"status": "success", "organization": dict}"organization" is Pipedrive's created record put through the same fixed field set as get_organization, so read it with .get() and take the new organisation id from organization["id"]. Fields Pipedrive did not set are left out of the dict rather than set to null. Name is the only field this mode sends, so an owner, address or custom field has to be set with a direct qbash.http call to the Pipedrive API.
list_persons()List persons, page by page.
result = qbash.integrations.pipedrive.list_persons(
limit=10,
cursor="…",
)
limitint | str | NonedefaultNoneMaximum persons to return in the page fetched, at most 500. Omit for Pipedrive's default of 100. A value that is not a whole number is ignored.
cursorstr | NonedefaultNoneThe next_cursor of a previous call, to read the page after it. Omit to start at the first page.
{"status": "success", "persons": list[dict], "count": int, "next_cursor": str | None}Each person carries the same fixed field set as get_person, with email and phone flattened to one primary value and unset fields left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read them with .get(). One page is returned per call. "next_cursor" is Pipedrive's cursor for the next page: pass it back as cursor to read on. It is None on the last page. "count" is the length of "persons" on this page rather than the total Pipedrive holds. To find a person by name, email or phone, use search_persons.
list_organizations()List organizations, page by page.
result = qbash.integrations.pipedrive.list_organizations(
limit=10,
cursor="…",
)
limitint | str | NonedefaultNoneMaximum organisations to return in the page fetched, at most 500. Omit for Pipedrive's default of 100. A value that is not a whole number is ignored.
cursorstr | NonedefaultNoneThe next_cursor of a previous call, to read the page after it. Omit to start at the first page.
{"status": "success", "organizations": list[dict], "count": int, "next_cursor": str | None}Each organisation carries the same fixed field set as get_organization, with unset fields left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read them with .get(). One page is returned per call. "next_cursor" is Pipedrive's cursor for the next page: pass it back as cursor to read on. It is None on the last page. "count" is the length of "organizations" on this page rather than the total Pipedrive holds. To find an organisation by name, use search_organizations.
list_pipelines()List deal pipelines, page by page.
result = qbash.integrations.pipedrive.list_pipelines(
limit=10,
cursor="…",
)
limitint | str | NonedefaultNoneMaximum pipelines to return in the page fetched, at most 500. Omit for Pipedrive's default of 100. A value that is not a whole number is ignored.
cursorstr | NonedefaultNoneThe next_cursor of a previous call, to read the page after it. Omit to start at the first page.
{"status": "success", "pipelines": list[dict], "count": int, "next_cursor": str | None}Each pipeline holds id, name, order_nr, is_deleted, is_deal_probability_enabled, add_time and update_time, and nothing else. A key Pipedrive has no value for at all is left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read every field with .get(). One page is returned per call. "next_cursor" is Pipedrive's cursor for the next page: pass it back as cursor to read on. It is None on the last page. "count" is the length of "pipelines" on this page rather than the total Pipedrive holds. A deal's pipeline_id and list_stages' pipeline_id filter hold these ids.
list_stages()List pipeline stages, page by page.
result = qbash.integrations.pipedrive.list_stages(
limit=10,
cursor="…",
)
limitint | str | NonedefaultNoneMaximum stages to return in the page fetched, at most 500. Omit for Pipedrive's default of 100. A value that is not a whole number is ignored.
cursorstr | NonedefaultNoneThe next_cursor of a previous call, to read the page after it. Omit to start at the first page.
pipeline_idstr | int | NonedefaultNonePipeline ID to list the stages of. Omit to list the stages of every pipeline. A value that is not a whole number is ignored, returning the stages of every pipeline.
{"status": "success", "stages": list[dict], "count": int, "next_cursor": str | None}Each stage holds id, name, pipeline_id, order_nr, deal_probability, is_deal_rot_enabled, days_to_rotten, is_deleted, add_time and update_time, and nothing else. A key Pipedrive has no value for at all is left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read every field with .get(). One page is returned per call. "next_cursor" is Pipedrive's cursor for the next page: pass it back as cursor to read on. It is None on the last page. "count" is the length of "stages" on this page rather than the total Pipedrive holds. Stage names repeat across pipelines, so pass pipeline_id or check each stage's pipeline_id. The stage id is what create_deal and update_deal take as stage_id.
get_activities()List activities.
result = qbash.integrations.pipedrive.get_activities(
limit=10,
cursor="…",
)
limitint | str | NonedefaultNoneMaximum activities to return in the single page fetched. Omit to use Pipedrive's own default page size. A value that is not a whole number is ignored.
cursorstr | NonedefaultNoneCursor to start from, taken from a Pipedrive response obtained elsewhere. No continuation cursor is returned by this mode.
deal_idstr | int | NonedefaultNoneFilter by deal ID. A value that is not a whole number is ignored, returning activities for every deal.
person_idstr | int | NonedefaultNoneFilter by person ID. A value that is not a whole number is ignored, returning activities for every person.
org_idstr | int | NonedefaultNoneFilter by organisation ID. A value that is not a whole number is ignored, returning activities for every organisation.
{"status": "success", "activities": list[dict], "count": int}Each activity holds id, subject, type, due_date, done, deal_id, person_id, org_id, note, add_time and update_time, and nothing else. A key Pipedrive has no value for at all is left out of the dict rather than set to null, while an empty string, empty list, False or 0 is kept as a real value, so read every field with .get(). One page is returned and Pipedrive's next cursor is not returned by this mode, so a script cannot walk past the first page, and "count" is the length of "activities" rather than the total Pipedrive holds. The filters are ANDed, and a filter whose value is not a whole number is ignored, which returns unfiltered activities rather than an error. Filtering by done or not done is not exposed here.
create_activity()Create a new activity.
result = qbash.integrations.pipedrive.create_activity(
subject="…",
)
subjectstrrequiredActivity subject.
typestr | NonedefaultNoneActivity type key, such as call, meeting, task, deadline, email or lunch. Pipedrive rejects a key that is not configured on the account. Nothing here requires it and the field is simply not sent when omitted, so whether the activity is created without one is up to Pipedrive. Pass it unless you have confirmed the account accepts an activity with no type.
due_datestr | NonedefaultNoneDue date as YYYY-MM-DD. Pipedrive rejects any other format.
notestr | NonedefaultNoneActivity note. Accepts basic HTML.
deal_idstr | int | NonedefaultNoneDeal ID to link. A value that is not a whole number is dropped and the activity is created without the link.
person_idstr | int | NonedefaultNonePerson ID to link. A value that is not a whole number is dropped and the activity is created without the link.
org_idstr | int | NonedefaultNoneOrganisation ID to link. A value that is not a whole number is dropped and the activity is created without the link.
{"status": "success", "activity": dict}"activity" is Pipedrive's created record put through the same fixed field set as get_activities, so read it with .get() and take the new activity id from activity["id"]. Fields Pipedrive did not set are left out of the dict rather than set to null. Whether the activity is done is never sent, so Pipedrive decides, and marking one done is not exposed here. A due time, duration or assignee cannot be set through this mode and needs a direct qbash.http call to the Pipedrive API.
create_note()Create a note on a deal, person, or organization.
result = qbash.integrations.pipedrive.create_note(
content="…",
)
contentstrrequiredNote content. Accepts basic HTML.
deal_idstr | int | NonedefaultNoneDeal ID to attach the note to. A value that is not a whole number is dropped.
person_idstr | int | NonedefaultNonePerson ID to attach the note to. A value that is not a whole number is dropped.
org_idstr | int | NonedefaultNoneOrganisation ID to attach the note to. A value that is not a whole number is dropped.
{"status": "success", "note": dict}"note" holds id, content, deal_id, person_id, org_id, add_time and update_time, and nothing else. Fields Pipedrive did not set are left out of the dict rather than set to null, so read them with .get() and take the new note id from note["id"]. At least one of deal_id, person_id or org_id has to be a whole number for the note to attach to anything. Nothing here checks that, so a note with no valid id is left to Pipedrive to reject, and passing several attaches the note to each of them.
execute_query()Execute a SQL query against a PostgreSQL database. It runs with the rights of the configured role, so point the credential at a read only role to keep it to reads.
result = qbash.integrations.postgres.execute_query(
query="search terms",
)
querystrrequiredSQL query to execute. Use $1, $2, ... placeholders for parameterised values.
output_formatstr | Nonedefault"json_rows"Output format: json_rows, single_value, or columns_and_rows. Any other value falls back to json_rows without an error.
query_timeoutint | str | Nonedefault30000Query timeout in milliseconds. A string of digits is parsed, and anything else falls back to 30000.
query_paramslist | NonedefaultNoneValues bound to the $1, $2, ... placeholders, in order. Anything that is not a list is dropped and the query runs with no bound values, which fails if it holds placeholders.
read_onlybooldefaultFalseRun the query in a read only transaction. Only the boolean True enables it, so the string 'true' leaves the query writable. Rejected with an error when the workflow supplies a connection pool.
retry_attemptsint | str | Nonedefault1Attempts to make when the connection is refused or times out, from 1 to 4. Values outside that range are clamped rather than rejected. Retries wait 15 to 60 seconds each, and only connection errors are retried, never a failing query.
{"status": "success", "result": list[dict], "row_count": int} when output_format is json_rows, or {"status": "success", "result": {"columns": list[str], "rows": list[list], "row_count": int}, "row_count": int} when columns_and_rows, or {"status": "success", "result": str | int | float | bool | list | dict | None, "row_count": 1} when single_value"status" is always "success" on the ok path, so it carries no information a caller has to test. What "result" holds depends entirely on output_format. Under json_rows it is a list of row dicts keyed by column name, and a statement that returns no rows at all, meaning DDL or an INSERT, UPDATE or DELETE without RETURNING, gives an empty list. The top level "row_count" is then the length of that list, so it reports 0 for those statements even though rows were affected. Only columns_and_rows carries Postgres's own count, in both result["row_count"] and the top level one, so use that format when the affected row count matters. Under single_value the result is the first column of the first row as a bare value, None when nothing matched, and the top level "row_count" is 1 in either case, so it cannot be used to tell a match from a miss. Values are coerced to JSON types on the way out: numeric and decimal columns become str to preserve precision, dates, times and timestamps become ISO 8601 str, inet and macaddr become str, points and ranges become dicts, and a bytea that is not valid UTF-8 becomes a base64 str. An interval is not ISO 8601: it becomes a human readable str such as "2 months 3 days 4.0 seconds", holding only the non-zero parts, so parse it yourself or cast it in SQL when you need a duration. Passing read_only True is rejected with an error when the workflow supplies a connection pool, since the pool cannot open the read only transaction.
list_schemas()List the user schemas in the connected database.
result = qbash.integrations.postgres.list_schemas()
{"status": "success", "schemas": list[dict]}Each entry in "schemas" holds "schema_name" and "table_count", where the count covers ordinary and partitioned tables and leaves out views and foreign tables. The catalog schemas pg_catalog, information_schema and pg_toast are excluded, along with the per session temporary ones, so a database holding nothing else answers with an empty list rather than an error. The query runs with the rights of the configured role, so a schema the role cannot see is absent from the list.
list_tables()List the tables, views and foreign tables in a schema.
result = qbash.integrations.postgres.list_tables(
schema="…",
)
schemastr | Nonedefault"public"Schema to list. Omitting it, or passing an empty string, reads the public schema.
{"status": "success", "schema": str, "tables": list[dict]}"schema" repeats the schema that was read, including the default, so the caller does not have to track which one it asked for. Each entry in "tables" holds "table_name" and "table_type", where the type is BASE TABLE, VIEW or FOREIGN TABLE, and the rows are ordered by type and then name. A schema that does not exist, or one the configured role cannot read, answers with an empty list rather than an error, so check the length before reading the first entry.
describe_table()Describe the columns, indexes, foreign keys and check constraints of a table.
result = qbash.integrations.postgres.describe_table(
table="…",
)
tablestrrequiredTable, view or foreign table to describe. Missing or empty fails before any query runs.
schemastr | Nonedefault"public"Schema holding the table. Omitting it, or passing an empty string, reads the public schema.
{"status": "success", "schema": str, "table": str, "columns": list[dict], "indexes": list[dict], "foreign_keys": list[dict], "check_constraints": list[dict]}Four catalog queries run one after another and the first error ends the call, so a partial description never comes back. A column entry holds "column_name", "data_type", "character_maximum_length", "numeric_precision", "numeric_scale", "is_nullable", "column_default", "is_primary_key" and "column_comment", with the last two flags spelled YES or NO rather than as booleans. An index entry holds "index_name", "type_desc", "is_unique", "is_primary_key", "index_def" and a "columns" string of comma separated column names. A foreign key entry holds "fk_name", "column_name", "references_table" and "references_column", where a composite key lists its columns comma separated in matching order. A check constraint entry holds "constraint_name" and "check_clause". A table that does not exist answers with four empty lists rather than an error, so read an empty "columns" as not found.
subreddit()Fetch posts from a subreddit.
result = qbash.integrations.reddit.subreddit(
subredditName="…",
)
subredditNamestrrequiredName of the subreddit to fetch posts from (without r/).
filterTypestrdefault"hot"Post sort type: hot, new, top, rising, or controversial.
timeFilterstrdefault"all"Time window for top/controversial: hour, day, week, month, year, or all.
limitintdefault10Max number of posts to return.
afterstr | NonedefaultNonePagination cursor (after) from a previous response.
beforestr | NonedefaultNonePagination cursor (before) from a previous response.
flairstr | NonedefaultNoneFilter posts by flair text.
{"posts": list[dict], "after": str | None, "before": str | None}There is no status key around this result. Each post is cut down to reddit_id, title, author, body, url, created_utc, flair, upvotes, upvote_ratio, num_comments and stickied, and every other Reddit field is dropped. after and before are the listing cursors to pass back for the next and previous page, and both are None at the end of the listing. When flair is set the posts are filtered after Reddit has already applied limit, so fewer posts than limit come back and an empty posts list does not mean the listing has ended. The flair match is on the whole flair text, ignoring case.
comment()Fetch comments from a Reddit post.
result = qbash.integrations.reddit.comment(
postId="…",
)
postIdstrrequiredReddit post ID (the part after /comments/ in the URL).
limitintdefault100Max number of comments to return.
sortstrdefault"top"Comment sort order: top, new, best, controversial, or old.
contextintdefault0Number of parent comments to include for context.
list[dict] of comments, with no wrapping objectThe result is a bare list rather than a map, so there is no status key and no cursor. Each comment holds author, body, created_utc, score, reddit_id and parent_id, where parent_id is None for a comment on the post itself and otherwise the id of the parent comment. The reply tree is flattened into one list, so limit counts replies as well as top level comments and the flattened list is cut to limit. Collapsed more links are skipped, so a long thread comes back partial with nothing in the result to mark it.
search()Search across all of Reddit.
result = qbash.integrations.reddit.search(
query="search terms",
)
querystrrequiredSearch query string.
sortstrdefault"relevance"Sort order: relevance, hot, top, new, or comments.
timeFilterstrdefault"all"Time window: hour, day, week, month, year, or all.
limitintdefault25Max number of results to return.
afterstr | NonedefaultNonePagination cursor (after).
beforestr | NonedefaultNonePagination cursor (before).
{"posts": list[dict], "after": str | None, "before": str | None}There is no status key around this result. Posts carry the same cut down fields as the subreddit mode, which are reddit_id, title, author, body, url, created_utc, flair, upvotes, upvote_ratio, num_comments and stickied. One page comes back per call, holding up to limit posts as Reddit applies it. after and before are the listing cursors to pass back, and both are None at the end of the results.
search_subreddit()Search within a specific subreddit.
result = qbash.integrations.reddit.search_subreddit(
subredditName="…",
query="search terms",
)
subredditNamestrrequiredName of the subreddit to search within.
querystrrequiredSearch query string.
sortstrdefault"relevance"Sort order: relevance, hot, top, new, or comments.
timeFilterstrdefault"all"Time window: hour, day, week, month, year, or all.
limitintdefault25Max number of results to return.
afterstr | NonedefaultNonePagination cursor (after).
beforestr | NonedefaultNonePagination cursor (before).
{"posts": list[dict], "after": str | None, "before": str | None}There is no status key around this result. The search is restricted to the named subreddit. Posts carry the same cut down fields as the subreddit mode, which are reddit_id, title, author, body, url, created_utc, flair, upvotes, upvote_ratio, num_comments and stickied. One page comes back per call, holding up to limit posts as Reddit applies it. after and before are the listing cursors to pass back, and both are None at the end of the results.
user_info()Get public profile information for a Reddit user.
result = qbash.integrations.reddit.user_info(
username="…",
)
usernamestrrequiredReddit username (without u/).
{"username": str | None, "reddit_id": str | None, "created_utc": float | None, "link_karma": int | None, "comment_karma": int | None, "total_karma": int | None, "is_gold": bool | None, "is_mod": bool | None, "verified": bool | None, "has_verified_email": bool | None}The result is this fixed projection of the Reddit profile, with no status key and no other profile fields. Every key is always present and holds None when Reddit does not supply that field, so read the optional ones with .get() or handle None. A username Reddit does not serve at all comes back as an error.
user_posts()Get posts submitted by a Reddit user.
result = qbash.integrations.reddit.user_posts(
username="…",
)
usernamestrrequiredReddit username.
sortstrdefault"new"Sort order: new, hot, top, or controversial.
timeFilterstrdefault"all"Time window for top/controversial: hour, day, week, month, year, or all.
limitintdefault25Max number of posts to return.
afterstr | NonedefaultNonePagination cursor (after).
beforestr | NonedefaultNonePagination cursor (before).
{"posts": list[dict], "after": str | None, "before": str | None}There is no status key around this result. Posts carry the same cut down fields as the subreddit mode, which are reddit_id, title, author, body, url, created_utc, flair, upvotes, upvote_ratio, num_comments and stickied. One page comes back per call, holding up to limit posts as Reddit applies it. after and before are the listing cursors to pass back, and both are None at the end of the listing.
user_comments()Get comments submitted by a Reddit user.
result = qbash.integrations.reddit.user_comments(
username="…",
)
usernamestrrequiredReddit username.
sortstrdefault"new"Sort order: new, hot, top, or controversial.
timeFilterstrdefault"all"Time window: hour, day, week, month, year, or all.
limitintdefault25Max number of comments to return.
afterstr | NonedefaultNonePagination cursor (after).
beforestr | NonedefaultNonePagination cursor (before).
{"comments": list[dict], "after": str | None, "before": str | None}The collection key is comments here, not posts as in user_posts, and there is no status key around the result. Each comment holds author, body, created_utc, score, reddit_id and parent_id, where parent_id is None when the comment replies to a post rather than to another comment. Nothing in a comment names the post or subreddit it came from. after and before are the listing cursors to pass back, and both are None at the end of the listing.
subreddit_info()Get metadata and statistics for a subreddit.
result = qbash.integrations.reddit.subreddit_info(
subredditName="…",
)
subredditNamestrrequiredName of the subreddit (without r/).
{"name": str | None, "reddit_id": str | None, "title": str | None, "description": str | None, "description_html": str | None, "subscribers": int | None, "active_users": int | None, "created_utc": float | None, "is_nsfw": bool | None, "url": str | None, "icon_img": str | None, "banner_img": str | None}The result is this fixed projection of the Reddit subreddit profile, with no status key and no other fields. description is the short public description and description_html is the longer sidebar text. Every key is always present and holds None when Reddit does not supply that field, so active_users is None for a subreddit that does not report it.
subreddit_rules()Get the rules for a subreddit.
result = qbash.integrations.reddit.subreddit_rules(
subredditName="…",
)
subredditNamestrrequiredName of the subreddit (without r/).
list[dict] with keys short_name, description, kind, violation_reason and priority, with no wrapping objectThe result is a bare list rather than a map, so there is no status key. Only the rules the subreddit set itself are returned. The site wide Reddit rules that the same endpoint carries are not exposed.
subreddit_flairs()List available post flairs for a subreddit.
result = qbash.integrations.reddit.subreddit_flairs(
subredditName="…",
)
subredditNamestrrequiredName of the subreddit (without r/).
list[dict] with keys id, text, css_class and mod_only, with no wrapping objectThe result is a bare list of post flair templates rather than a map, so there is no status key. Only those four fields are kept from each template and the rest are dropped. mod_only marks a flair that only subreddit moderators can apply.
search()Search the web via SerpAPI and return structured results.
result = qbash.integrations.serp.search(
search_string="…",
)
search_stringstrrequiredSearch query to run.
search_enginestr | Nonedefault"google"Search engine to use: google, google_maps, google_local, google_jobs, google_shopping, google_news, google_scholar, google_flights, google_trends, google_finance, bing, bing_maps, bing_news, duckduckgo or duckduckgo_news. Anything else falls back to google.
locationstr | NonedefaultNoneGeographic location to localise results (e.g. 'Los Angeles, CA'). Applies to google, google_maps, google_local and google_jobs, and is dropped for other engines.
time_periodstr | NonedefaultNoneTime filter for results (past_day, past_week, past_month, past_year). Applies to google and the news engines. Any other value, or any other engine, drops the filter without raising.
zoomint | NonedefaultNoneMap zoom level from 3 to 21, used by the google_maps engine when a location is set. Defaults to 13. The range is not checked here, so any other value is passed to SerpAPI as given.
sortstr | NonedefaultNoneResult ordering for the news engines, either date or relevance. Any other value, or any other engine, drops the ordering without raising.
glstr | NonedefaultNoneTwo letter country code for the search locale (e.g. us).
hlstr | NonedefaultNoneTwo letter language code for the results (e.g. en).
{"status": "success", "search_string": str, "search_engine": str, "serpapi_response": dict, "message": str}Everything except "serpapi_response" is an envelope built here. The results are in "serpapi_response", which is SerpAPI's own body forwarded unchanged, so its keys are the vendor's and vary by engine: "organic_results" for google, "news_results" for the news engines, "local_results" for google_maps. Nothing here reads or normalises that body, so look up the shape for the engine being queried and read it with .get() rather than expecting a common one. "status" is "success" whenever the HTTP call succeeded, including when the engine found nothing, so an empty result set is not an error. "search_engine" echoes the value that was passed rather than the engine SerpAPI ran, and a name outside the supported list falls back to google without raising. Every call asks SerpAPI for ten results and neither that count nor SerpAPI's own paging offset is exposed here, so only the first page is reachable through this mode and how many results an engine actually returns for that request is up to the engine. Going further needs qbash.http against SerpAPI directly.
list_files()List directory entries on the SFTP/FTP server: name, type (file, dir, symlink, other), size in bytes and last-modified time. To explore a server, start at / and descend into any entry of type dir by passing its full path; a symlink is followed by listing its own path. On plain FTP, mtime is the server's local clock with no timezone offset and can be hours off.
result = qbash.integrations.sftp.list_files(
path="…",
timeoutSeconds=0,
)
pathstr | Nonedefault"/"Absolute remote directory path to list, e.g. / or /data/reports (defaults to /).
timeoutSecondsint | Nonedefault30Connection timeout in seconds.
retryAttemptsint | Nonedefault3Attempts to make when the server returns a transient error, from 1 to 10.
{"path": str, "entries": list[dict]}The result has no status key, it is the listing itself, and path echoes what was asked for. Each entry has name, type, size and mtime, and all four keys are always present. type is file, dir, symlink or other, size is None for anything that is not a regular file, and mtime is None when the server gives no usable timestamp. On FTP a directory line the parser cannot read comes back with the raw line in name and None in the other three keys. One directory is read per call, with no recursion, no pagination and no glob filter.
get_file_size()Return the byte size of a remote file.
result = qbash.integrations.sftp.get_file_size(
path="…",
)
pathstrrequiredRemote file path.
timeoutSecondsint | Nonedefault30Connection timeout in seconds.
retryAttemptsint | Nonedefault3Attempts to make when the server returns a transient error, from 1 to 10.
{"byte_size": int}byte_size is the only key, there is no status key and the path is not echoed back. On FTP the size comes from the raw SIZE command, and a server that answers with anything other than a 213 response produces an error rather than a result.
get_row_count()Count data rows in a remote CSV file.
result = qbash.integrations.sftp.get_row_count(
path="…",
)
pathstrrequiredRemote CSV file path.
hasHeaderbool | NonedefaultTrueWhether the file has a header row.
chunkSizeint | Nonedefault1000Row chunk size for building the seek index.
timeoutSecondsint | Nonedefault30Connection timeout in seconds.
retryAttemptsint | Nonedefault3Attempts to make when the server returns a transient error, from 1 to 10.
{"row_count": int}row_count is the only key and it always excludes the first line of the file. The hasHeader and chunkSize parameters are not read by this mode, so a file without a header row counts one row short and the index interval is fixed at 10000 rows. The whole file is read to produce the count, and on SFTP that scan also caches the byte offset index that later read_chunk calls seek with.
read_chunk()Read a bounded slice of rows from a remote CSV file.
result = qbash.integrations.sftp.read_chunk(
path="…",
startRow=0,
rowCount=10,
)
pathstrrequiredRemote CSV file path.
startRowintrequiredZero-based row index to start reading from.
rowCountintrequiredNumber of rows to read.
hasHeaderbool | NonedefaultTrueWhether the file has a header row.
timeoutSecondsint | Nonedefault30Connection timeout in seconds.
retryAttemptsint | Nonedefault3Attempts to make when the server returns a transient error, from 1 to 10.
{"rows": list[dict], "headers": list[str], "rows_returned": int} or {"rows": list[list[str]], "headers": [], "rows_returned": int}With hasHeader true each row is a dict keyed by column name and headers holds the column names in file order. With hasHeader false headers is empty and each row is a list of strings in column order, so branch on hasHeader before indexing a row. Rows are zipped against the headers, so a row with fewer fields than the header lacks those keys and a row with more fields loses the extra values. Repeated column names collapse into one key. rows_returned is how many rows were read, which is below rowCount at the end of the file.
download_file()Download a remote file and return it as a QBash file-reference object.
result = qbash.integrations.sftp.download_file(
path="…",
)
pathstrrequiredRemote file path to download (max 5 GB).
contentTypestr | NonedefaultNoneMIME type override (auto-detected if omitted).
timeoutSecondsint | Nonedefault120How long one read may wait, in seconds.
retryAttemptsint | Nonedefault3Attempts to make when the server returns a transient error, from 1 to 10.
fileThe file is streamed into qbash storage and what comes back is a file reference rather than the bytes. When the resolved content type is an image the type key is aisle_input_image and the payload sits under a key of that name, so read type first and index with it. The content type comes from the contentType parameter when it is given and from the filename extension otherwise, so contentType decides which of the two shapes you get. A file over qbash's 5 GB file limit is refused by a size check before the download starts. timeoutSeconds limits how long one read may wait, not the whole download, and a download that drops partway fails the call. A multi-GB download needs a run whose expected runtime is over 15 minutes.
read_lines()Read raw text lines from a remote file: CSV, TSV, fixed-width, plain text or any line-delimited format, to inspect contents, check headers or validate a layout. Reading stops at lineCount lines or 64 KB, whichever comes first, and the result carries the total file size.
result = qbash.integrations.sftp.read_lines(
path="…",
lineCount=10,
)
pathstrrequiredAbsolute remote file path.
lineCountintrequiredMaximum number of lines to read (capped at 200).
offsetint | Nonedefault0Zero-based line number to start reading from. SFTP only: FTP always reads from the first line and ignores this.
timeoutSecondsint | Nonedefault30Connection timeout in seconds.
retryAttemptsint | Nonedefault3Attempts to make when the server returns a transient error, from 1 to 10.
{"lines": list[str], "line_count": int, "offset": int, "file_size": int | None, "truncated": bool, "encoding": str}Reading stops at lineCount lines or 65536 bytes, whichever comes first, and lineCount itself is capped at 200. truncated is true when the byte cap ended the read, so the lines you have are a prefix of what you asked for. encoding is utf-8, or latin-1 when the bytes were not valid UTF-8 and were converted. On FTP the offset parameter is ignored and reading always starts at the first line, and the offset key in the result is 0 no matter what was requested. file_size is None on FTP when the server does not answer the SIZE command.
get_shop()Get the connected Shopify store's name, myshopify domain, currency, timezone and plan. Use it to check the credential works.
result = qbash.integrations.shopify.get_shop()
{"status": "success", "shop": dict, "api_version": str}shop has id, name, email, contact_email, myshopify_domain, primary_domain, primary_url, currency, enabled_presentment_currencies, timezone (IANA name), timezone_abbreviation, weight_unit, created_at, plan, partner_development and shopify_plus. Order timestamps are UTC, so use timezone to turn a calendar day in the store's time into the created_since and created_before bounds of list_orders. api_version is the Admin API version every call is pinned to.
list_orders()List orders, newest first, filtered by created or updated time, financial status, fulfillment status or a Shopify order search query. Returns one page with totals as decimal strings in the shop currency. Only the last 60 days are visible unless the app has read_all_orders.
result = qbash.integrations.shopify.list_orders(
created_since="…",
created_before="…",
)
created_sincestr | NonedefaultNoneInclusive lower bound on created_at, ISO 8601 (2026-09-29T00:00:00-04:00). Include the offset for an exact day boundary.
created_beforestr | NonedefaultNoneExclusive upper bound on created_at, ISO 8601. For one day pass the next day's midnight.
updated_sincestr | NonedefaultNoneInclusive lower bound on updated_at, ISO 8601. Use it to sync orders changed since the last run.
updated_beforestr | NonedefaultNoneExclusive upper bound on updated_at, ISO 8601.
financial_statusstr | NonedefaultNoneFinancial status filter: paid, pending, authorized, partially_paid, partially_refunded, refunded, voided or expired.
fulfillment_statusstr | NonedefaultNoneFulfillment status filter: shipped, partial, unshipped, unfulfilled, on_hold or scheduled.
statusstr | NonedefaultNoneOrder status filter: open, closed or cancelled. Omit for all.
querystr | NonedefaultNoneExtra Shopify order search syntax ANDed with the filters above, for example tag:wholesale or email:ann@example.com.
sort_keystrdefault"CREATED_AT"Sort field: CREATED_AT, PROCESSED_AT, UPDATED_AT, TOTAL_PRICE, ORDER_NUMBER or ID.
reversebooldefaultTrueTrue sorts descending (newest first). Set False for oldest first.
include_line_itemsbooldefaultFalseInclude up to 20 line items per order. Caps limit at 10.
limitintdefault50Orders per page, 1 to 50 (1 to 10 with include_line_items).
cursorstr | NonedefaultNonenext_cursor from the previous page.
{"status": "success", "orders": list[dict], "count": int, "has_next_page": bool, "next_cursor": str | None, "query": str | None, "warnings": list[str] | None}Each order has id (GID), legacy_id, name (#1001), created_at, processed_at, updated_at, cancelled_at, cancel_reason, closed_at, financial_status (PAID, PENDING, REFUNDED, PARTIALLY_REFUNDED and so on), fulfillment_status, test, source_name, currency, email, tags, note, total_price, subtotal_price, total_tax, total_discounts, total_shipping, total_refunded, current_total_price (after refunds and edits) and customer (id, display_name, email). Amounts are strings such as "12.50". With include_line_items each order also has line_items (id, title, variant_title, sku, vendor, quantity, current_quantity, variant_id, product_id, total after discounts) capped at 20 per order, and line_items_truncated is true when an order has more (use get_order for the rest). The filters are joined with AND into one Shopify search query, so the query param accepts the same syntax (for example tag:wholesale or name:#1001). Pass next_cursor back as cursor until it is None. Orders older than 60 days are hidden unless the app was granted read_all_orders. Customer name and email come back None with a warnings entry when the app is not approved for protected customer data.
get_order()Get one order with line items, addresses, shipping lines, fulfillments with tracking, and refunds. Accepts the GID, the numeric ID or the order name (#1001).
result = qbash.integrations.shopify.get_order(
order_id="…",
)
order_idstrrequiredOrder GID (gid://shopify/Order/123), numeric ID, or order name such as #1001.
{"status": "success", "order": dict, "warnings": list[str] | None}order has every list_orders field plus phone, presentment_currency, confirmation_number, status_page_url, discount_codes, custom_attributes (key and value), shipping_address, billing_address, shipping_lines (title, code, source, price), line_items (up to 100, each with unit_price and total), line_items_truncated, fulfillments (id, status, created_at, tracking with company, number and url) and refunds (id, created_at, note, amount). An order name is looked up with a name: search first. Addresses, phone and email are protected customer data and come back None with a warnings entry when the app is not approved for them.
list_customers()List customers with email, order count and total spent, filtered by a Shopify customer search query. Returns one page.
result = qbash.integrations.shopify.list_customers(
query="search terms",
sort_key="my-key",
)
querystr | NonedefaultNoneShopify customer search, for example email:ann@example.com, tag:vip, country:Canada or orders_count:>5.
sort_keystr | NonedefaultNoneSort field: CREATED_AT, UPDATED_AT, NAME, ID, LOCATION or RELEVANCE.
reversebooldefaultFalseTrue sorts descending.
limitintdefault50Customers per page, 1 to 100.
cursorstr | NonedefaultNonenext_cursor from the previous page.
{"status": "success", "customers": list[dict], "count": int, "has_next_page": bool, "next_cursor": str | None, "query": str | None, "warnings": list[str] | None}Each customer has id, legacy_id, display_name, first_name, last_name, email, email_marketing_state, phone, number_of_orders, amount_spent (decimal string), currency, state, verified_email, tax_exempt, tags, note, locale, created_at and updated_at. Names, email and phone are protected customer data and come back None with a warnings entry when the app is not approved for them. Pass next_cursor back as cursor until it is None.
get_customer()Get one customer with their default address and most recent order.
result = qbash.integrations.shopify.get_customer(
customer_id="…",
)
customer_idstrrequiredCustomer GID (gid://shopify/Customer/123) or numeric ID.
{"status": "success", "customer": dict, "warnings": list[str] | None}customer has every list_customers field plus default_address and last_order (id, name, created_at). To find a customer by email, call list_customers with query="email:ann@example.com" first.
list_products()List products with price range, total inventory and, by default, their first 10 variants with SKU, price and stock. Returns one page.
result = qbash.integrations.shopify.list_products(
query="search terms",
include_variants=True,
)
querystr | NonedefaultNoneShopify product search, for example status:active, title:shirt*, sku:ABC-1 or inventory_total:<5.
include_variantsbooldefaultTrueInclude up to 10 variants per product. Caps limit at 25.
sort_keystr | NonedefaultNoneSort field: TITLE, CREATED_AT, UPDATED_AT, INVENTORY_TOTAL, PRODUCT_TYPE, VENDOR or ID.
reversebooldefaultFalseTrue sorts descending.
limitintdefault25Products per page, 1 to 25 with variants or 1 to 100 without.
cursorstr | NonedefaultNonenext_cursor from the previous page.
{"status": "success", "products": list[dict], "count": int, "has_next_page": bool, "next_cursor": str | None, "query": str | None}Each product has id, legacy_id, title, handle, status (ACTIVE, DRAFT, ARCHIVED), vendor, product_type, tags, created_at, updated_at, published_at, total_inventory, tracks_inventory, online_store_url, min_price, max_price and currency. With include_variants each product has variants (id, title, sku, barcode, price, compare_at_price, inventory_quantity, available_for_sale, inventory_item_id) and variants_truncated is true when there are more than 10 (use get_product for up to 100). Filter with query, for example status:active, vendor:Acme, sku:ABC-1 or updated_at:>2026-09-01. Pass next_cursor back as cursor until it is None.
get_product()Get one product with description, options, up to 100 variants with SKU, price and stock, its featured image and the collections it belongs to.
result = qbash.integrations.shopify.get_product(
product_id="…",
)
product_idstrrequiredProduct GID (gid://shopify/Product/123) or numeric ID.
{"status": "success", "product": dict}product has every list_products field plus description (plain text), options (name and values), image_url, collections (id, title, handle, first 20) and variants with selected_options.
list_collections()List product collections with their product counts. Returns one page.
result = qbash.integrations.shopify.list_collections(
query="search terms",
sort_key="my-key",
)
querystr | NonedefaultNoneShopify collection search, for example title:Summer* or collection_type:smart.
sort_keystr | NonedefaultNoneSort field: TITLE, UPDATED_AT, ID or RELEVANCE.
reversebooldefaultFalseTrue sorts descending.
limitintdefault50Collections per page, 1 to 100.
cursorstr | NonedefaultNonenext_cursor from the previous page.
{"status": "success", "collections": list[dict], "count": int, "has_next_page": bool, "next_cursor": str | None, "query": str | None}Each collection has id, legacy_id, title, handle, updated_at, sort_order, products_count and type (smart for rule-based collections, custom for hand-picked ones). To list a collection's products call list_products with query="collection_id:<legacy_id>".
list_locations()List the store's inventory locations (warehouses, shops, apps that hold stock). Use a location id with list_inventory_levels.
result = qbash.integrations.shopify.list_locations(
include_inactive=True,
limit=10,
)
include_inactivebooldefaultFalseAlso return deactivated locations.
limitintdefault50Locations per page, 1 to 250.
cursorstr | NonedefaultNonenext_cursor from the previous page.
{"status": "success", "locations": list[dict], "count": int, "has_next_page": bool, "next_cursor": str | None}Each location has id (GID), legacy_id, name, is_active, fulfills_online_orders and address (address1, address2, city, province, provinceCode, zip, country, countryCode). Deactivated locations are left out unless include_inactive is True.
list_inventory_levels()List stock levels (available, on hand, committed, incoming) for every item at one location, with SKU and product title. Set max_available to keep only low-stock items. Returns one page.
result = qbash.integrations.shopify.list_inventory_levels(
location_id="…",
)
location_idstrrequiredLocation GID (gid://shopify/Location/123) or numeric ID from list_locations.
max_availableint | NonedefaultNoneKeep only items whose available quantity is at or below this number.
limitintdefault50Levels fetched per page, 1 to 100.
cursorstr | NonedefaultNonenext_cursor from the previous page.
{"status": "success", "inventory_levels": list[dict], "count": int, "scanned": int, "has_next_page": bool, "next_cursor": str | None, "location_id": str, "location_name": str}Each level has id, updated_at, available, on_hand, committed, incoming, inventory_item_id, sku, tracked, variant_id, variant_title, display_name (product and variant title), product_id and product_title. max_available is applied to each page after it is fetched, so a page can hold fewer items than limit, or none, while has_next_page is still True. scanned is how many levels the page held before filtering. For a low-stock report keep paging until next_cursor is None. Items with tracked False have no meaningful quantity.
graphql_query()Run a read-only Shopify Admin GraphQL query for data the other modes do not cover (metafields, draft orders, discounts, gift cards). Mutations are refused.
result = qbash.integrations.shopify.graphql_query(
query="search terms",
)
querystrrequiredGraphQL query document. Must be a query operation.
variablesdict | NonedefaultNoneVariables for the query.
{"status": "success", "data": dict, "cost": dict | None, "warnings": list[str] | None}Posts the query to the pinned Admin API version and returns data untouched (camelCase, as Shopify sends it). Any document containing a mutation or subscription operation is refused before it is sent. Paginate with first and after variables and read pageInfo { hasNextPage endCursor } yourself. A single query may cost at most 1000 points. cost reports requested, actual, available, maximum and restore_rate from Shopify's throttle status. Throttled requests are retried with backoff. Fields the app lacks scopes or protected data approval for come back null with a warnings entry.
add_tags()Add tags to an order, customer, product or draft order. Tags already present are left as they are. Needs the write scope for that resource.
result = qbash.integrations.shopify.add_tags(
id="…",
tags=[...],
)
idstrrequiredGID of the order, customer, product or draft order, or a numeric ID together with resource_type.
tagslist[str]requiredTags to add. A comma separated string is also accepted.
resource_typestrdefault"order"order, customer, product or draft_order. Only read when id is numeric.
{"status": "success", "id": str, "tags": list[str]}Uses the tagsAdd mutation. A typical use is marking orders a scheduled task has already handled (tag processed-by-aisle) and excluding them next run with query="-tag:processed-by-aisle". tags echoes the tags that were sent. Requires write_orders, write_customers, write_products or write_draft_orders.
remove_tags()Remove tags from an order, customer, product or draft order. Needs the write scope for that resource.
result = qbash.integrations.shopify.remove_tags(
id="…",
tags=[...],
)
idstrrequiredGID of the order, customer, product or draft order, or a numeric ID together with resource_type.
tagslist[str]requiredTags to remove. A comma separated string is also accepted.
resource_typestrdefault"order"order, customer, product or draft_order. Only read when id is numeric.
{"status": "success", "id": str, "tags": list[str]}Uses the tagsRemove mutation. Tags the resource does not have are ignored. tags echoes the tags that were sent.
create_message()Send a message to a channel.
result = qbash.integrations.slack.create_message(
channel="…",
text="…",
)
channelstrrequiredChannel ID (e.g. C0123) or name (e.g. #general).
textstrrequiredMessage text. Supports Slack mrkdwn.
{"status": "success", "channel": str, "text": str, "timestamp": str, "message": str}Posts through chat.postMessage. "timestamp" is the new message ts, pass it as thread_ts to reply_to_thread. "channel" echoes the value you passed rather than the resolved channel ID. Block Kit blocks, attachments and unfurl controls are not exposed, only plain text with Slack mrkdwn.
reply_to_thread()Reply to a message thread.
result = qbash.integrations.slack.reply_to_thread(
channel="…",
text="…",
thread_ts="…",
)
channelstrrequiredChannel ID or name where the parent message lives.
textstrrequiredReply text. Supports Slack mrkdwn.
thread_tsstrrequiredTimestamp of the parent message, the ts value from get_conversation_history.
{"status": "success", "channel": str, "text": str, "thread_ts": str, "timestamp": str, "message": str}Posts through chat.postMessage with thread_ts set. "timestamp" is the ts of the reply itself, "thread_ts" is the parent. "channel" echoes the value you passed rather than the resolved channel ID. The reply is not broadcast to the channel and reply_broadcast is not exposed.
get_user_profile()Get a user's profile information.
result = qbash.integrations.slack.get_user_profile(
user_id="…",
)
user_idstrrequiredSlack user ID (e.g. U0123).
{"status": "success", "user_id": str, "user": dict, "message": str}Reads users.info and reshapes the result. "user" holds id, name, real_name, display_name, email, title, phone, image_url, is_admin, is_bot, deleted, timezone and timezone_label. Any field the workspace does not expose comes back as None. An unknown user ID raises an error rather than returning an empty user.
look_up_user_by_email()Find a user by their email address.
result = qbash.integrations.slack.look_up_user_by_email(
email="…",
)
emailstrrequiredEmail address registered to the Slack user.
{"status": "success", "email": str, "user": dict, "message": str} or {"status": "error", "email": str, "user": {}, "message": str}Reads users.lookupByEmail. A missing user still counts as a successful call. It comes back with status "error" and an empty "user" dict, so check status before reading user fields. On a match "user" holds the same keys as get_user_profile.
download_file()Download a file shared in Slack.
result = qbash.integrations.slack.download_file(
file={"key": "value"},
)
filedict | list[dict]requiredA Slack file object, or a list of them, as they appear in the files array of a message from get_conversation_history.
url_private_downloadstrrequiredAuthenticated download URL from the Slack file object. Must be an HTTPS URL on files.slack.com.
namestr | NonedefaultNoneFile name to store the download under. Falls back to title, then to "attachment".
titlestr | NonedefaultNoneFile title, used as the file name when name is absent.
mimetypestr | NonedefaultNoneContent type of the file. Inferred from the file name when absent.
file | list[file]Streams the file into qbash storage with the bot token and returns a file reference you can pass to another integration or to qbash.output. The key holding the reference matches the "type" value, so images arrive under "aisle_input_image" and everything else under "aisle_input_file". Files up to qbash's 5 GB file limit are stored. If the file cannot be stored the call returns an error. Passing a list returns a list of references in the same order, and it is all or nothing, so one failed download fails the whole call.
get_conversation_history()Fetch messages from a channel within a date range.
result = qbash.integrations.slack.get_conversation_history(
channel="…",
)
channelstrrequiredChannel ID (e.g. C0123). Use list_conversations to resolve a channel name to an ID.
oldeststr | int | float | NonedefaultNoneEarliest message to include, as a Unix timestamp, a Slack message ts, an ISO 8601 datetime or an ISO 8601 date. A date alone is read as midnight UTC.
lateststr | int | float | NonedefaultNoneNewest message to include, in the same formats as oldest.
limitintdefault100Max messages to return (1-1000).
cursorstr | NonedefaultNonePagination cursor from a previous response.
inclusivebooldefaultFalseInclude messages with timestamps equal to oldest or latest.
{"status": "success", "messages": list[dict], "has_more": bool, "next_cursor": str | None}Reads conversations.history, newest message first. One call returns a single page of at most limit messages, pass the returned next_cursor back as cursor to read the rest. Each message dict holds type, user, text, ts, thread_ts, reply_count, reply_users_count, is_thread_parent, attachments, files, reactions and edited, and any of those the message does not carry comes back as None. "next_cursor" is absent, not None, when Slack returns no pagination metadata, and it is an empty string when Slack reports nothing after this page, so read it with .get() and treat an empty value as the end. "has_more" is derived from that cursor rather than from Slack's own has_more field. Only the thread parent appears here, use get_conversation_replies with a message ts to read the replies.
get_conversation_replies()Fetch replies in a thread.
result = qbash.integrations.slack.get_conversation_replies(
channel="…",
thread_ts="…",
)
channelstrrequiredChannel ID (e.g. C0123) where the thread lives.
thread_tsstrrequiredTimestamp of the parent message, the ts value from get_conversation_history.
oldeststr | int | float | NonedefaultNoneEarliest reply to include, as a Unix timestamp, a Slack message ts, an ISO 8601 datetime or an ISO 8601 date.
lateststr | int | float | NonedefaultNoneNewest reply to include, in the same formats as oldest.
limitintdefault100Max replies to return (1-1000).
cursorstr | NonedefaultNonePagination cursor from a previous response.
inclusivebooldefaultFalseInclude replies with timestamps equal to oldest or latest.
{"status": "success", "messages": list[dict], "has_more": bool, "next_cursor": str | None}Reads conversations.replies. The first entry in "messages" is the thread parent, the rest are replies in order. One call returns a single page of at most limit messages, so a long thread does not come back in full, pass the returned next_cursor back as cursor to read the rest. Message dicts hold the same keys as get_conversation_history and any key the message does not carry comes back as None. "next_cursor" is absent, not None, when Slack returns no pagination metadata, and it is an empty string when Slack reports nothing after this page, so read it with .get() and treat an empty value as the end. "has_more" is derived from that cursor rather than from Slack's own has_more field.
list_users()List users in the workspace.
result = qbash.integrations.slack.list_users(
limit=10,
cursor="…",
)
limitintdefault100Max users to return (1-1000).
cursorstr | NonedefaultNonePagination cursor from a previous response.
include_localebooldefaultFalseInclude the user's locale in each result.
{"status": "success", "users": list[dict], "has_more": bool, "next_cursor": str | None}Reads users.list. One call returns a single page of at most limit users rather than the whole workspace, pass the returned next_cursor back as cursor to read the rest. Each user dict holds id, name, real_name, display_name, email, title, image_url, is_admin, is_owner, is_bot, deleted and timezone, and any of those the workspace does not expose comes back as None. Deactivated accounts and bots are included, filter on deleted and is_bot. "next_cursor" is absent, not None, when Slack returns no pagination metadata, and it is an empty string when Slack reports nothing after this page, so read it with .get() and treat an empty value as the end.
list_conversations()List channels and conversations accessible to the bot.
result = qbash.integrations.slack.list_conversations(
types=[...],
limit=10,
)
typesstr | list | Nonedefault"public_channel"Channel types to include. Accepts a list or a comma-separated string of public_channel, private_channel, mpim and im.
limitintdefault100Max conversations to return (1-1000).
cursorstr | NonedefaultNonePagination cursor from a previous response.
exclude_archivedbooldefaultFalseOmit archived channels from the results.
{"status": "success", "conversations": list[dict], "has_more": bool, "next_cursor": str | None}Reads conversations.list. One call returns a single page of at most limit conversations rather than every channel the bot can reach, pass the returned next_cursor back as cursor to read the rest. The collection key is "conversations", not Slack's "channels". Each dict holds id, name, is_channel, is_private, is_archived, is_general, is_member, num_members, topic, purpose and created, where topic and purpose are the plain text values, and any key Slack omits comes back as None. "next_cursor" is absent, not None, when Slack returns no pagination metadata, and it is an empty string when Slack reports nothing after this page, so read it with .get() and treat an empty value as the end.
get_conversation_info()Get detailed information about a specific channel.
result = qbash.integrations.slack.get_conversation_info(
channel="…",
)
channelstrrequiredChannel ID (e.g. C0123). Use list_conversations to resolve a channel name to an ID.
include_localebooldefaultFalseInclude locale information in the result.
include_num_membersbooldefaultFalseInclude the member count in the result.
{"status": "success", "conversation": dict}Reads conversations.info. The channel is under "conversation" and holds id, name, is_channel, is_private, is_archived, is_general, is_member, num_members, topic, purpose and created. Any of those keys Slack omits comes back as None, including num_members unless include_num_members is true.
get_conversation_members()List member user IDs in a channel.
result = qbash.integrations.slack.get_conversation_members(
channel="…",
)
channelstrrequiredChannel ID (e.g. C0123). Use list_conversations to resolve a channel name to an ID.
limitintdefault100Max member IDs to return (1-1000).
cursorstr | NonedefaultNonePagination cursor from a previous response.
{"status": "success", "members": list[str], "has_more": bool, "next_cursor": str | None}Reads conversations.members. "members" is a list of user ID strings, not user objects, so pass each one to get_user_profile for names or emails. One call returns a single page of at most limit IDs rather than the full membership, pass the returned next_cursor back as cursor to read the rest. "next_cursor" is absent, not None, when Slack returns no pagination metadata, and it is an empty string when Slack reports nothing after this page, so read it with .get() and treat an empty value as the end.
execute_query()Run one SQL statement in Snowflake and return its rows. It runs with the rights of the credential's role, so a role granted only SELECT keeps it to reads. Set read_only to refuse anything but SELECT, WITH, SHOW, DESCRIBE and EXPLAIN.
result = qbash.integrations.snowflake.execute_query(
query="search terms",
)
querystrrequiredOne SQL statement. Use ? placeholders for values.
query_paramslist | NonedefaultNoneValues bound to the ? placeholders, in order. Must be a list.
output_formatstr | Nonedefault"json_rows"Output format: json_rows, single_value, or columns_and_rows. Any other value falls back to json_rows.
max_rowsint | str | Nonedefault10000Most rows to return, up to 100000. The statement still runs in full in Snowflake.
query_timeoutint | str | Nonedefault60000Milliseconds to wait for the statement before cancelling it, from 1000 to 3600000.
read_onlybooldefaultFalseRefuse any statement that is not a read. Only the boolean True enables it.
warehousestr | NonedefaultNoneWarehouse to run on. Omitting it uses the credential's warehouse, then the user's default.
databasestr | NonedefaultNoneDatabase for unqualified table names. Omitting it uses the credential's database.
schemastr | NonedefaultNoneSchema for unqualified table names. Omitting it uses the credential's schema.
rolestr | NonedefaultNoneRole to run as. It must be granted to the credential's user. Omitting it uses the credential's role, then the user's default.
{"status": "success", "result": list[dict], "row_count": int, "total_rows": int, "truncated": bool, "rows_affected": int | None, "query_id": str} when output_format is json_rows, or the same keys with "result": {"columns": list[str], "rows": list[list], "row_count": int} when columns_and_rows, or "result": str | int | float | bool | list | dict | None when single_valueRuns through the Snowflake SQL API, one statement per call. Use ? placeholders and query_params for values. "row_count" is the number of rows returned and "total_rows" is the number Snowflake produced. "truncated" is True when max_rows cut the result short. For INSERT, UPDATE, DELETE and MERGE, "rows_affected" holds Snowflake's count of changed rows, and it is None for other statements. "query_id" finds the statement in Snowsight query history, where qbash statements carry the query tag qbash. Values are converted to JSON types. NUMBER with no scale becomes int, and NUMBER with a scale stays a str to keep its digits. FLOAT and BOOLEAN become float and bool. DATE, TIME and TIMESTAMP become ISO 8601 str with fractions past microseconds dropped. TIMESTAMP_TZ keeps its offset and TIMESTAMP_LTZ is given in UTC. VARIANT, OBJECT and ARRAY become dicts, lists or scalars, and BINARY stays a hex str. A statement that runs past query_timeout is cancelled in Snowflake. The SQL API does not run PUT, GET, USE, ALTER SESSION, BEGIN, COMMIT or ROLLBACK. With read_only True the statement text is checked before it is sent. It must start with SELECT, WITH, SHOW, DESCRIBE or EXPLAIN. A SELECT or WITH may not contain INSERT, UPDATE, DELETE, MERGE, CALL, PROCEDURE or DDL keywords, so quote a column with one of those names. SYSTEM$ functions and the ->> pipe operator are refused. External functions the role can call still run. To block them, use a role that cannot call them.
insert_rows()Insert rows into a Snowflake table. Each row is a dict keyed by column name.
result = qbash.integrations.snowflake.insert_rows(
table="…",
rows={"key": "value"},
)
tablestrrequiredTable to insert into.
rowslist[dict]requiredRows to insert, 1 to 1000 dicts keyed by column name.
databasestr | NonedefaultNoneDatabase holding the table. Omitting it uses the credential's database.
schemastr | NonedefaultNoneSchema holding the table. Omitting it uses the credential's schema.
warehousestr | NonedefaultNoneWarehouse to run on. Omitting it uses the credential's warehouse, then the user's default.
rolestr | NonedefaultNoneRole to run as. Omitting it uses the credential's role, then the user's default.
query_timeoutint | str | Nonedefault60000Milliseconds to wait for the insert before cancelling it, from 1000 to 3600000.
{"status": "success", "table": str, "rows_inserted": int, "query_id": str}Builds one INSERT ... VALUES statement with a ? placeholder per value and runs it on a warehouse. Up to 1000 rows and 50000 values per call, so split larger loads into batches. The columns are every key that appears in any row, and a row without one of them inserts NULL there. Key names follow Snowflake's identifier rules: an unquoted name such as order_id matches the column ORDER_ID, and a name in double quotes keeps its case. Values are sent as text and Snowflake converts them to each column's type, so ISO 8601 strings work for DATE and TIMESTAMP columns. A dict or list value is sent as JSON text, which a VARIANT column stores as a string. To store it as an object, use execute_query with INSERT ... SELECT PARSE_JSON(?). The insert is one statement, so either every row is inserted or none are.
list_databases()List the databases the credential's role can see.
result = qbash.integrations.snowflake.list_databases()
{"status": "success", "databases": list[dict], "truncated": bool}Runs SHOW DATABASES, which needs no warehouse. Each entry holds "database_name", "kind" (STANDARD, or IMPORTED DATABASE for a share such as SNOWFLAKE_SAMPLE_DATA), "owner", "comment" and "created_on". Up to 10000 databases, with "truncated" True when there are more.
list_schemas()List the schemas in a Snowflake database.
result = qbash.integrations.snowflake.list_schemas(
database="…",
)
databasestr | NonedefaultNoneDatabase to list. Omitting it uses the credential's database.
{"status": "success", "database": str, "schemas": list[dict], "truncated": bool}Runs SHOW SCHEMAS, which needs no warehouse. Each entry holds "schema_name", "owner", "comment" and "created_on". INFORMATION_SCHEMA is left out. "database" is the database read, as Snowflake stores its name.
list_tables()List the tables and views in a Snowflake schema.
result = qbash.integrations.snowflake.list_tables(
database="…",
schema="…",
)
databasestr | NonedefaultNoneDatabase holding the schema. Omitting it uses the credential's database.
schemastr | NonedefaultNoneSchema to list. Omitting it uses the credential's schema.
{"status": "success", "database": str, "schema": str, "tables": list[dict], "truncated": bool}Runs SHOW OBJECTS, which needs no warehouse. Each entry holds "table_name", "table_type" (TABLE or VIEW), "row_count" and "bytes" (None for views), "owner", "comment" and "created_on". Up to 10000 objects, with "truncated" True when there are more.
describe_table()Describe the columns of a Snowflake table or view.
result = qbash.integrations.snowflake.describe_table(
table="…",
)
tablestrrequiredTable or view to describe.
databasestr | NonedefaultNoneDatabase holding the table. Omitting it uses the credential's database.
schemastr | NonedefaultNoneSchema holding the table. Omitting it uses the credential's schema.
{"status": "success", "database": str, "schema": str, "table": str, "columns": list[dict]}Runs DESCRIBE TABLE, then DESCRIBE VIEW when no table has the name. Neither needs a warehouse. Each column holds "column_name", "data_type" (such as NUMBER(38,0) or VARCHAR(16777216)), "is_nullable", "column_default", "is_primary_key", "is_unique_key" and "comment", with the flags spelled YES or NO. Snowflake records primary and unique keys but does not enforce them on standard tables.
list_warehouses()List the Snowflake warehouses the credential's role can see, with their size and state.
result = qbash.integrations.snowflake.list_warehouses()
{"status": "success", "warehouses": list[dict]}Runs SHOW WAREHOUSES, which does not resume a suspended warehouse. Each entry holds "warehouse_name", "state" (STARTED, SUSPENDED or RESIZING), "type", "size", "auto_suspend" (seconds), "auto_resume", "owner" and "comment". A role only sees warehouses it has a privilege on.
get_balance()Get the Stripe account balance: funds available to pay out and funds still pending, per currency.
result = qbash.integrations.stripe.get_balance()
{"status": "success", "available": list[dict], "pending": list[dict], "instant_available": list[dict], "livemode": bool}Each entry is {amount, currency} with amount in the smallest currency unit. livemode is False for a test mode key. Restricted keys need Balance read.
list_balance_transactions()List balance transactions (charges, refunds, fees, payouts, adjustments) with gross amount, fee and net. Use for revenue and fee reporting.
result = qbash.integrations.stripe.list_balance_transactions(
type="…",
payout="…",
)
typestr | NonedefaultNoneOnly this transaction type, for example charge, refund, payout, stripe_fee or adjustment.
payoutstr | NonedefaultNoneOnly transactions settled in this automatic payout id (po_...).
sourcestr | NonedefaultNoneOnly transactions created by this source id, such as a charge id.
currencystr | NonedefaultNoneOnly this three-letter ISO currency, lowercase.
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "balance_transactions": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each item has id, amount, fee, net, currency, type, reporting_category, status, source, description, created, available_on, exchange_rate and fee_details. Restricted keys need Balance read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_customers()List customers, newest first, optionally filtered by exact email or creation date.
result = qbash.integrations.stripe.list_customers(
email="…",
created_gte=0,
)
emailstr | NonedefaultNoneExact email address to match.
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "customers": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each customer has id, email, name, description, phone, created, currency, balance, delinquent, address, default_payment_method, livemode and metadata. The email filter is exact and case sensitive. Use search_customers for partial matches. Restricted keys need Customers read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
get_customer()Get one Stripe customer by id.
result = qbash.integrations.stripe.get_customer(
customer_id="…",
)
customer_idstrrequiredCustomer id (cus_...).
{"status": "success", "customer": dict}customer has the same keys as a list_customers item. A deleted customer comes back with deleted True and few other keys. Restricted keys need Customers read.
search_customers()Search customers with Stripe search syntax, for example email~'example.com' or metadata['plan']:'pro'.
result = qbash.integrations.stripe.search_customers(
query="search terms",
)
querystrrequiredStripe search query, for example email:'jo@example.com' or status:'failed' AND created>1700000000. Strings take single quotes.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous search page. Omit for the first page.
{"status": "success", "customers": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Searchable fields include email, name, phone, created and metadata. Items have the list_customers keys. Restricted keys need Customers read. Stripe search reads an index that can lag new writes by about a minute, and is not available for accounts in India. Paging: pass next_cursor back as cursor until it is None.
list_charges()List charges with status, amounts, failure code and message, and card details. Use for failed-payment monitoring and payment reporting.
result = qbash.integrations.stripe.list_charges(
customer="…",
payment_intent="…",
)
customerstr | NonedefaultNoneOnly objects for this customer id (cus_...).
payment_intentstr | NonedefaultNoneOnly objects for this PaymentIntent id (pi_...).
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "charges": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each charge has id, amount, amount_captured, amount_refunded, currency, status (succeeded, pending or failed), paid, captured, refunded, disputed, customer, payment_intent, balance_transaction, description, receipt_email, billing_email, failure_code, failure_message, outcome_type, outcome_reason, risk_level, seller_message, payment_method_type, card_brand, card_last4, card_country, created, livemode and metadata. The list cannot filter on status, so use search_charges with status:'failed' to fetch only failures. Restricted keys need Charges read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
search_charges()Search charges with Stripe search syntax, for example status:'failed' AND created>1700000000.
result = qbash.integrations.stripe.search_charges(
query="search terms",
)
querystrrequiredStripe search query, for example email:'jo@example.com' or status:'failed' AND created>1700000000. Strings take single quotes.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous search page. Omit for the first page.
{"status": "success", "charges": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Searchable fields include amount, currency, customer, status, disputed, refunded, payment_method_details.card.last4, created and metadata. Items have the list_charges keys. Restricted keys need Charges read. Stripe search reads an index that can lag new writes by about a minute, and is not available for accounts in India. Paging: pass next_cursor back as cursor until it is None.
list_payment_intents()List PaymentIntents with status, amounts and the last payment error.
result = qbash.integrations.stripe.list_payment_intents(
customer="…",
created_gte=0,
)
customerstr | NonedefaultNoneOnly objects for this customer id (cus_...).
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "payment_intents": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each item has id, amount, amount_received, currency, status, customer, description, latest_charge, cancellation_reason, last_payment_error_code, last_payment_error_decline_code, last_payment_error_message, created, livemode and metadata. status requires_payment_method after an attempt usually means the payment failed. Restricted keys need PaymentIntents read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_subscriptions()List subscriptions with status, billing period, items, cancellation details and a monthly list-price amount. Use for MRR, churn and trial reporting.
result = qbash.integrations.stripe.list_subscriptions(
customer="…",
price="…",
)
customerstr | NonedefaultNoneOnly objects for this customer id (cus_...).
pricestr | NonedefaultNoneOnly subscriptions that include this price id (price_...).
statusstr | NonedefaultNoneactive, past_due, unpaid, canceled, incomplete, incomplete_expired, trialing, paused, ended or all. Omitted means every status except canceled.
collection_methodstr | NonedefaultNonecharge_automatically or send_invoice.
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "subscriptions": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Stripe leaves out canceled subscriptions unless status is canceled or all. Each item has id, customer, status, currency, collection_method, created, start_date, current_period_start, current_period_end, cancel_at, cancel_at_period_end, canceled_at, ended_at, trial_start, trial_end, cancellation_reason, cancellation_feedback, cancellation_comment, latest_invoice, default_payment_method, items, monthly_amount, livemode and metadata. items holds id, price_id, product, quantity, unit_amount, currency, interval and interval_count. monthly_amount is the sum of unit_amount times quantity normalized to one month, before discounts, tax and usage charges, and is absent when an item has no flat unit_amount. Restricted keys need Subscriptions read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
get_subscription()Get one subscription by id with its items and billing period.
result = qbash.integrations.stripe.get_subscription(
subscription_id="…",
)
subscription_idstrrequiredSubscription id (sub_...).
{"status": "success", "subscription": dict}subscription has the same keys as a list_subscriptions item. Restricted keys need Subscriptions read.
search_subscriptions()Search subscriptions with Stripe search syntax, for example status:'past_due' or metadata['team']:'acme'.
result = qbash.integrations.stripe.search_subscriptions(
query="search terms",
)
querystrrequiredStripe search query, for example email:'jo@example.com' or status:'failed' AND created>1700000000. Strings take single quotes.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous search page. Omit for the first page.
{"status": "success", "subscriptions": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Searchable fields include status, created, canceled_at and metadata. Items have the list_subscriptions keys. Restricted keys need Subscriptions read. Stripe search reads an index that can lag new writes by about a minute, and is not available for accounts in India. Paging: pass next_cursor back as cursor until it is None.
list_invoices()List invoices with status, amounts due and paid, due date and payment attempts. Use for invoice follow-up and dunning reports.
result = qbash.integrations.stripe.list_invoices(
customer="…",
subscription="…",
)
customerstr | NonedefaultNoneOnly objects for this customer id (cus_...).
subscriptionstr | NonedefaultNoneOnly invoices for this subscription id (sub_...).
statusstr | NonedefaultNonedraft, open, paid, uncollectible or void.
collection_methodstr | NonedefaultNonecharge_automatically or send_invoice.
due_date_gtestr | int | NonedefaultNoneOnly invoices due at or after this time. Same formats as created_gte.
due_date_ltestr | int | NonedefaultNoneOnly invoices due at or before this time. Same formats as created_lte.
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "invoices": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each invoice has id, number, customer, customer_email, customer_name, subscription, status, billing_reason, collection_method, currency, subtotal, total, amount_due, amount_paid, amount_remaining, attempt_count, attempted, next_payment_attempt, due_date, created, period_start, period_end, finalized_at, paid_at, voided_at, marked_uncollectible_at, hosted_invoice_url, invoice_pdf, description, livemode and metadata. Open invoices past their due_date are overdue. Restricted keys need Invoices read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
get_invoice()Get one invoice by id, including its line items.
result = qbash.integrations.stripe.get_invoice(
invoice_id="…",
)
invoice_idstrrequiredInvoice id (in_...).
{"status": "success", "invoice": dict}invoice has the list_invoices keys plus lines (id, description, amount, currency, quantity, price_id, period_start, period_end) and lines_has_more, which is True when the invoice has more line items than Stripe returned inline. Restricted keys need Invoices read.
search_invoices()Search invoices with Stripe search syntax, for example status:'open' AND total>10000.
result = qbash.integrations.stripe.search_invoices(
query="search terms",
)
querystrrequiredStripe search query, for example email:'jo@example.com' or status:'failed' AND created>1700000000. Strings take single quotes.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous search page. Omit for the first page.
{"status": "success", "invoices": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Searchable fields include customer, status, subscription, total, number, created and metadata. Items have the list_invoices keys. Restricted keys need Invoices read. Stripe search reads an index that can lag new writes by about a minute, and is not available for accounts in India. Paging: pass next_cursor back as cursor until it is None.
list_payouts()List payouts to the bank account with amount, status, arrival date and failure details.
result = qbash.integrations.stripe.list_payouts(
status="…",
created_gte=0,
)
statusstr | NonedefaultNonepending, paid, failed or canceled.
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "payouts": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each payout has id, amount, currency, status (paid, pending, in_transit, canceled or failed), type, method, automatic, arrival_date, created, description, statement_descriptor, failure_code, failure_message, balance_transaction, destination, livemode and metadata. Pass a payout id to list_balance_transactions to see what it settled. Restricted keys need Payouts read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_refunds()List refunds with amount, status, reason and the charge they belong to.
result = qbash.integrations.stripe.list_refunds(
charge="…",
payment_intent="…",
)
chargestr | NonedefaultNoneOnly objects for this charge id (ch_...).
payment_intentstr | NonedefaultNoneOnly objects for this PaymentIntent id (pi_...).
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "refunds": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each refund has id, amount, currency, status, reason, charge, payment_intent, failure_reason, created and metadata. Restricted keys need Refunds read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_products()List products in the catalog with name, active flag and default price.
result = qbash.integrations.stripe.list_products(
active=True,
created_gte=0,
)
activebool | NonedefaultNoneTrue for active products only, False for archived ones only.
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "products": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each product has id, name, description, active, default_price, url, created, updated, livemode and metadata. Restricted keys need Products read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_prices()List prices with amount, currency, billing interval and the product they belong to.
result = qbash.integrations.stripe.list_prices(
product="…",
active=True,
)
productstr | NonedefaultNoneOnly prices for this product id (prod_...).
activebool | NonedefaultNoneTrue for active prices only, False for archived ones only.
typestr | NonedefaultNoneone_time or recurring.
currencystr | NonedefaultNoneOnly this three-letter ISO currency, lowercase.
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "prices": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each price has id, product, active, nickname, lookup_key, currency, unit_amount, billing_scheme, tiers_mode, type (one_time or recurring), interval, interval_count, usage_type, created, livemode and metadata. unit_amount is absent for tiered prices. Restricted keys need Prices read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_disputes()List disputes (chargebacks) with amount, status, reason and the evidence deadline.
result = qbash.integrations.stripe.list_disputes(
charge="…",
payment_intent="…",
)
chargestr | NonedefaultNoneOnly objects for this charge id (ch_...).
payment_intentstr | NonedefaultNoneOnly objects for this PaymentIntent id (pi_...).
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "disputes": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Each dispute has id, amount, currency, status, reason, charge, payment_intent, evidence_due_by, is_charge_refundable, created, livemode and metadata. status needs_response or warning_needs_response means evidence is still due. Restricted keys need Disputes read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_events()List account events from the last 30 days, such as invoice.payment_failed, customer.subscription.deleted or charge.dispute.created.
result = qbash.integrations.stripe.list_events(
type="…",
types=[...],
)
typestr | NonedefaultNoneOne event type, or a wildcard such as invoice.* .
typeslist[str] | NonedefaultNoneUp to 20 exact event types. Use instead of type.
created_gtestr | int | NonedefaultNoneOnly objects created at or after this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. Dates are UTC midnight.
created_ltestr | int | NonedefaultNoneOnly objects created at or before this time. Accepts YYYY-MM-DD, an ISO 8601 datetime or a unix timestamp. A bare date includes that whole UTC day.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "events": list[dict], "count": int, "has_more": bool, "next_cursor": str | None}Stripe keeps events for 30 days. Each event has id, type, created, livemode, api_version, object_id, object_type, object (the full Stripe object at the time of the event, untrimmed), previous_attributes (for *.updated events) and request_id. Restricted keys need Events read. Amounts are integers in the currency's smallest unit (cents for usd, whole yen for jpy), and timestamps are unix seconds. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
create_customer()Create a Stripe customer with email, name, phone, description and metadata.
result = qbash.integrations.stripe.create_customer(
email="…",
name="…",
)
emailstr | NonedefaultNoneCustomer email.
namestr | NonedefaultNoneCustomer or company name.
phonestr | NonedefaultNonePhone number.
descriptionstr | NonedefaultNoneInternal description shown in the dashboard.
metadatadict | NonedefaultNoneString key/value pairs to store on the customer. On update the keys merge with existing metadata, and a key set to an empty string is removed.
idempotency_keystr | NonedefaultNoneOptional Idempotency-Key header. Reusing a key within 24 hours returns the first result instead of repeating the write.
{"status": "success", "customer": dict}Stripe does not deduplicate customers by email, so check with list_customers or search_customers first, or pass idempotency_key. customer has the list_customers keys. Restricted keys need Customers write.
update_customer()Update a Stripe customer's email, name, phone, description or metadata.
result = qbash.integrations.stripe.update_customer(
customer_id="…",
)
customer_idstrrequiredCustomer id (cus_...).
emailstr | NonedefaultNoneNew email.
namestr | NonedefaultNoneNew name.
phonestr | NonedefaultNoneNew phone number.
descriptionstr | NonedefaultNoneNew internal description.
metadatadict | NonedefaultNoneString key/value pairs to store on the customer. On update the keys merge with existing metadata, and a key set to an empty string is removed.
{"status": "success", "customer": dict}Only the params you pass change. metadata keys merge with the existing ones, and a key set to an empty string is removed. customer has the list_customers keys. Restricted keys need Customers write.
send_invoice()Email an open invoice to its customer again, as a payment reminder. Works for invoices with collection_method send_invoice.
result = qbash.integrations.stripe.send_invoice(
invoice_id="…",
)
invoice_idstrrequiredInvoice id (in_...).
idempotency_keystr | NonedefaultNoneOptional Idempotency-Key header. Reusing a key within 24 hours returns the first result instead of repeating the write.
{"status": "success", "invoice": dict}Calls POST /v1/invoices/{id}/send. Stripe sends the email only in live mode. In test mode the call succeeds and no email goes out. Stripe rejects draft, paid and void invoices and invoices charged automatically. invoice has the list_invoices keys. Restricted keys need Invoices write.
list_projects()List all accessible Supabase projects.
result = qbash.integrations.supabase.list_projects()
{"status": "success", "projects": list[dict], "project_count": int}Each entry in projects is trimmed to id, ref, name, organization_id, region, status and created_at, and every other field the Management API sends about a project is dropped here. Any of those keys whose value was null is removed as well, so read them with .get() rather than indexing. project_count is the length of projects in this response. The mode makes one GET to the projects endpoint with no paging params, so the list is whatever that single response carried.
get_project()Get details for a specific Supabase project.
result = qbash.integrations.supabase.get_project(
projectRef="…",
)
projectRefstr | NonedefaultNoneSupabase project reference (uses credential default if omitted).
{"status": "success", "project": dict}project is trimmed to id, ref, name, organization_id, region, status and created_at, and every other field the Management API sends about a project is dropped here. Any of those keys whose value was null is removed as well, so read them with .get() rather than indexing. The project reference is taken from the projectRef param first, then from a reference embedded in the API key credential, then from the project_url credential. A reference that does not exist returns an error rather than an empty result.
list_organizations()List organizations the user is a member of.
result = qbash.integrations.supabase.list_organizations()
{"status": "success", "organizations": list[dict], "organization_count": int}Each entry in organizations is trimmed to id, name, slug and billing_email, and every other field the Management API sends is dropped here. Any of those keys whose value was null is removed as well, so read them with .get() rather than indexing. organization_count is the length of organizations in this response. The mode makes one GET with no paging params, so the list is whatever that single response carried.
get_organization()Get details for a specific Supabase organization.
result = qbash.integrations.supabase.get_organization(
orgId="…",
)
orgIdstrrequiredSupabase organization ID.
{"status": "success", "organization": dict}organization is trimmed to id, name, slug and billing_email, and any of those keys whose value was null is removed, so read them with .get() rather than indexing. An organization ID that does not exist returns an error rather than an empty result.
list_tables()List tables in a Supabase database schema.
result = qbash.integrations.supabase.list_tables(
projectRef="…",
schema="…",
)
projectRefstr | NonedefaultNoneSupabase project reference (uses credential default if omitted).
schemastrdefault"public"Database schema to list tables from.
{"status": "success", "tables": list[dict], "table_count": int}Each entry in tables has name, type and schema, read from the information_schema.tables columns table_name, table_type and table_schema, so views are listed alongside base tables and type is what separates them. This runs a SQL query through the Management API query endpoint rather than a tables endpoint, so it needs the same access as execute_sql. tables is also empty when the query response arrives in a shape the formatter does not recognise, so an empty list is not proof that the schema holds no tables. schema must be a plain SQL identifier and anything else is rejected before the request goes out. table_count is the length of tables.
list_extensions()List installed PostgreSQL extensions for a Supabase project.
result = qbash.integrations.supabase.list_extensions(
projectRef="…",
)
projectRefstr | NonedefaultNoneSupabase project reference (uses credential default if omitted).
{"status": "success", "extensions": list[dict], "extension_count": int}Each entry in extensions has name, version and schema, read from pg_extension joined to pg_namespace, so this is the list of extensions installed in the database rather than the ones available to install. extensions is also empty when the query response arrives in a shape the formatter does not recognise, so an empty list is not proof that nothing is installed. extension_count is the length of extensions.
list_migrations()List database migration history for a Supabase project.
result = qbash.integrations.supabase.list_migrations(
projectRef="…",
)
projectRefstr | NonedefaultNoneSupabase project reference (uses credential default if omitted).
{"status": "success", "migrations": list[dict], "migration_count": int}Each entry in migrations is trimmed to version, name, applied_at and statements, and any of those keys whose value was null is removed, so read them with .get() rather than indexing. statements is the SQL Supabase recorded for that migration. migration_count is the length of migrations in this response. The mode makes one GET with no paging params, so the history is whatever that single response carried.
apply_migration()Apply a SQL migration to a Supabase database.
result = qbash.integrations.supabase.apply_migration(
migrationName="…",
statements=[...],
)
migrationNamestrrequiredName for the migration.
statementslistrequiredList of SQL statement strings to apply.
versionstr | NonedefaultNoneMigration version identifier such as 20250101120000. Supabase assigns one when omitted.
projectRefstr | NonedefaultNoneSupabase project reference (uses credential default if omitted).
{"status": "success", "result": dict | list | str | int | float | bool | None}result is the Management API response to the migration POST, decoded from JSON and passed through without being read here, so check its type before indexing rather than assuming a dict. The statements run as one migration in the order given. version is optional and Supabase assigns one when it is omitted. A 2xx response with an empty body does not decode as JSON and the call fails after the migration has already been applied, so a failure from this mode is not proof that nothing changed.
execute_sql()Execute a raw SQL query against a Supabase database.
result = qbash.integrations.supabase.execute_sql(
query="search terms",
)
querystrrequiredSQL query to execute.
projectRefstr | NonedefaultNoneSupabase project reference (uses credential default if omitted).
{"status": "success", "rows": list[dict], "row_count": int} or {"status": "success", "result": dict}The query endpoint returns a JSON array for an ordinary query, and that array arrives as rows with row_count as its length, so a query that matched nothing gives rows of [] and row_count 0. When the endpoint returns a JSON object instead, that object is passed through under result and there is no rows key at all, so read rows with .get() and fall back to result. The object is not inspected here, so status is success whether it holds data or Supabase's own error payload. The query string is sent as given with no parameter binding, and it runs with the access the Management API token has, so a statement that writes or drops tables executes the same as a select.
get_logs()Retrieve project logs from Supabase for debugging.
result = qbash.integrations.supabase.get_logs(
projectRef="…",
isoTimestampStart="…",
)
projectRefstr | NonedefaultNoneSupabase project reference (uses credential default if omitted).
isoTimestampStartstr | NonedefaultNoneISO 8601 start timestamp to filter logs (e.g. 2025-10-14T14:57:35Z).
isoTimestampEndstr | NonedefaultNoneISO 8601 end timestamp to filter logs.
{"status": "success", "logs": list[dict], "log_count": int}Each entry in logs is a row from the analytics logs endpoint, forwarded unchanged and never reshaped here, so read its keys with .get(). Supabase answers either with a bare array or with an object carrying a result key, and both arrive here as logs. With neither isoTimestampStart nor isoTimestampEnd set, Supabase returns only about the last minute of logs. There is no limit param because the logs API rejects one, so the timestamp window is the only way to narrow the result. log_count is the length of logs in this response, and no cursor or paging param is exposed.
list_storage_buckets()List all storage buckets in a Supabase project.
result = qbash.integrations.supabase.list_storage_buckets()
{"status": "success", "buckets": list[dict], "bucket_count": int}Each entry in buckets is a Storage API bucket object forwarded unchanged and never reshaped here, so read its keys with .get(). bucket_count is the length of buckets in this response and the mode makes one GET with no paging params. The storage modes authenticate with the project_url and service_role_key credential fields rather than the Management API token, and a project_url that is not of the form https://ref.supabase.co or https://ref.supabase.com is rejected before any request goes out.
create_storage_bucket()Create a new storage bucket in Supabase.
result = qbash.integrations.supabase.create_storage_bucket(
bucketName="…",
)
bucketNamestrrequiredName for the new storage bucket.
publicbooldefaultFalseWhether the bucket should be publicly accessible.
fileSizeLimitint | NonedefaultNoneMaximum file size in bytes for uploads to this bucket.
allowedMimeTypesstr | NonedefaultNoneComma-separated list of allowed MIME types.
{"status": "success", "bucket": dict}bucket is the Storage API response to the create call, forwarded unchanged and never read here, so do not assume it echoes the settings that were sent. Read them back with get_storage_bucket when they matter. allowedMimeTypes is split on commas and trimmed into a list before it is sent. fileSizeLimit is in bytes, and a value that does not parse as an integer is dropped rather than rejected, as is a public value that is neither true nor false. This mode authenticates with the project_url and service_role_key credential fields rather than the Management API token.
get_storage_bucket()Get details about a specific Supabase storage bucket.
result = qbash.integrations.supabase.get_storage_bucket(
bucketId="…",
)
bucketIdstrrequiredStorage bucket ID to retrieve.
{"status": "success", "bucket": dict}bucket is the Storage API bucket object forwarded unchanged and never reshaped here, so read its keys with .get(). A bucket ID that does not exist returns an error rather than an empty result. This mode authenticates with the project_url and service_role_key credential fields rather than the Management API token.
list_storage_files()List files in a Supabase storage bucket.
result = qbash.integrations.supabase.list_storage_files(
bucketId="…",
)
bucketIdstrrequiredStorage bucket ID to list files from.
prefixstr | NonedefaultNoneOptional path prefix to filter files.
limitint | NonedefaultNoneMaximum number of files to return.
offsetint | NonedefaultNoneOffset for pagination.
{"status": "success", "files": list[dict], "file_count": int}Each entry in files is a Storage API object row forwarded unchanged and never reshaped here, so read its keys with .get(). One request is made, so files holds a single page at whatever size the Storage API defaults to when limit is omitted. Page through it with limit and offset, and treat file_count as the size of the page that came back rather than the size of the bucket. prefix defaults to an empty string when it is omitted. limit and offset are dropped when they do not parse as integers, which sends the request unpaged rather than failing it. This mode authenticates with the project_url and service_role_key credential fields rather than the Management API token.
scrape_web()Scrape content from a single web page.
result = qbash.integrations.supadata.scrape_web(
url="https://…",
)
urlstrrequiredURL of the web page to scrape.
dict or strReads Supadata GET /v1/web/scrape and returns that response body unchanged, so the result is Supadata's own shape under Supadata's own key names rather than an envelope built here. Nothing in this integration reads, renames or validates those keys, so read the body with .get() and check Supadata's API documentation for the fields of the scrape response. A url with no scheme is prefixed with https:// before the call. A response body that is not JSON comes back as a raw string instead of a dict, so check the type before subscripting. The request times out after 30 seconds.
map_website()Map a website to discover the URLs Supadata finds on it.
result = qbash.integrations.supadata.map_website(
url="https://…",
)
urlstrrequiredRoot URL of the website to map.
dict or strReads Supadata GET /v1/web/map and returns that response body unchanged, so the result is Supadata's own shape under Supadata's own key names. Nothing here reads, renames or validates those keys, so read the body with .get() and check Supadata's API documentation for the fields of the map response. Only the url is sent, so there is no depth, include or exclude control and no way to resume a crawl. Mapping crawls the site, so it is given 90 seconds rather than the usual 30. A site that is large or bot-protected fails with an error rather than returning a partial result. A response body that is not JSON comes back as a raw string rather than a dict, so check the type before subscripting.
scrape_youtube_transcript()Extract the transcript from a YouTube video.
result = qbash.integrations.supadata.scrape_youtube_transcript(
videoId="…",
)
videoIdstrrequired11-character YouTube video ID (e.g. 'dQw4w9WgXcQ').
langstr | NonedefaultNonePreferred transcript language code (e.g. 'en'). Ignored when the video has no transcript in that language.
textbool | NonedefaultNoneReturn plain text instead of timestamped segments. Defaults to segments.
chunkSizeint | NonedefaultNoneSplit transcript into chunks of this many characters. Applies only when text is False.
dict or strReads Supadata GET /v1/youtube/transcript and returns that response body unchanged, so the result is Supadata's own shape under Supadata's own key names. Nothing here reads, renames or validates those keys, so read the body with .get() and check Supadata's API documentation for the fields of the transcript response, which differ between the plain text form and the timestamped segment form. text, lang and chunkSize are forwarded to Supadata as given and are left off the request when unset. The video ID is turned into a watch URL here, so a full YouTube URL is not accepted. A video with no captions comes back as an error rather than an empty transcript. A response body that is not JSON comes back as a raw string rather than a dict, so check the type before subscripting.
scrape_youtube_channel()Get videos from a YouTube channel.
result = qbash.integrations.supadata.scrape_youtube_channel(
channelId="…",
)
channelIdstrrequired24-character YouTube channel ID starting with 'UC'.
limitint | NonedefaultNoneMaximum number of videos to return. Defaults to 30.
channelTypestr | NonedefaultNoneFormat filter: all, video, short or live. Defaults to all.
dict or strReads Supadata GET /v1/youtube/channel/videos and returns that response body unchanged, so the result is Supadata's own shape under Supadata's own key names. Nothing here reads, renames or validates those keys, so read the body with .get() and check Supadata's API documentation for the fields of the channel videos response. limit is sent on every call and defaults to 30, and channelType is sent as the vendor's type and defaults to all, so a longer listing needs an explicit limit. No cursor or page parameter is exposed, so whatever falls outside the limit Supadata honours cannot be reached through this mode. A response body that is not JSON comes back as a raw string rather than a dict, so check the type before subscripting.
data_source_discovery()List Supermetrics data sources and which ones the team has connected, or return one source's report types and settings.
result = qbash.integrations.supermetrics.data_source_discovery(
ds_id="…",
filter="…",
)
ds_idstr | NonedefaultNoneData source ID such as AW for Google Ads. When set, config holds that source's report types and settings.
filterstr | NonedefaultNoneKeyword matched against source IDs, names, descriptions, categories, aliases and tags.
{"status": "success", "data_sources": list[dict], "count": int, "config": dict | None}Without ds_id this searches the public Supermetrics catalog, limited to sources the API can query. Each source has ds_id, name, description, categories, status, is_premium, tags, connected and login_count. connected and login_count come from the team's logins, count logins that are not revoked, and are None when the API key cannot read logins because it lacks the ds_logins_read scope or the plan does not include that endpoint. A source only this team can use is listed when the team has a login for it. With ds_id, data_sources is empty, count is 0, and config holds the configuration object Supermetrics returns for that source, which the API documents with has_account_list, has_fields, has_report_type_selection, is_date_range_required, report_types and common_settings, plus connected and login_count added here. The ds_id lookup needs the team ID on the credential.
accounts_discovery()List the accounts the team's Supermetrics logins can query for a data source.
result = qbash.integrations.supermetrics.accounts_discovery(
ds_id="…",
)
ds_idstrrequiredData source ID from data_source_discovery, such as AW for Google Ads.
filterstr | NonedefaultNoneComma-separated terms. An account matches when its ID, name or group contains any term, ignoring case.
limitintdefault100Most accounts to return, from 1 through 10000.
{"status": "success", "ds_id": str, "accounts": list[dict], "count": int, "truncated": bool}Each account has account_id, account_name, group_name, ds_user and login_name. Pass account_id values to data_query as ds_accounts. ds_user is the data source username of the login that reaches the account, so pass it to data_query when several logins can see the same account. An empty list means the team has no login for the source or its logins reach no accounts. filter runs here, after Supermetrics returns every account, and limit then keeps the first matches. truncated is true when more accounts matched than limit, so raise limit or narrow filter to see the rest.
field_discovery()List the metrics and dimensions a Supermetrics data source offers.
result = qbash.integrations.supermetrics.field_discovery(
ds_id="…",
)
ds_idstrrequiredData source ID from data_source_discovery, such as AW for Google Ads.
filterstr | NonedefaultNoneComma-separated terms. A field matches when its ID, name, description, group, data type or report type contains any term, ignoring case.
ds_accountslist[str] | str | NonedefaultNoneAccount IDs whose custom fields to include. Needs ds_user.
ds_userstr | NonedefaultNoneData source username of the login for ds_accounts, from accounts_discovery.
{"status": "success", "ds_id": str, "total": int, "metrics": list[dict], "dimensions": list[dict]}Fields carry field_id, field_name, field_type, data_type, description, group_name, is_filterable and report_types when Supermetrics sends them, and metrics also carry is_non_aggregatable. Pass field_id values to data_query. When a source uses report types, every field in one query must share a report type. Custom fields, such as custom conversions, come back only when both ds_accounts and ds_user are set. total counts the fields left after filter, which runs here.
data_query()Run a Supermetrics report query and return rows keyed by field ID.
result = qbash.integrations.supermetrics.data_query(
ds_id="…",
)
ds_idstrrequiredData source ID from data_source_discovery, such as AW for Google Ads or GAWA for Google Analytics 4.
ds_accountslist[str] | str | NonedefaultNoneAccount IDs from accounts_discovery, a comma-separated string of them, or list.all_accounts. Most sources require it.
fieldslist[str] | str | NonedefaultNoneField IDs from field_discovery, dimensions before metrics.
date_range_typestr | NonedefaultNoneRange name such as last_month or last_7_days, or custom with start_date and end_date. Names ending in _inc include today.
start_datestr | NonedefaultNoneStart of a custom range as YYYY-MM-DD, @ plus a Unix timestamp, or a relative date such as -30 days.
end_datestr | NonedefaultNoneEnd of a custom range, in the same formats as start_date.
timezonestr | NonedefaultNoneIANA timezone for relative dates, such as America/New_York. Defaults to the team's query timezone setting.
filtersstr | NonedefaultNoneFilter expression such as clicks > 100 AND campaign_name =@ brand. Operators are ==, !=, >, >=, <, <=, =@ (contains), !@ (does not contain), =~ (regex) and !~ (not regex). Contains and regex apply to dimensions only.
settingsdict | NonedefaultNoneSupermetrics settings keyed by setting ID, including report_type and the source settings listed by data_source_discovery.
compare_typestr | NonedefaultNoneComparison period: prev_range, prev_year, prev_year_weekday, or custom with compare_start_date and compare_end_date.
compare_showstr | NonedefaultNoneHow comparison values appear: perc_change (the Supermetrics default), abs_change or value.
compare_start_datestr | NonedefaultNoneStart of a custom comparison range, in the same formats as start_date.
compare_end_datestr | NonedefaultNoneEnd of a custom comparison range, in the same formats as start_date.
max_rowsintdefault1000Most rows Supermetrics fetches from the source, from 1 through 1,000,000. The license may allow fewer.
ds_userstr | NonedefaultNoneData source username of the login to query with, from accounts_discovery.
order_rowslist[str] | str | NonedefaultNoneUp to two sort instructions such as cost desc.
cache_minutesint | NonedefaultNoneOldest cached result Supermetrics may return, in minutes, from 0 through 10080.
offset_startintdefault0Zero-based index of the first row in this response.
offset_endint | NonedefaultNoneZero-based index of the last row in this response. Defaults to the end of a 10,000 row page, or of max_rows when that is smaller. A response never holds more than 10,000 rows.
{"status": "success", "query_status": str, "status_code": str | None, "schedule_id": str | None, "fields": list[str], "rows": list[dict], "row_count": int, "total_rows": int | None, "offset_start": int, "offset_end": int, "next_offset_start": int | None, "row_limit_reached": bool | None, "start_date": str | None, "end_date": str | None, "data_sampled": bool | None, "cache_used": bool | None, "cache_time": str | None}Supermetrics holds the request for up to 120 seconds. When the query finishes, query_status is completed and rows holds the rows from offset_start through offset_end, at most 10,000, each keyed by field ID in the order of fields. A column Supermetrics does not map to a field ID is keyed column_ plus its zero-based index. When the query is still running, query_status is pending and rows is empty. Pass this result's schedule_id, offset_start and offset_end to get_async_query_results to fetch the page once it finishes. max_rows caps how many rows Supermetrics fetches from the source, and the license can cap it lower. While next_offset_start is set, read the next page with get_async_query_results(schedule_id=..., offset_start=next_offset_start) within an hour of the query finishing. row_limit_reached is true when total_rows reaches max_rows. settings.no_headers is always true, and settings.clean_numeric_types defaults to true so metric values arrive as numbers. timezone overrides settings.timezone. start_date and end_date apply only when date_range_type is custom. Instagram and Facebook organic data use ds_id IGI or FB. cache_used and cache_time report whether Supermetrics answered from its cache and when that cache was filled.
get_async_query_results()Wait for a Supermetrics query by schedule ID and return a page of its rows.
result = qbash.integrations.supermetrics.get_async_query_results(
schedule_id="…",
)
schedule_idstrrequiredschedule_id from data_query.
offset_startintdefault0Zero-based index of the first row in this response.
offset_endint | NonedefaultNoneZero-based index of the last row in this response. Defaults to the end of a 10,000 row page, and a response never holds more than 10,000 rows.
{"status": "success", "query_status": str, "status_code": str | None, "schedule_id": str | None, "fields": list[str], "rows": list[dict], "row_count": int, "total_rows": int | None, "offset_start": int, "offset_end": int, "next_offset_start": int | None, "row_limit_reached": bool | None, "start_date": str | None, "end_date": str | None, "data_sampled": bool | None, "cache_used": bool | None, "cache_time": str | None}Checks the query every few seconds for up to 120 seconds. When it is still running after that, query_status is pending, so call again with the same schedule_id. Rows use the same shape as data_query. Once the query has finished, pass offset_start (and optionally offset_end) to read later pages without running the query again, for as long as next_offset_start is set. Supermetrics keeps results for an hour after a query stops. row_limit_reached is always None here because only data_query knows max_rows.
send_message()Send a text message to a chat.
result = qbash.integrations.telegram.send_message(
chat_id="…",
text="…",
)
chat_idstrrequiredChat ID or username.
textstrrequiredMessage text.
parse_modestr | NonedefaultNoneFormatting mode (Markdown / HTML).
disable_web_page_previewbool | NonedefaultNoneDisable link preview in the message.
disable_notificationbool | NonedefaultNoneSend silently without notification.
reply_to_message_idint | NonedefaultNoneReply to a specific message ID.
reply_markupdict | NonedefaultNoneInline keyboard markup (JSON object or dict).
{"status": "success", "message": dict}The message dict is the Telegram Message narrowed to message_id, from, chat, date, text, caption, photo, document, poll and reply_to_message. Any of those keys whose value is null is removed before the result is built, so read everything except message_id, chat and date with .get(). Other Message fields such as entities, message_thread_id and link_preview_options are dropped and cannot be recovered through this mode. The result carries no delivery confirmation beyond the message_id Telegram assigned.
send_photo()Send a photo to a chat.
result = qbash.integrations.telegram.send_photo(
chat_id="…",
photo="…",
)
chat_idstrrequiredChat ID or username.
photostrrequiredPhoto URL or file_id.
captionstr | NonedefaultNonePhoto caption.
parse_modestr | NonedefaultNoneFormatting mode (Markdown / HTML).
disable_notificationbool | NonedefaultNoneSend silently without notification.
reply_to_message_idint | NonedefaultNoneReply to a specific message ID.
{"status": "success", "message": dict}The message dict is the Telegram Message narrowed to message_id, from, chat, date, text, caption, photo, document, poll and reply_to_message. Any of those keys whose value is null is removed before the result is built, so read everything except message_id, chat and date with .get(). photo holds the list of size variants Telegram generated, and caption is absent when none was sent. The photo is sent as a JSON field, so only a URL Telegram can fetch or a file_id from an earlier message works. Uploading local file bytes is not supported.
send_document()Send a document to a chat.
result = qbash.integrations.telegram.send_document(
chat_id="…",
document="…",
)
chat_idstrrequiredChat ID or username.
documentstrrequiredDocument URL or file_id.
captionstr | NonedefaultNoneDocument caption.
parse_modestr | NonedefaultNoneFormatting mode (Markdown / HTML).
disable_notificationbool | NonedefaultNoneSend silently without notification.
reply_to_message_idint | NonedefaultNoneReply to a specific message ID.
{"status": "success", "message": dict}The message dict is the Telegram Message narrowed to message_id, from, chat, date, text, caption, photo, document, poll and reply_to_message. Any of those keys whose value is null is removed before the result is built, so read everything except message_id, chat and date with .get(). document holds the file metadata Telegram stored, including the file_id needed to send the same file again. The document is sent as a JSON field, so only a URL Telegram can fetch or an existing file_id works. Uploading local file bytes is not supported.
edit_message()Edit a previously sent message.
result = qbash.integrations.telegram.edit_message(
chat_id="…",
message_id=0,
text="…",
)
chat_idstrrequiredChat ID or username.
message_idintrequiredID of the message to edit.
textstrrequiredNew message text.
parse_modestr | NonedefaultNoneFormatting mode (Markdown / HTML).
reply_markupdict | NonedefaultNoneUpdated inline keyboard markup.
{"status": "success", "message": dict}The message dict is the Telegram Message narrowed to message_id, from, chat, date, text, caption, photo, document, poll and reply_to_message. Any of those keys whose value is null is removed before the result is built, so read everything except message_id, chat and date with .get(). When Telegram answers with true rather than a message object, message comes back as {"result": True} and carries no message_id, so check for message_id before indexing it. Editing captions, media or the keyboard on its own is not exposed, and text is required on every call.
delete_message()Delete a message from a chat.
result = qbash.integrations.telegram.delete_message(
chat_id="…",
message_id=0,
)
chat_idstrrequiredChat ID or username.
message_idintrequiredID of the message to delete.
{"status": "success", "result": bool, "chat_id": str, "message_id": int}result is Telegram's own acknowledgement, a boolean rather than an object, and is True when the message was removed. chat_id and message_id echo what was passed, with message_id parsed to an integer. The deletion cannot be undone and the message text is not returned before it goes.
send_poll()Send a poll to a chat.
result = qbash.integrations.telegram.send_poll(
chat_id="…",
question="…",
options=[...],
)
chat_idstrrequiredChat ID or username.
questionstrrequiredPoll question.
optionslistrequiredList of answer options (2-10 strings).
is_anonymousbool | NonedefaultNoneWhether the poll is anonymous.
typestr | NonedefaultNonePoll type (regular / quiz).
correct_option_idint | NonedefaultNoneZero-based index of the correct answer (required for quiz polls).
{"status": "success", "message": dict}The message dict is the Telegram Message narrowed to message_id, from, chat, date, text, caption, photo, document, poll and reply_to_message. Any of those keys whose value is null is removed before the result is built, so read everything except message_id, chat and date with .get(). The poll itself is at message["poll"] and its id, needed to stop the poll later, is at message["poll"]["id"]. Stopping a poll is not exposed. Neither are open_period, close_date, explanation or allows_multiple_answers, so every poll is single choice and stays open.
get_updates()Poll for new incoming updates.
result = qbash.integrations.telegram.get_updates(
offset=0,
limit=10,
)
offsetint | NonedefaultNoneUpdate ID offset for pagination.
limitintdefault100Maximum number of updates to return.
timeoutintdefault0Long-polling timeout in seconds (0 for short polling).
allowed_updateslist | NonedefaultNoneList of update types to receive (e.g. ["message", "callback_query"]).
{"status": "success", "updates": list[dict], "count": int}Returns one batch per call, holding at most limit updates. count is the size of that batch rather than how many updates are waiting. To advance, pass offset as the highest update_id returned plus one, which also confirms the earlier updates and drops them from Telegram's queue for good. Each update dict carries update_id plus only the event keys Telegram sent, such as message or callback_query, so branch with .get() rather than indexing. Event types the formatter does not know about, such as message_reaction, are dropped from the update. Telegram refuses this call while a webhook is configured, so set_webhook and this mode cannot both be in use.
get_chat()Get detailed info about a chat.
result = qbash.integrations.telegram.get_chat(
chat_id="…",
)
chat_idstrrequiredChat ID or username.
{"status": "success", "chat": dict}chat is the Telegram Chat narrowed to id, type, title, username, first_name, last_name, description, invite_link and pinned_message, with null valued keys removed. A private chat has no title and most chats have no username or description, so read everything except id and type with .get(). Fields outside that list, such as permissions, photo and member limits, are not carried through.
get_chat_member_count()Get the member count of a chat.
result = qbash.integrations.telegram.get_chat_member_count(
chat_id="…",
)
chat_idstrrequiredChat ID or username.
{"status": "success", "member_count": int, "chat_id": str}member_count is the number Telegram reports for the chat, including the bot itself. chat_id echoes the value that was passed, so it comes back as the username or id the caller supplied rather than a normalised numeric id. Listing or inspecting individual members is not exposed.
set_webhook()Configure a webhook URL for receiving updates.
result = qbash.integrations.telegram.set_webhook(
url="https://…",
)
urlstrrequiredHTTPS URL to send updates to.
max_connectionsint | NonedefaultNoneMaximum allowed number of simultaneous HTTPS connections.
allowed_updateslist | NonedefaultNoneList of update types to receive.
secret_tokenstr | NonedefaultNoneSecret token sent in the X-Telegram-Bot-Api-Secret-Token header.
{"status": "success", "result": bool, "webhook_url": str}result is Telegram's own acknowledgement, a boolean rather than an object, and is True when the webhook was accepted. webhook_url echoes the url that was passed. Telegram requires an HTTPS url. An empty url, which is how Telegram removes a webhook, is rejected before the call, so a webhook cannot be cleared through this mode. certificate, ip_address and drop_pending_updates are not exposed.
get_me()Get basic info about the bot (test connection).
result = qbash.integrations.telegram.get_me()
{"status": "success", "bot": dict}bot is the Telegram User for this token narrowed to id, is_bot, first_name, last_name, username, can_join_groups, can_read_all_group_messages and supports_inline_queries, with null valued keys removed. id, is_bot and first_name are always present. last_name and username are absent when the bot has none, and the capability flags are absent unless Telegram sends them, so read them with .get().
pin_message()Pin a message in a chat.
result = qbash.integrations.telegram.pin_message(
chat_id="…",
message_id=0,
)
chat_idstrrequiredChat ID or username.
message_idintrequiredID of the message to pin.
disable_notificationbool | NonedefaultNonePin silently without notification.
{"status": "success", "result": bool, "chat_id": str, "message_id": int}unpin_message()Unpin a message in a chat.
result = qbash.integrations.telegram.unpin_message(
chat_id="…",
)
chat_idstrrequiredChat ID or username.
message_idint | NonedefaultNoneID of the message to unpin (unpins the most recent if omitted).
{"status": "success", "result": bool, "chat_id": str}Omitting message_id unpins the most recently pinned message. Unlike pin_message the result does not echo message_id, so a caller that needs to know which message was unpinned has to track it. result is a boolean acknowledgement and is True even when there was nothing pinned to remove.
set_chat_title()Set the title of a chat.
result = qbash.integrations.telegram.set_chat_title(
chat_id="…",
title="…",
)
chat_idstrrequiredChat ID or username.
titlestrrequiredNew chat title.
{"status": "success", "result": bool, "chat_id": str}set_chat_description()Set the description of a chat.
result = qbash.integrations.telegram.set_chat_description(
chat_id="…",
)
chat_idstrrequiredChat ID or username.
descriptionstr | Nonedefault""New chat description (pass empty string to clear).
{"status": "success", "result": bool, "chat_id": str}description falls back to an empty string when it is omitted, and an empty string clears the existing description, so calling this mode with only chat_id wipes the current description. The previous description is not returned, so read it with get_chat first if it needs keeping.
leave_chat()Leave a group, supergroup, or channel.
result = qbash.integrations.telegram.leave_chat(
chat_id="…",
)
chat_idstrrequiredChat ID or username.
{"status": "success", "result": bool, "chat_id": str}The bot loses access to the chat and cannot rejoin on its own. A chat administrator has to add it again, so treat this as irreversible from the task's point of view. result is a boolean acknowledgement rather than an object.
send_chat_action()Send a chat action like typing indicator.
result = qbash.integrations.telegram.send_chat_action(
chat_id="…",
action="…",
)
chat_idstrrequiredChat ID or username.
actionstrrequiredAction to broadcast (typing / upload_photo / upload_document / etc.).
{"status": "success", "result": bool, "chat_id": str, "action": str}list_workspaces()List all workspaces.
result = qbash.integrations.typeform.list_workspaces(
page=0,
page_size=10,
)
pageintPage number (1-indexed).
page_sizeintdefault10Results per page (max 200).
searchstrFilter workspaces by name substring.
{"status": "success", "workspaces": list[dict] | None, "total": int | None}workspaces is the items array of Typeform's response and total is its total_items, both read straight off the body and both null when the body carries no such key, so read them with .get. Only one page is returned per call and nothing here loops. Walk further pages with the page and page_size params, since no cursor is returned. The page_count Typeform reports is dropped here, so total and page_size are what a loop has to work from. Each workspace is the vendor's own record, unchanged.
get_workspace()Get a workspace by ID.
result = qbash.integrations.typeform.get_workspace(
workspace_id="…",
)
workspace_idstrrequiredWorkspace ID.
{"status": "success", "workspace": dict | list | str | int | float | bool | None}workspace holds the record. It is Typeform's response body forwarded unchanged, so its keys are the vendor's and nothing here renames, trims or checks them. A workspace id that does not exist comes back as an error rather than an empty result.
list_forms()List forms, optionally filtered by workspace.
result = qbash.integrations.typeform.list_forms(
workspace_id="…",
search="…",
)
workspace_idstrFilter by workspace ID.
searchstrSearch forms by title.
pageintPage number.
page_sizeintdefault10Results per page (max 200).
{"status": "success", "forms": list[dict] | None, "total": int | None}forms is the items array of Typeform's response and total is its total_items, both read straight off the body and both null when the body carries no such key, so read them with .get. Only one page is returned per call and nothing here loops. Walk further pages with the page and page_size params, since no cursor is returned and the page_count Typeform reports is dropped here. total is the count Typeform reported for the whole query rather than the length of forms. Each entry is the vendor's own record, unchanged, and get_form is what returns a form definition.
get_form()Get a form definition by ID (fields, logic, settings).
result = qbash.integrations.typeform.get_form(
form_id="…",
)
form_idstrrequiredForm ID.
{"status": "success", "form": dict | list | str | int | float | bool | None}form holds the whole definition, including fields, logic and settings. It is Typeform's response body forwarded unchanged, so its keys are the vendor's and nothing here renames, trims or checks them. Read a form with this mode before calling update_form, since update_form replaces the definition and drops anything left out.
create_form()Create a new form with fields, settings, and logic.
result = qbash.integrations.typeform.create_form(
title="…",
fields=[...],
)
titlestrrequiredForm title.
typestrdefaultform'form' or 'quiz'.
fieldslistrequiredArray of field definitions. Each has: type, title, ref, properties, validations.
settingsdictForm settings (language, progress_bar, meta, etc.).
logiclistLogic jump definitions for conditional branching.
welcome_screenslistWelcome screen definitions.
thankyou_screenslistThank-you screen definitions.
theme_hrefstrTheme API URL (e.g. https://api.typeform.com/themes/{id}).
workspace_hrefstrWorkspace API URL to place the form in.
hiddenlistHidden field names for URL parameter passthrough.
variablesdictVariable definitions as key-value pairs (e.g. {"score": 0, "price": 0}).
{"status": "success", "form": dict | list | str | int | float | bool | None}form holds the created form as Typeform stored it, so it carries the id and the refs Typeform assigned to fields that were sent without one. It is Typeform's response body forwarded unchanged, so its keys are the vendor's and nothing here renames, trims or checks them. When Typeform answers a create with an empty 201 the client substitutes an envelope holding status and location keys, and that envelope is what lands here instead of the record, so read this value with .get rather than indexing.
update_form()Full-replace a form definition (PUT). Use get_form first to preserve unchanged fields.
result = qbash.integrations.typeform.update_form(
form_id="…",
title="…",
fields=[...],
)
form_idstrrequiredForm ID to update.
titlestrrequiredForm title (required for PUT).
typestr'form' or 'quiz' (omitting on PUT resets to 'form').
fieldslistrequiredFull fields array. Required, and omitting it removes every field from the form.
settingsdictForm settings.
logiclistLogic jump definitions.
welcome_screenslistWelcome screen definitions.
thankyou_screenslistThank-you screen definitions.
hiddenlistHidden field names (omitting on PUT removes them).
variableslistVariable definitions (omitting on PUT removes them).
theme_hrefstrTheme API URL (omitting on PUT resets to default).
workspace_hrefstrWorkspace API URL (omitting on PUT may change workspace).
{"status": "success", "form": dict | list | str | int | float | bool | None}form holds Typeform's response body forwarded unchanged, so its keys are the vendor's and nothing here renames, trims or checks them. Typeform answers this PUT with 204 and no body in some cases, and that arrives as form set to a map holding only "status", which is not the form definition. Check for the form's own keys before reading them, and call get_form when the definition after the replace is what is needed. This is a PUT, so every part of the definition left out of the request is gone.
delete_form()Delete a form.
result = qbash.integrations.typeform.delete_form(
form_id="…",
)
form_idstrrequiredForm ID to delete.
{"status": "success"}status is the only key. Typeform's response body is discarded and any 2xx counts as success, so the result confirms nothing about what was removed. An id that does not exist comes back as an error rather than a success with no effect.
list_responses()List form responses with filtering and pagination.
result = qbash.integrations.typeform.list_responses(
form_id="…",
)
form_idstrrequiredForm ID.
page_sizeintdefault25Results per page (max 1000).
sincestrOnly responses after this ISO 8601 datetime.
untilstrOnly responses before this ISO 8601 datetime.
afterstrCursor token for forward pagination.
beforestrCursor token for backward pagination.
completedboolIf true, only completed responses.
querystrSearch within response answers.
fieldsstrComma-separated field IDs to include in results.
{"status": "success", "responses": list[dict] | None, "total": int | None, "page_count": int | None}responses is the items array of Typeform's response, total is its total_items and page_count is its page_count, all read straight off the body and all null when the body carries no such key, so read them with .get. total is the count Typeform reported for the whole query rather than the length of responses. Only one page is returned per call and nothing here loops. No continuation cursor is passed through, so paging further means supplying after or before yourself from values on the page already in hand. Each response is the vendor's record, unchanged, and the fields param narrows the answers inside it.
delete_responses()Delete form responses by response ID(s).
result = qbash.integrations.typeform.delete_responses(
form_id="…",
included_response_ids="…",
)
form_idstrrequiredForm ID.
included_response_idsstrrequiredComma-separated response IDs to delete (from list_responses results).
{"status": "success"}status is the only key. The result reports no per id outcome and no count, so it does not say how many of the ids matched a response. Typeform's response body is discarded and any 2xx counts as success. included_response_ids is sent as the comma separated string it was given, without being split or checked.
list_images()List all images in the account.
result = qbash.integrations.typeform.list_images()
{"status": "success", "images": list[dict], "count": int}images is the array Typeform returned, unchanged, and count is its length. No paging param is sent and none is exposed, so what comes back is one response from the account images endpoint. Each entry is the vendor's image record. A body that is not an array raises rather than returning an error tuple, so treat a non list body as a failed call.
get_image()Get image details and URLs by ID.
result = qbash.integrations.typeform.get_image(
image_id="…",
)
image_idstrrequiredImage ID.
{"status": "success", "image": dict | list | str | int | float | bool | None}The request asks for application/json, so image holds the image record with its urls and metadata rather than the image bytes. It is Typeform's response body forwarded unchanged, so its keys are the vendor's and nothing here renames, trims or checks them. Downloading the bytes is not exposed here and needs qbash.http against the url in the record.
upload_image()Upload an image (base64 or URL).
result = qbash.integrations.typeform.upload_image(
file_name="…",
)
file_namestrrequiredImage file name.
imagestrBase64-encoded image data (use this OR url, not both).
urlstrPublic URL of image to import (use this OR image, not both).
{"status": "success", "image_url": str | None} or {"status": "success", "image": dict | list | str | int | float | bool | None}Two shapes come back and status is success for both, so branch on which key is present rather than on status. Typeform answering with an empty 201 gives image_url, taken from the Location header of that response, and it is null when the header is missing. Typeform answering with a body gives image instead, holding that body unchanged. Read both with .get. Supply either image as base64 or url, and note that nothing here checks that exactly one of them was sent.
delete_image()Delete an image.
result = qbash.integrations.typeform.delete_image(
image_id="…",
)
image_idstrrequiredImage ID to delete.
{"status": "success"}status is the only key. Typeform's response body is discarded and any 2xx counts as success, so the result confirms nothing about what was removed. An id that does not exist comes back as an error rather than a success with no effect.
list_themes()List all themes.
result = qbash.integrations.typeform.list_themes(
page=0,
page_size=10,
)
pageintPage number.
page_sizeintdefault10Results per page.
{"status": "success", "themes": list[dict] | None, "count": int | None}themes is the items array of Typeform's response and count is its total_items, so count is the number of themes on the account rather than the number in themes. Both are read straight off the body and both are null when the body carries no such key, so read them with .get. Only one page is returned per call and nothing here loops. Walk further pages with the page and page_size params. Each theme is the vendor's record, unchanged.
get_theme()Get a theme by ID.
result = qbash.integrations.typeform.get_theme(
theme_id="…",
)
theme_idstrrequiredTheme ID.
{"status": "success", "theme": dict | list | str | int | float | bool | None}theme holds the record. It is Typeform's response body forwarded unchanged, so its keys are the vendor's and nothing here renames, trims or checks them. Read a theme with this mode before calling update_theme, since update_theme replaces the whole theme.
create_theme()Create a custom theme.
result = qbash.integrations.typeform.create_theme(
name="…",
)
namestrrequiredTheme name.
colorsdictColor settings: {question, answer, button, background}.
fontstrFont family name.
has_transparent_buttonboolIf true, buttons are transparent.
backgrounddictBackground config: {href, layout, brightness}.
{"status": "success", "theme": dict | list | str | int | float | bool | None}theme holds the created theme as Typeform stored it, so it carries the id and any value Typeform defaulted. It is Typeform's response body forwarded unchanged, so its keys are the vendor's and nothing here renames, trims or checks them. When Typeform answers a create with an empty 201 the client substitutes an envelope holding status and location keys, and that envelope is what lands here instead of the record, so read this value with .get rather than indexing. The theme_href param on create_form and update_form expects the theme api url of a theme created this way.
update_theme()Full-replace a theme (PUT). Use get_theme first to preserve unchanged fields.
result = qbash.integrations.typeform.update_theme(
theme_id="…",
name="…",
colors={"key": "value"},
font="…",
)
theme_idstrrequiredTheme ID to update.
namestrrequiredTheme name (required for PUT).
colorsdictrequiredColor settings. Required, and omitting it resets them to the defaults. Keys: question, answer, button, background.
fontstrrequiredFont family name. Required, and omitting it resets the font to the default.
has_transparent_buttonboolIf true, buttons are transparent.
backgrounddictBackground config.
visibilitystrTheme visibility: 'private' or 'public' (omitting on PUT resets).
rounded_cornersstrCorner style: 'none', 'small', or 'large' (omitting on PUT resets).
{"status": "success", "theme": dict | list | str | int | float | bool | None}theme holds Typeform's response body forwarded unchanged, so its keys are the vendor's and nothing here renames, trims or checks them. Typeform answers this PUT with 204 and no body in some cases, and that arrives as theme set to a map holding only "status", which is not the theme. Check for the theme's own keys before reading them, and call get_theme when the theme after the replace is what is needed. This is a PUT, so anything left out of the request is reset rather than kept.
delete_theme()Delete a theme.
result = qbash.integrations.typeform.delete_theme(
theme_id="…",
)
theme_idstrrequiredTheme ID to delete.
{"status": "success"}status is the only key. Typeform's response body is discarded and any 2xx counts as success, so the result confirms nothing about what was removed. An id that does not exist comes back as an error rather than a success with no effect.
get_account()Get the connected Vimeo account: name, plan, video count, storage use and the scopes the credential holds.
result = qbash.integrations.vimeo.get_account()
{"status": "success", "account": dict, "scopes": list[str] | None, "auth_method": str, "api_host": str | None}account has user_id, uri, name, link, location, bio, account_type (the plan, e.g. free, standard, advanced, enterprise), created_time, video_count, storage_used_bytes, storage_max_bytes and storage_free_bytes. scopes comes from /oauth/verify and is None when Vimeo does not return it. api_host is set only for an Enterprise custom subdomain.
list_videos()List the account's videos with name, link, duration, privacy, plays, likes, comments and tags. Filter by text or tag, and sort by date, plays, likes and more.
result = qbash.integrations.vimeo.list_videos(
query="search terms",
sort="…",
)
querystr | NonedefaultNoneOnly items whose name or description matches this text.
sortstr | NonedefaultNonedate, alphabetical, plays, likes, duration, modified_time, last_user_action_event_date or default.
directionstr | NonedefaultNoneasc or desc. Applies to sort.
filterstr | NonedefaultNoneOne of embeddable, featured, live, nolive, playable, no_placeholder or screen_recorded.
filter_tagstr | NonedefaultNoneOnly videos with this tag. Several tags can be comma separated and match any of them.
user_idstr | NonedefaultNoneNumeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "videos": list[dict], "total": int | None, "next_cursor": str | None}GET /me/videos. Each video has video_id, uri, name, description, link, embed_url, duration (seconds), width, height, language, created_time, modified_time, release_time, status, is_playable, privacy_view, privacy_embed, privacy_download, privacy_comments, plays, comment_count, like_count, text_track_count, tags, thumbnail_url, owner_name, owner_uri, folder_id, folder_name, transcode_status, upload_status, content_rating and license. plays is the lifetime play count Vimeo reports on the video. Paging: pass next_cursor back as cursor until it is None. Keys whose value is null are left out, so read optional fields with .get().
get_video()Get one video by ID or URL with its details, privacy settings, play count and tags.
result = qbash.integrations.vimeo.get_video(
video_id="…",
)
video_idstrrequiredVimeo video ID or URL, e.g. 76979871 or https://vimeo.com/76979871.
{"status": "success", "video": dict}GET /videos/<id>. video has the keys listed for list_videos. A video URL such as https://vimeo.com/76979871 or https://player.vimeo.com/video/76979871 also works as the ID.
list_folders()List the account's folders with video and subfolder counts.
result = qbash.integrations.vimeo.list_folders(
query="search terms",
sort="…",
)
querystr | NonedefaultNoneOnly items whose name or description matches this text.
sortstr | NonedefaultNonedate, name, modified_time, pinned_on or default.
directionstr | NonedefaultNoneasc or desc. Applies to sort.
user_idstr | NonedefaultNoneNumeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "folders": list[dict], "total": int | None, "next_cursor": str | None}GET /me/projects. Each folder has folder_id, uri, name, link, created_time, modified_time, last_activity_time, privacy_view, video_count and subfolder_count. Paging: pass next_cursor back as cursor until it is None. Keys whose value is null are left out, so read optional fields with .get().
list_folder_videos()List the videos in one folder, optionally including its subfolders.
result = qbash.integrations.vimeo.list_folder_videos(
folder_id="my-folder",
)
folder_idstrrequiredFolder ID from list_folders, or the folder's URL.
include_subfoldersbooldefaultFalseAlso list videos in the folder's subfolders.
querystr | NonedefaultNoneOnly items whose name or description matches this text.
sortstr | NonedefaultNonedate, alphabetical, duration, last_user_action_event_date or default.
directionstr | NonedefaultNoneasc or desc. Applies to sort.
filter_tagstr | NonedefaultNoneOnly videos with this tag. Several tags can be comma separated and match any of them.
user_idstr | NonedefaultNoneNumeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "videos": list[dict], "total": int | None, "next_cursor": str | None}GET /me/projects/<folder_id>/videos. Videos have the keys listed for list_videos. Paging: pass next_cursor back as cursor until it is None. Keys whose value is null are left out, so read optional fields with .get().
list_showcases()List the account's showcases with video counts and privacy.
result = qbash.integrations.vimeo.list_showcases(
query="search terms",
sort="…",
)
querystr | NonedefaultNoneOnly items whose name or description matches this text.
sortstr | NonedefaultNonedate, alphabetical, duration, last_modified or videos.
directionstr | NonedefaultNoneasc or desc. Applies to sort.
user_idstr | NonedefaultNoneNumeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "showcases": list[dict], "total": int | None, "next_cursor": str | None}GET /me/albums. Each showcase has showcase_id, uri, name, description, link, created_time, modified_time, duration, privacy_view, video_count and thumbnail_url. Paging: pass next_cursor back as cursor until it is None. Keys whose value is null are left out, so read optional fields with .get().
list_showcase_videos()List the videos in one showcase.
result = qbash.integrations.vimeo.list_showcase_videos(
showcase_id="…",
)
showcase_idstrrequiredShowcase ID from list_showcases, or the showcase's URL.
querystr | NonedefaultNoneOnly items whose name or description matches this text.
sortstr | NonedefaultNonemanual (the showcase's own order), date, alphabetical, plays, likes, comments, duration, modified_time or default.
directionstr | NonedefaultNoneasc or desc. Applies to sort.
filter_tagstr | NonedefaultNoneOnly videos with this tag. Several tags can be comma separated and match any of them.
user_idstr | NonedefaultNoneNumeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "videos": list[dict], "total": int | None, "next_cursor": str | None}GET /me/albums/<showcase_id>/videos. Only the showcase owner (or a team member passing the owner's user_id) can list it. Videos have the keys listed for list_videos. Paging: pass next_cursor back as cursor until it is None. Keys whose value is null are left out, so read optional fields with .get().
list_comments()List the comments on a video, or the replies to one comment.
result = qbash.integrations.vimeo.list_comments(
video_id="…",
)
video_idstrrequiredVimeo video ID or URL, e.g. 76979871 or https://vimeo.com/76979871.
parent_comment_idstr | NonedefaultNoneA comment_id from a previous call, to list that comment's replies instead.
directionstr | NonedefaultNoneasc or desc. Applies to sort.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "comments": list[dict], "total": int | None, "next_cursor": str | None}GET /videos/<id>/comments, or /videos/<id>/comments/<comment_id>/replies when parent_comment_id is set. Each comment has comment_id, uri, text, created_on, author_name, author_uri, author_link and reply_count. Paging: pass next_cursor back as cursor until it is None. Keys whose value is null are left out, so read optional fields with .get().
list_text_tracks()List a video's captions and subtitles with language, type and a download link.
result = qbash.integrations.vimeo.list_text_tracks(
video_id="…",
)
video_idstrrequiredVimeo video ID or URL, e.g. 76979871 or https://vimeo.com/76979871.
{"status": "success", "text_tracks": list[dict], "total": int | None}GET /videos/<id>/texttracks. Each track has text_track_id, uri, language, display_language, name, type (captions, subtitles or another kind), active, link (a WebVTT file) and link_expires_time. Only the video's owner can list them. The link expires, so fetch a fresh one rather than storing it. Use get_transcript for the text itself.
get_transcript()Get a video's transcript as plain text from its captions or subtitles, optionally with timed segments.
result = qbash.integrations.vimeo.get_transcript(
video_id="…",
)
video_idstrrequiredVimeo video ID or URL, e.g. 76979871 or https://vimeo.com/76979871.
text_track_idstr | NonedefaultNoneA text_track_id from list_text_tracks. Takes precedence over language.
languagestr | NonedefaultNoneLanguage code to pick a track, e.g. en or es-MX.
include_segmentsbooldefaultFalseAlso return the timed segments.
{"status": "success", "video_id": str, "text_track_id": str, "language": str | None, "name": str | None, "type": str | None, "text": str, "segments": list[{"start": float, "end": float, "text": str}] | None, "available_tracks": list[dict]}Reads the video's text tracks, picks one, downloads its WebVTT file and strips the timing and markup. With neither text_track_id nor language it takes the first active track. language matches a prefix, so en finds en-US. segments is present only when include_segments is True, with start and end in seconds. available_tracks lists every track's text_track_id, language and type, so a second call can pick another. Only the video's owner can read its text tracks. A video without captions returns an error, and Vimeo's automatic captions appear once they finish processing.
list_chapters()List a video's chapters with title and start time.
result = qbash.integrations.vimeo.list_chapters(
video_id="…",
)
video_idstrrequiredVimeo video ID or URL, e.g. 76979871 or https://vimeo.com/76979871.
{"status": "success", "chapters": list[dict], "total": int | None}GET /videos/<id>/chapters. Each chapter has chapter_id, uri, title and timecode (seconds from the start).
search_videos()Search public Vimeo videos by text, sorted by relevance, date, plays or likes.
result = qbash.integrations.vimeo.search_videos(
query="search terms",
)
querystrrequiredSearch text.
sortstr | NonedefaultNonerelevant, date, alphabetical, plays, likes, comments or duration.
directionstr | NonedefaultNoneasc or desc. Applies to sort.
filterstr | NonedefaultNoneA Creative Commons license (CC, CC-BY, CC-BY-SA, CC-BY-ND, CC-BY-NC, CC-BY-NC-SA, CC-BY-NC-ND, in-progress), trending or upload_date.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "videos": list[dict], "total": int | None, "next_cursor": str | None}GET /videos. Searches every public video on Vimeo, not only the account's. Videos have the keys listed for list_videos. Vimeo sometimes turns search off and answers 503, so a task should treat that as a skip. Paging: pass next_cursor back as cursor until it is None. Keys whose value is null are left out, so read optional fields with .get().
get_analytics()Get Vimeo analytics (views, impressions, finishes, watch time) for a date range, grouped by video, country, device or another dimension. Enterprise plans only.
result = qbash.integrations.vimeo.get_analytics(
dimension="…",
start_date="…",
end_date="…",
)
dimensionstrrequiredHow to group rows: video, country, region, city, device_type, os, browser, embed_domain, embed_domain_path, streaming_type or total.
start_datestrrequiredFirst day of the period, YYYY-MM-DD (UTC) or a full ISO 8601 datetime.
end_datestrrequiredLast day of the period, YYYY-MM-DD (UTC, inclusive) or a full ISO 8601 datetime.
time_intervalstr | NonedefaultNoneSplit each row by day, week, month or year. Omit for one total per dimension value.
video_idslist[str] | NonedefaultNoneOnly these videos (IDs or URLs).
countrieslist[str] | NonedefaultNoneOnly these countries, as ISO 3166 alpha-2 codes such as US.
device_typeslist[str] | NonedefaultNoneOnly these device types, e.g. desktop, mobile, tablet or tv.
embed_domainslist[str] | NonedefaultNoneOnly plays embedded on these domains.
sortstr | NonedefaultNoneMetric to sort by, e.g. views, impressions, finishes or total_seconds_watched.
directionstr | NonedefaultNoneasc or desc. Applies to sort.
user_idstr | NonedefaultNoneNumeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
limitintdefault100Rows per page, from 1 through 1000.
{"status": "success", "rows": list[dict], "total": int | None, "next_cursor": str | None}GET /me/analytics. Needs an Enterprise plan, the stats scope and API analytics turned on for the app by Vimeo Support. Without them, Vimeo answers 403 or 404. Each row holds the dimension's value and metrics such as views, impressions, unique_impressions, unique_viewers, finishes, completions, downloads, total_seconds_watched, mean_seconds_watched, mean_percent_watched, comments and likes, plus the period when time_interval is set. Rows are passed through as Vimeo returns them. Paging: pass next_cursor back as cursor until it is None. Keys whose value is null are left out, so read optional fields with .get().
get_site_info()Get the site's name, description, URL and timezone, and the connected user's name and roles.
result = qbash.integrations.wordpress.get_site_info()
{"status": "success", "auth_method": str, "name": str, "description": str, "url": str, "home": str, "timezone_string": str | None, "gmt_offset": float | None, "namespaces": list[str] | None, "blog_id": int | None, "user": dict | None}With an application password this reads the REST index and users/me in the edit context, so user carries id, name, slug, username, email and roles. With WordPress.com it reads the v1.1 site and /me endpoints, so namespaces is absent and user has id, name, username and email. Keys whose value is null are left out.
list_posts()List posts with title, status, dates, author, categories, tags and link. Filter by status, text, dates, author, category or tag.
result = qbash.integrations.wordpress.list_posts(
status="…",
search="…",
)
statusstrdefault"publish"publish, future, draft, pending, private or any. Several can be comma separated.
searchstr | NonedefaultNoneOnly items matching this text.
afterstr | NonedefaultNoneOnly items published after this ISO 8601 datetime, in the site's timezone.
beforestr | NonedefaultNoneOnly items published before this ISO 8601 datetime, in the site's timezone.
modified_afterstr | NonedefaultNoneOnly items modified after this ISO 8601 datetime. Pair with orderby="modified" and order="asc" for incremental syncs.
authorint | NonedefaultNoneOnly items by this user id.
categorieslist[int] | NonedefaultNoneOnly posts in any of these category ids.
tagslist[int] | NonedefaultNoneOnly posts with any of these tag ids.
orderbystr | NonedefaultNonedate (the default), modified, title, id, slug, relevance (with search) or menu_order (pages).
orderstr | NonedefaultNonedesc (the default) or asc.
contextstrdefault"view"view (rendered fields) or edit (adds raw title and content and private fields, and needs edit rights on the items).
include_contentbooldefaultFalseAdd each item's rendered content. Off by default to keep pages small.
cursorstr | NonedefaultNonenext_cursor from the previous page (a page number). Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "posts": list[dict], "total": int | None, "next_cursor": str | None}Each post has id, type, status, slug, link, title, excerpt, date, date_gmt, modified, modified_gmt, author, featured_media, comment_status, sticky, categories and tags, plus content when include_content is True. Drafts, pending and private posts need Contributor or above for the user's own and Editor for everyone's. Paging: pass next_cursor back as cursor until it is None. total is the item count WordPress reported, or None when the host strips the X-WP-Total header. Keys whose value is null are left out, so read optional fields with .get(). Titles and content are rendered HTML, so they can carry HTML entities in place of quotes and dashes.
get_post()Get one post by id, including its rendered content.
result = qbash.integrations.wordpress.get_post(
post_id=0,
)
post_idintrequiredPost id.
contextstrdefault"view"view (rendered fields) or edit (adds raw title and content and private fields, and needs edit rights on the items).
{"status": "success", "post": dict}post has the list_posts keys plus content. With context="edit" it also has title_raw, content_raw and excerpt_raw, the stored markup before rendering.
list_pages()List pages with title, status, dates, parent page and link.
result = qbash.integrations.wordpress.list_pages(
status="…",
search="…",
)
statusstrdefault"publish"publish, future, draft, pending, private or any. Several can be comma separated.
searchstr | NonedefaultNoneOnly items matching this text.
afterstr | NonedefaultNoneOnly items published after this ISO 8601 datetime, in the site's timezone.
beforestr | NonedefaultNoneOnly items published before this ISO 8601 datetime, in the site's timezone.
modified_afterstr | NonedefaultNoneOnly items modified after this ISO 8601 datetime. Pair with orderby="modified" and order="asc" for incremental syncs.
authorint | NonedefaultNoneOnly items by this user id.
parentint | NonedefaultNoneOnly children of this page id. 0 lists top-level pages.
orderbystr | NonedefaultNonedate (the default), modified, title, id, slug, relevance (with search) or menu_order (pages).
orderstr | NonedefaultNonedesc (the default) or asc.
contextstrdefault"view"view (rendered fields) or edit (adds raw title and content and private fields, and needs edit rights on the items).
include_contentbooldefaultFalseAdd each item's rendered content. Off by default to keep pages small.
cursorstr | NonedefaultNonenext_cursor from the previous page (a page number). Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "pages": list[dict], "total": int | None, "next_cursor": str | None}Each page has the list_posts keys without categories and tags, plus parent and menu_order. Draft and private pages need Editor or above. Paging: pass next_cursor back as cursor until it is None. total is the item count WordPress reported, or None when the host strips the X-WP-Total header. Keys whose value is null are left out, so read optional fields with .get(). Titles and content are rendered HTML, so they can carry HTML entities in place of quotes and dashes.
get_page()Get one page by id, including its rendered content.
result = qbash.integrations.wordpress.get_page(
page_id=0,
)
page_idintrequiredPage id.
contextstrdefault"view"view (rendered fields) or edit (adds raw title and content and private fields, and needs edit rights on the items).
{"status": "success", "page": dict}page has the list_pages keys plus content. With context="edit" it also has title_raw, content_raw and excerpt_raw.
list_comments()List comments, for one post or across the site, by moderation status: approve, hold or spam.
result = qbash.integrations.wordpress.list_comments(
post=0,
status="…",
)
postint | NonedefaultNoneOnly comments on this post id.
statusstrdefault"approve"approve, hold or spam.
searchstr | NonedefaultNoneOnly items matching this text.
afterstr | NonedefaultNoneOnly items published after this ISO 8601 datetime, in the site's timezone.
parentint | NonedefaultNoneOnly replies to this comment id. 0 lists top-level comments.
contextstrdefault"view"view (rendered fields) or edit (adds raw title and content and private fields, and needs edit rights on the items).
cursorstr | NonedefaultNonenext_cursor from the previous page (a page number). Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "comments": list[dict], "total": int | None, "next_cursor": str | None}Each comment has id, post, parent, author, author_name, author_url, date, date_gmt, status, type, link and content. author_email needs context="edit". Held and spam comments need Editor or above. Paging: pass next_cursor back as cursor until it is None. total is the item count WordPress reported, or None when the host strips the X-WP-Total header. Keys whose value is null are left out, so read optional fields with .get(). Titles and content are rendered HTML, so they can carry HTML entities in place of quotes and dashes.
list_categories()List post categories with their slug, parent and post count.
result = qbash.integrations.wordpress.list_categories(
search="…",
parent=0,
)
searchstr | NonedefaultNoneOnly items matching this text.
parentint | NonedefaultNoneOnly children of this category id. 0 lists top-level categories.
hide_emptybooldefaultFalseLeave out terms with no posts.
cursorstr | NonedefaultNonenext_cursor from the previous page (a page number). Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "categories": list[dict], "total": int | None, "next_cursor": str | None}Each category has id, name, slug, description, count, parent, taxonomy and link. Use the ids for list_posts filters and create_post. Paging: pass next_cursor back as cursor until it is None. total is the item count WordPress reported, or None when the host strips the X-WP-Total header. Keys whose value is null are left out, so read optional fields with .get(). Titles and content are rendered HTML, so they can carry HTML entities in place of quotes and dashes.
list_tags()List post tags with their slug and post count.
result = qbash.integrations.wordpress.list_tags(
search="…",
hide_empty=True,
)
searchstr | NonedefaultNoneOnly items matching this text.
hide_emptybooldefaultFalseLeave out terms with no posts.
cursorstr | NonedefaultNonenext_cursor from the previous page (a page number). Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "tags": list[dict], "total": int | None, "next_cursor": str | None}Each tag has id, name, slug, description, count, taxonomy and link. Paging: pass next_cursor back as cursor until it is None. total is the item count WordPress reported, or None when the host strips the X-WP-Total header. Keys whose value is null are left out, so read optional fields with .get(). Titles and content are rendered HTML, so they can carry HTML entities in place of quotes and dashes.
list_users()List the site's users. Emails, usernames and roles need context="edit" and an Administrator.
result = qbash.integrations.wordpress.list_users(
search="…",
roles=[...],
)
searchstr | NonedefaultNoneOnly items matching this text.
roleslist[str] | NonedefaultNoneOnly users with any of these roles, such as administrator, editor, author. Needs Administrator.
contextstrdefault"view"view (rendered fields) or edit (adds raw title and content and private fields, and needs edit rights on the items).
cursorstr | NonedefaultNonenext_cursor from the previous page (a page number). Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "users": list[dict], "total": int | None, "next_cursor": str | None}Each user has id, name, slug, url, description and link, plus username, email, roles and registered_date in the edit context. In the view context WordPress lists only users who have published posts. WordPress.com restricts user endpoints, so the edit context and the roles filter may be refused there. Paging: pass next_cursor back as cursor until it is None. total is the item count WordPress reported, or None when the host strips the X-WP-Total header. Keys whose value is null are left out, so read optional fields with .get(). Titles and content are rendered HTML, so they can carry HTML entities in place of quotes and dashes.
list_media()List media library items with file URL, MIME type, size and alt text.
result = qbash.integrations.wordpress.list_media(
search="…",
media_type="…",
)
searchstr | NonedefaultNoneOnly items matching this text.
media_typestr | NonedefaultNoneimage, video, text, application or audio.
parentint | NonedefaultNoneOnly media attached to this post id.
afterstr | NonedefaultNoneOnly items published after this ISO 8601 datetime, in the site's timezone.
cursorstr | NonedefaultNonenext_cursor from the previous page (a page number). Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "media": list[dict], "total": int | None, "next_cursor": str | None}Each item has id, date, slug, status, link, author, post (the attached post id), title, caption, alt_text, media_type, mime_type, source_url, width and height. Paging: pass next_cursor back as cursor until it is None. total is the item count WordPress reported, or None when the host strips the X-WP-Total header. Keys whose value is null are left out, so read optional fields with .get(). Titles and content are rendered HTML, so they can carry HTML entities in place of quotes and dashes.
search()Search posts and pages by text and return each match's id, title, URL and type.
result = qbash.integrations.wordpress.search(
query="search terms",
)
querystrrequiredText to search for.
typestrdefault"post"post, term or post-format.
subtypestrdefault"any"post, page or any. With type="term": category or tag.
cursorstr | NonedefaultNonenext_cursor from the previous page (a page number). Omit for the first page.
limitintdefault25Items per page, from 1 through 100.
{"status": "success", "results": list[dict], "total": int | None, "next_cursor": str | None}Each result has id, title, url, type and subtype. Fetch the full item with get_post or get_page. On WordPress.com, when the search route is not available, this falls back to a text search of posts (or pages, with subtype="page"). Paging: pass next_cursor back as cursor until it is None. total is the item count WordPress reported, or None when the host strips the X-WP-Total header. Keys whose value is null are left out, so read optional fields with .get(). Titles and content are rendered HTML, so they can carry HTML entities in place of quotes and dashes.
create_post()Create a post, saved as a draft unless status is set. Pass status="publish" to publish it.
result = qbash.integrations.wordpress.create_post(
title="…",
)
titlestrrequiredPost title.
statusstrdefault"draft"draft, pending, private, publish or future. publish and future need Author or above.
contentstr | NonedefaultNonePost body as HTML or block markup.
excerptstr | NonedefaultNoneManual excerpt.
categorieslist[int] | NonedefaultNoneCategory ids from list_categories. Replaces the post's categories.
tagslist[int] | NonedefaultNoneTag ids from list_tags. Replaces the post's tags.
slugstr | NonedefaultNoneURL slug.
datestr | NonedefaultNonePublish date as an ISO 8601 datetime in the site's timezone. A future date with status future schedules the post.
featured_mediaint | NonedefaultNoneMedia id from list_media to use as the featured image.
{"status": "success", "post": dict}post has the get_post keys, including id, link and status. A draft needs Contributor or above. Publishing or scheduling needs Author or above.
update_post()Update a post's title, content, status, excerpt, categories, tags, slug, date or featured image. Fields left out stay as they are.
result = qbash.integrations.wordpress.update_post(
post_id=0,
)
post_idintrequiredPost id.
titlestr | NonedefaultNoneNew title.
statusstr | NonedefaultNonedraft, pending, private, publish or future. publish and future need Author or above.
contentstr | NonedefaultNonePost body as HTML or block markup.
excerptstr | NonedefaultNoneManual excerpt.
categorieslist[int] | NonedefaultNoneCategory ids from list_categories. Replaces the post's categories.
tagslist[int] | NonedefaultNoneTag ids from list_tags. Replaces the post's tags.
slugstr | NonedefaultNoneURL slug.
datestr | NonedefaultNonePublish date as an ISO 8601 datetime in the site's timezone. A future date with status future schedules the post.
featured_mediaint | NonedefaultNoneMedia id from list_media to use as the featured image.
{"status": "success", "post": dict}post has the get_post keys after the update. Pass at least one field. Editing the user's own posts needs Author or above, and editing other people's or published posts needs Editor or above.
create_comment()Post a comment as the connected user, or reply to a comment by passing parent.
result = qbash.integrations.wordpress.create_comment(
post_id=0,
content="…",
)
post_idintrequiredPost id to comment on.
contentstrrequiredComment text. Basic HTML is allowed.
parentint | NonedefaultNoneComment id to reply to.
{"status": "success", "comment": dict}comment has the list_comments keys. The post must be open for comments. Comments from Editors and above are approved at once, and others follow the site's discussion settings.
moderate_comment()Set a comment's status to approve, hold or spam.
result = qbash.integrations.wordpress.moderate_comment(
comment_id=0,
status="…",
)
comment_idintrequiredComment id.
statusstrrequiredapprove, hold or spam.
{"status": "success", "comment": dict}comment has the list_comments keys after the change. Needs Editor or above. Trashing and deleting are not offered.
get_stats()Get WordPress.com Stats: views and visitors for a period, or top posts by views. Needs a WordPress.com connection.
result = qbash.integrations.wordpress.get_stats(
report="…",
period="…",
)
reportstrdefault"summary"summary or top_posts.
periodstrdefault"day"day, week, month or year.
datestr | NonedefaultNoneLast day of the range as YYYY-MM-DD. Defaults to today in the site's timezone.
numintdefault1Number of periods to cover, from 1 through 90.
maxintdefault10top_posts only: posts per day, from 1 through 100.
{"status": "success", "report": str, "period": str, "date": str | None, "summary": dict | None, "days": list[dict] | None}report="summary" returns summary with views, visitors, likes, comments and followers over num periods ending on date. report="top_posts" returns days, newest first, each with date, total_views and posts (id, title, url, views, type), up to max posts per day. Stats come from WordPress.com and Jetpack, so an application password credential cannot read them.
get_me()Return the authenticated user's profile (requires OAuth user context).
result = qbash.integrations.x.get_me()
{"status": "success", "user": dict | None}Reads the X users/me endpoint and keeps only the data object from that response, under user. The profile carries id, name and username plus the description, public_metrics and created_at fields this mode asks for. A response body with no data object leaves user set to None, so check it before reading keys from it.
create_tweet()Publish a new tweet (requires OAuth user context).
result = qbash.integrations.x.create_tweet(
text="…",
)
textstrrequiredTweet text (max 280 characters).
reply_to_tweet_idstr | NonedefaultNoneTweet ID to reply to.
media_idslist[str] | NonedefaultNoneMedia ids from upload_media, up to 4 images or one video or GIF.
{"status": "success", "tweet": dict | None, "message": str}Publishes the tweet and keeps only the data object from the X response, under tweet. message is a fixed confirmation string built here rather than anything X returned. The text, an optional reply target and up to 4 media_ids from upload_media are sent, so polls, quote tweets, scheduling and reply settings are not exposed. X takes up to 4 images or one video or GIF per post and refuses a video that is still processing, so check get_media_status for succeeded before posting it. A response body with no data object leaves tweet set to None.
upload_media()Upload a stored image, GIF or video to X and return a media_id for create_tweet.
result = qbash.integrations.x.upload_media(
file=qbash.inputs["file"],
)
filefilerequiredThe file to upload: a file from qbash.inputs, another integration's file result, or its file_upload_id. Max 5 GB, qbash's file limit. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
media_categorystrdefault"tweet_video""tweet_video", "tweet_image" or "tweet_gif", matching the file.
max_wait_secondsintdefault60How long to wait for X to finish processing before returning, 0 to 300 seconds.
{"status": str, "media_id": str, "processing_state": str, "byte_size": int}Sends a stored file to the X media upload endpoint in 5 MB segments, read from storage one segment at a time, so a large video never sits in memory. The file is capped at qbash's 5 GB file limit. X allows video up to 8 GB and 20 minutes by default (16 GB and 125 minutes on Premium) and checks its own image and GIF limits. A long upload needs a run whose expected runtime is over 15 minutes. X processes video and GIFs after the upload, and this mode asks X for progress for up to max_wait_seconds. processing_state is succeeded once the media can be posted, and in_progress when the wait ran out first, and a state this mode does not know comes back as X sent it. Until it reads succeeded, call get_media_status with the media_id before passing the id to create_tweet. Processing that fails returns an error carrying X's message. byte_size is the size of the stored file. The X connection needs the media.write scope, so a connection made before media upload was available must reconnect, and the X app behind it must allow media upload.
get_media_status()Check whether X has finished processing an uploaded media id.
result = qbash.integrations.x.get_media_status(
media_id="…",
)
media_idstrrequiredThe media_id upload_media returned.
{"status": str, "media_id": str, "processing_state": str, "progress_percent": int | None, "check_after_secs": int | None}Reads the X media STATUS endpoint for a media_id from upload_media. processing_state is the state X reports: pending, in_progress or succeeded. A failed state returns an error carrying X's message. When X reports no processing for the media, as with most images, processing_state is succeeded and progress_percent and check_after_secs are None. While processing runs, wait check_after_secs seconds before asking again, and pass the media_id to create_tweet once it reports succeeded.
get_user()Look up a public user profile by username.
result = qbash.integrations.x.get_user(
username="…",
)
usernamestrrequiredX username without the @ prefix (e.g. "elonmusk").
{"status": "success", "user": dict | None}Reads the X lookup by username endpoint and keeps only the data object, under user. The rest of the response is dropped, including the errors array X uses to explain a lookup that found nothing, so a username that does not exist or is suspended arrives as user set to None with a success status. Check user for None before reading it. The profile carries id, name and username plus description, public_metrics, created_at, profile_image_url and verified.
get_tweet()Fetch a single tweet with public engagement metrics.
result = qbash.integrations.x.get_tweet(
tweet_id="…",
)
tweet_idstrrequiredNumeric tweet ID string.
{"status": "success", "tweet": dict | None}Reads the X single tweet endpoint and keeps only the data object, under tweet. The errors array X returns for a tweet that is deleted or not visible to this token is dropped, so those arrive as tweet set to None with a success status. The tweet carries id and text plus author_id, created_at, public_metrics and lang. No expansions are requested, so the author is identified by author_id alone and no name or handle comes back with it.
get_tweet_metrics()Fetch a tweet with extended metrics including non_public_metrics and organic_metrics for tweets authored by the authenticated user.
result = qbash.integrations.x.get_tweet_metrics(
tweet_id="…",
)
tweet_idstrrequiredNumeric tweet ID string.
{"status": "success", "tweet": dict | None, "auth_method": str, "note": str}Reads the same endpoint as get_tweet and additionally asks for non_public_metrics and organic_metrics. X returns those two only for tweets the authenticated user posted in the last 30 days, so read them with .get() and expect them to be missing on anything else. auth_method and note are fixed strings describing the request rather than data about the tweet. tweet is None when the response carries no data object, which is what a deleted or invisible tweet gives.
list_user_tweets()List recent tweets from a user by their numeric user ID.
result = qbash.integrations.x.list_user_tweets(
user_id="…",
)
user_idstrrequiredNumeric user ID string.
max_resultsintdefault10Max tweets to return (5 to 100).
pagination_tokenstr | NonedefaultNonePagination token from a previous response.
start_timestr | NonedefaultNoneISO 8601 datetime for the earliest tweet to include.
end_timestr | NonedefaultNoneISO 8601 datetime for the newest tweet to include.
{"status": "success", "tweets": list[dict], "count": int, "meta": dict}Returns one page of the user timeline. count is the number of tweets in this page rather than a total for the user. meta holds the X pagination fields, so read next_token from it with .get() and pass that value back as pagination_token to fetch the following page. next_token is absent on the last page, and meta is an empty dict when X sends no meta at all. Tweets here are raw X objects carrying id, text, author_id, created_at, public_metrics and lang, which is a different shape from the flattened tweets that search, user_timeline and user_mentions return.
search()Search recent posts on X using the X API v2.
result = qbash.integrations.x.search(
query="search terms",
)
querystrrequiredX API v2 search query string.
max_resultsint | Nonedefault10Number of results to return (10 to 100).
allowed_x_handlesstr | NonedefaultNoneComma-separated handles to restrict results to.
excluded_x_handlesstr | NonedefaultNoneComma-separated handles to exclude from results.
from_datestr | NonedefaultNoneStart date in YYYY-MM-DD format.
to_datestr | NonedefaultNoneEnd date in YYYY-MM-DD format.
{"tweets": list[dict], "result_count": int}This result has no status key. Each tweet is rebuilt here rather than passed through and holds exactly id, text, author_username, author_name, created_at, retweet_count, like_count, reply_count and url, with an empty string or zero filled in where X sent nothing. Everything else is dropped in that rebuild, including quote and impression counts, entities and lang, so use get_tweet_metrics when the full metric set matters. One page comes back and the X next_token is dropped, so max_results, which is forced into the range 10 to 100, is the only way to ask for more and there is no way to reach a second page. result_count is the count for this page. The endpoint is X recent search, so from_date and to_date narrow the recent window rather than reaching back into the full archive.
user_mentions()Fetch recent @mentions of a user by username.
result = qbash.integrations.x.user_mentions(
username="…",
)
usernamestrrequiredX username without the @ prefix.
max_resultsint | Nonedefault10Number of mentions to return (5 to 100).
{"tweets": list[dict], "result_count": int}This result has no status key. Tweets are the same rebuilt shape as search, holding exactly id, text, author_username, author_name, created_at, retweet_count, like_count, reply_count and url, so entities, lang and the quote and impression counts are not available here. The username is resolved to a numeric user ID with an extra lookup first, and a username X does not know fails with a lookup error rather than an empty list. One page comes back, max_results is forced into the range 5 to 100 and the X next_token is dropped, so there is no way to reach a second page. result_count is the count for this page.
tweet_counts()Get recent tweet volume counts for a search query.
result = qbash.integrations.x.tweet_counts(
query="search terms",
)
querystrrequiredX API v2 search query string.
granularitystr | Nonedefault"hour"Time bucket granularity: "minute", "hour", or "day".
{"buckets": list[dict], "total_count": int}This result has no status key. Each bucket holds exactly start, end and tweet_count, and any other field X sends for a bucket is dropped. total_count is the total_tweet_count X reports for the query and is 0 when the response carries no meta. The endpoint is the recent counts one, so the buckets cover the recent window rather than the full archive, and the X next_token is dropped, so a long range at minute granularity comes back as the first page of buckets with nothing in the result to signal the rest.
user_timeline()Fetch recent posts from a user's timeline by username.
result = qbash.integrations.x.user_timeline(
username="…",
)
usernamestrrequiredX username without the @ prefix.
max_resultsint | Nonedefault10Number of tweets to return (5 to 100).
excludestr | NonedefaultNoneComma-separated exclusions, e.g. "retweets,replies".
{"tweets": list[dict], "result_count": int}This result has no status key. Tweets are the same rebuilt shape as search, holding exactly id, text, author_username, author_name, created_at, retweet_count, like_count, reply_count and url, so entities, lang and the quote and impression counts are not available here. The username is resolved to a numeric user ID with an extra lookup first, and a username X does not know fails with a lookup error rather than an empty list. One page comes back, max_results is forced into the range 5 to 100 and the X next_token is dropped, so there is no way to reach a second page. result_count is the count for this page.
trends()Get trending topics for a location by WOEID.
result = qbash.integrations.x.trends(
woeid=0,
)
woeidint | Nonedefault1Where On Earth ID. Defaults to 1 (worldwide).
{"trends": list[dict]}This result has no status key and no count key. Each entry holds exactly name and tweet_count, and every other field X returns for a trend is dropped. tweet_count is None when X reports no volume for that trend, and name is None when the entry carries neither a name nor a trend_name key, so check both before using them. A woeid that is not a positive integer falls back to 1, which is worldwide, rather than failing.
model_search()Search X posts using Grok AI via the xAI Responses API.
result = qbash.integrations.grok_search.model_search(
query="search terms",
)
querystrrequiredSearch query to run against X posts.
modelstr | Nonedefault"grok-4.20-0309-non-reasoning"xAI model to use for the search.
allowed_x_handlesstr | NonedefaultNoneComma-separated X handles to restrict the search to. Up to 10 are used.
excluded_x_handlesstr | NonedefaultNoneComma-separated X handles to exclude from the search. Up to 10 are used.
from_datestr | NonedefaultNoneEarliest post date to search, in YYYY-MM-DD format.
to_datestr | NonedefaultNoneLatest post date to search, in YYYY-MM-DD format.
{"response": str, "citations": list[str], "result_count": int, "usage": dict}The result has no status key. This mode returns the parsed xAI response on its own rather than the wrapper most integrations put around it, so index response and citations directly. All four keys are always present. response is every output_text block the model produced, joined with newlines, and citations is the deduplicated list of url_citation URLs attached to those blocks. result_count is the number of unique citations rather than the number of posts the model read, so it is not a search result total. A search that turns up nothing returns an empty response string, an empty citations list and a result_count of zero rather than an error. usage is the xAI usage object passed through untouched, so its keys come from xAI rather than from this integration and are not verified here. Post text, authors, timestamps and engagement figures are not exposed, so a caller that needs structured posts has to fetch them from the citation URLs.
create_invoice()Create a new sales invoice.
result = qbash.integrations.xero.create_invoice(
contact_id="…",
)
contact_idstrrequiredContact ID.
line_itemslist | NonedefaultNoneLine Items (JSON array), e.g. [{"description":"...","quantity":1,"unit_amount":100,"account_code":"200"}].
due_datestr | NonedefaultNoneDue Date (YYYY-MM-DD).
invoice_numberstr | NonedefaultNoneInvoice Number.
referencestr | NonedefaultNoneReference.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.create_invoice/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention Xero wraps records in a PascalCase plural key even when there is only one, so the created invoice would be data["Invoices"][0] and its id data["Invoices"][0]["InvoiceID"], and nothing here lifts that id out to the top level. "status" is always "success" and "message" is a fixed sentence, so neither reports anything Xero said. line_items takes a list of dicts or a JSON array string, and any other value, including a string that does not parse, is sent as an empty LineItems array rather than raising.
update_invoice()Update an existing invoice.
result = qbash.integrations.xero.update_invoice(
invoice_id="…",
)
invoice_idstrrequiredInvoice ID.
statusstr | NonedefaultNoneNew status (DRAFT / SUBMITTED / AUTHORISED / VOIDED / PAID).
due_datestr | NonedefaultNoneDue Date (YYYY-MM-DD).
{"status": "success", "data": dict, "message": str}Only status and due_date are sent. When neither is given the request body is empty, so the call still posts and still comes back "success" with nothing changed. "data" is Xero's own response body, forwarded unchanged by Client.update_invoice/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the updated invoice sits at data["Invoices"][0]. The envelope's "status" is this integration's own word for the HTTP result and never the invoice state, which by the same convention is data["Invoices"][0]["Status"].
get_invoice()Get a single invoice by ID.
result = qbash.integrations.xero.get_invoice(
invoice_id="…",
)
invoice_idstrrequiredInvoice ID.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.get_invoice/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention Xero answers a single invoice lookup with a one element list, so the record would be at data["Invoices"][0] rather than on the top level. An unknown id arrives as an error rather than as an empty list, which is the service's own 404 branch rather than vendor convention.
list_invoices()List one page of sales invoices with optional filters.
result = qbash.integrations.xero.list_invoices(
status="…",
contact_id="…",
)
statusstr | NonedefaultNoneFilter by status (DRAFT / SUBMITTED / AUTHORISED / PAID / VOIDED).
contact_idstr | NonedefaultNoneFilter by Contact ID.
pagestr | NonedefaultNonePage number for pagination.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.list_invoices/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the page of invoices sits under data["Invoices"]. The filter always pins Type=="ACCREC", so only sales invoices come back and bills are reachable only through list_bills. One request is made per call, so this is a single page, and no cursor or total is read out of the body here. Pass the next number in "page" to walk forward and stop when the returned list is empty.
void_invoice()Void an existing invoice.
result = qbash.integrations.xero.void_invoice(
invoice_id="…",
)
invoice_idstrrequiredInvoice ID.
{"status": "success", "data": dict, "message": str}Posts Status VOIDED for the invoice. "data" is Xero's own response body, forwarded unchanged by Client.void_invoice/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. The envelope's "status" is the literal this integration writes for a successful HTTP call and never the invoice state, which by Xero's convention sits at data["Invoices"][0]["Status"]. A rejection from Xero, for an invoice that cannot be voided, arrives as an error instead of this envelope.
send_invoice()Email an invoice to the contact.
result = qbash.integrations.xero.send_invoice(
invoice_id="…",
)
invoice_idstrrequiredInvoice ID.
{"status": "success", "data": dict, "message": str}Posts an empty body to Xero's invoice email endpoint. The service accepts only 200 and 201 and then parses the reply as JSON, so a no content or non JSON reply surfaces as an error even when the email was sent. On the success path "data" is Xero's own response body, forwarded unchanged by Client.send_invoice/3, and nothing in this integration reads a key inside it. No key path is named here because the shape is unverified against this codebase and against observed traffic, so treat data as opaque, settle it with one try_call against a real tenant, and confirm delivery in Xero rather than indexing into it.
create_bill()Create a new bill (accounts payable).
result = qbash.integrations.xero.create_bill(
contact_id="…",
)
contact_idstrrequiredContact ID.
line_itemslist | NonedefaultNoneLine Items (JSON array).
due_datestr | NonedefaultNoneDue Date (YYYY-MM-DD).
statusstr | NonedefaultNoneStatus (DRAFT / SUBMITTED / AUTHORISED).
{"status": "success", "data": dict, "message": str}A bill is an invoice with Type ACCPAY, posted to the same /Invoices path as a sales invoice. "data" is Xero's own response body, forwarded unchanged by Client.create_bill/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the created bill sits at data["Invoices"][0], under a plural "Invoices" key rather than any "Bills" key. The status argument is accepted but never sent to Xero, so it has no effect and the bill status has to be changed afterwards with update_invoice. line_items behaves as it does for create_invoice.
list_bills()List one page of bills (accounts payable).
result = qbash.integrations.xero.list_bills(
page="…",
)
pagestr | NonedefaultNonePage number for pagination.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.list_bills/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the page of bills sits under data["Invoices"] rather than under any "Bills" key, because Xero serves both from one endpoint and this mode only adds a Type=="ACCPAY" filter. One request is made per call, so this is a single page and "page" has to be stepped by the caller until the returned list is empty.
create_contact()Create a new contact.
result = qbash.integrations.xero.create_contact(
name="…",
)
namestrrequiredContact name.
email_addressstr | NonedefaultNoneEmail Address.
phonestr | NonedefaultNonePhone number.
is_customerstr | NonedefaultNoneIs Customer (true/false).
is_supplierstr | NonedefaultNoneIs Supplier (true/false).
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.create_contact/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the created contact sits at data["Contacts"][0] and its id at data["Contacts"][0]["ContactID"]. is_customer and is_supplier are sent only when they are booleans or the strings "true" or "false", and any other value is dropped without an error. phone is sent as a single DEFAULT phone entry.
update_contact()Update an existing contact.
result = qbash.integrations.xero.update_contact(
contact_id="…",
)
contact_idstrrequiredContact ID.
namestr | NonedefaultNoneNew name.
email_addressstr | NonedefaultNoneEmail Address.
{"status": "success", "data": dict, "message": str}Only name and email_address are sent. When neither is given the request body is empty, so the call still reports success with nothing changed. "data" is Xero's own response body, forwarded unchanged by Client.update_contact/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the contact sits at data["Contacts"][0].
get_contact()Get a single contact by ID.
result = qbash.integrations.xero.get_contact(
contact_id="…",
)
contact_idstrrequiredContact ID.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.get_contact/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention a single contact lookup comes back as a one element list, so the record would be at data["Contacts"][0] rather than on the top level. An unknown id is turned into an error by the service rather than an empty list.
list_contacts()List one page of contacts with an optional filter.
result = qbash.integrations.xero.list_contacts(
where="…",
page="…",
)
wherestr | NonedefaultNoneWhere (filter) expression (e.g. IsCustomer==true).
pagestr | NonedefaultNonePage number for pagination.
page_sizeint | NonedefaultNoneContacts per page (1-1000). Xero uses 100 when omitted.
search_termstr | NonedefaultNoneCase insensitive search on name, contact number, company number and email address.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.list_contacts/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the page of contacts sits under data["Contacts"]. The where value is passed to Xero verbatim, so a malformed filter expression comes back as an error from Xero. page_size is sent as pageSize and search_term as searchTerm, both unchecked. Xero documents pageSize as 1 to 1000 with a default of 100, and searchTerm as a case insensitive match on Name, FirstName, LastName, ContactNumber, CompanyNumber and EmailAddress. One request is made per call, so this is a single page and "page" has to be stepped until the returned list is empty.
create_payment()Record a payment against an invoice.
result = qbash.integrations.xero.create_payment(
invoice_id="…",
account_id="…",
amount="…",
)
invoice_idstrrequiredInvoice ID.
account_idstrrequiredAccount ID (bank account to pay from).
amountstrrequiredPayment amount.
datestr | NonedefaultNonePayment date (YYYY-MM-DD, defaults to today).
referencestr | NonedefaultNoneReference.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.create_payment/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the payment sits at data["Payments"][0]. amount is sent exactly as it is given, so a string stays a string and no rounding or currency check happens here. When date is omitted nothing is sent for it and Xero dates the payment itself.
list_payments()List one page of payments.
result = qbash.integrations.xero.list_payments(
status="…",
page="…",
)
statusstr | NonedefaultNoneFilter by status (AUTHORISED / DELETED).
pagestr | NonedefaultNonePage number for pagination.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.list_payments/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the page of payments sits under data["Payments"]. One request is made per call, so this is a single page and "page" has to be stepped until the returned list is empty.
create_credit_note()Create a credit note.
result = qbash.integrations.xero.create_credit_note(
contact_id="…",
)
contact_idstrrequiredContact ID.
line_itemslist | NonedefaultNoneLine Items (JSON array).
typestr | NonedefaultNoneCredit note type (ACCRECCREDIT or ACCPAYCREDIT).
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.create_credit_note/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the credit note sits at data["CreditNotes"][0]. type defaults to ACCRECCREDIT when it is not given, and line_items behaves as it does for create_invoice.
list_credit_notes()List credit notes, one page only.
result = qbash.integrations.xero.list_credit_notes(
status="…",
)
statusstr | NonedefaultNoneFilter by status.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.list_credit_notes/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the credit notes sit under data["CreditNotes"]. Only the status filter is sent. This mode exposes no page parameter, so the caller gets whichever single page Xero returns for the request and later pages cannot be reached from here. On a large ledger narrow the set with status rather than expecting every credit note.
create_purchase_order()Create a purchase order.
result = qbash.integrations.xero.create_purchase_order(
contact_id="…",
)
contact_idstrrequiredContact ID.
line_itemslist | NonedefaultNoneLine Items (JSON array).
delivery_datestr | NonedefaultNoneDelivery Date (YYYY-MM-DD).
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.create_purchase_order/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the order sits at data["PurchaseOrders"][0]. No status is sent, so the order is created with whatever Xero defaults to, and line_items behaves as it does for create_invoice.
get_purchase_order()Get a purchase order by ID.
result = qbash.integrations.xero.get_purchase_order(
purchase_order_id="…",
)
purchase_order_idstrrequiredPurchase Order ID.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.get_purchase_order/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention a single lookup comes back as a one element list, so the record would be at data["PurchaseOrders"][0] rather than on the top level. An unknown id is turned into an error by the service rather than an empty list.
create_item()Create a new item/product.
result = qbash.integrations.xero.create_item(
code="…",
name="…",
)
codestrrequiredItem code (unique identifier).
namestrrequiredItem name.
descriptionstr | NonedefaultNoneItem description.
sales_unit_pricestr | NonedefaultNoneSales Unit Price.
purchase_unit_pricestr | NonedefaultNonePurchase Unit Price.
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.create_item/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the created item sits at data["Items"][0]. sales_unit_price and purchase_unit_price are each sent as a unit price nested in SalesDetails and PurchaseDetails, and are left out entirely when blank. A code that already exists is rejected by Xero and arrives as an error.
list_items()List items, one page only.
result = qbash.integrations.xero.list_items()
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.list_items/2, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the items sit under data["Items"]. This mode sends no filter and exposes no page parameter, so it returns whichever single response Xero gives for GET /Items and nothing more can be asked for from here.
list_accounts()List the chart of accounts, one page only.
result = qbash.integrations.xero.list_accounts(
account_type="…",
)
account_typestr | NonedefaultNoneAccount Type filter (e.g. BANK, REVENUE, EXPENSE).
{"status": "success", "data": dict, "message": str}"data" is Xero's own response body, forwarded unchanged by Client.list_accounts/3, and nothing in this integration reads a key inside it. The key paths named below are Xero's documented convention, unverified against this codebase and against observed traffic, so confirm them with one try_call against a real tenant before indexing into them. By that convention the accounts sit under data["Accounts"]. account_type is turned into a Type filter before the call. This mode exposes no page parameter, so it returns whichever single response Xero gives for the request. Use it to find the AccountID that create_payment needs.
upload_video()Upload a video to YouTube and return its ID. Defaults to private; videos stay private until the Google project passes YouTube's API audit.
result = qbash.integrations.youtube.upload_video(
title="…",
)
titlestrrequiredVideo title, max 100 characters.
file_idfile | NonedefaultNoneThe video file: a file from qbash.inputs, another integration's file result, or its file_upload_id. Provide file_id or video_data. Max 5 GB, qbash's file limit. A long upload needs a run whose expected runtime is over 15 minutes. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
video_datastr | NonedefaultNoneBase64-encoded video bytes, as an alternative to file_id. Bounded by the 50 MB request cap, so prefer file_id.
descriptionstr | NonedefaultNoneVideo description, max 5000 characters.
tagslist | NonedefaultNoneKeyword tags. Accepts a list or a comma-separated string.
category_idstrdefault"22"YouTube category ID as a string. 22 is People & Blogs, 28 Science & Technology, 27 Education, 24 Entertainment.
privacy_statusstrdefault"private"private, public, or unlisted.
publish_atstr | NonedefaultNoneRFC 3339 timestamp to schedule publication. Requires privacy_status=private.
made_for_kidsbool | NonedefaultNoneDeclare the video as child-directed.
notify_subscribersbooldefaultTrueWhether to send a new-upload notification to subscribers.
content_typestr | NonedefaultNoneMIME type of the video. Inferred from the file when omitted.
{"video_id": str, "url": str, "title": str, "description": str, "channel_id": str, "channel_title": str, "published_at": str, "tags": list[str] | None, "category_id": str, "thumbnail_url": str | None, "privacy_status": str, "upload_status": str | None, "publish_at": str | None, "license": str | None, "embeddable": bool | None, "made_for_kids": bool | None, "failure_reason": str | None, "rejection_reason": str | None, "view_count": int | None, "like_count": int | None, "comment_count": int | None, "duration": str | None, "definition": str | None, "caption": str | None, "status": "success", "note": str}get_upload_status()Check whether an uploaded video finished processing. Upload is asynchronous, so poll this after upload_video.
result = qbash.integrations.youtube.get_upload_status(
video_id="…",
)
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
{"video_id": str, "url": str, "upload_status": str | None, "privacy_status": str | None, "failure_reason": str | None, "rejection_reason": str | None, "processing_status": str | None, "processing_progress": dict | None, "processing_failure_reason": str | None}set_thumbnail()Set a custom thumbnail for a video. Max 2 MB, JPEG or PNG.
result = qbash.integrations.youtube.set_thumbnail(
video_id="…",
)
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
file_idfile | NonedefaultNoneThe image file: a file from qbash.inputs, another integration's file result, or its file_upload_id. Provide file_id or image_data. A qbash file reference, from qbash.inputs, qbash.files.write or another integration's result, or its file_upload_id.
image_datastr | NonedefaultNoneBase64-encoded image bytes, as an alternative to file_id.
content_typestr | NonedefaultNoneimage/jpeg or image/png. Inferred from the file or the inline image when omitted.
{"video_id": str, "thumbnails": dict | None}publish_video()Make a private video public, or schedule it to go public at a future time.
result = qbash.integrations.youtube.publish_video(
video_id="…",
)
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
privacy_statusstrdefault"public"public, private, or unlisted. private with no publish_at cancels a pending schedule.
publish_atstr | NonedefaultNoneRFC 3339 timestamp to schedule publication (e.g. 2026-09-01T14:00:00Z). YouTube requires the video be private until then, so supplying this forces privacy_status to private.
{"video_id": str, "url": str, "title": str, "description": str, "channel_id": str, "channel_title": str, "published_at": str, "tags": list[str] | None, "category_id": str, "thumbnail_url": str | None, "privacy_status": str, "upload_status": str | None, "publish_at": str | None, "license": str | None, "embeddable": bool | None, "made_for_kids": bool | None, "failure_reason": str | None, "rejection_reason": str | None, "view_count": int | None, "like_count": int | None, "comment_count": int | None, "duration": str | None, "definition": str | None, "caption": str | None}list_videos()List the channel's uploaded videos, newest first. Reads the uploads playlist, so it costs no search quota and includes videos uploaded moments ago.
result = qbash.integrations.youtube.list_videos(
max_results=10,
page_token="…",
)
max_resultsintdefault25Number of videos to return, 1-50.
page_tokenstr | NonedefaultNonePagination cursor returned as next_page_token by a previous call.
channel_idstr | NonedefaultNoneList another channel's uploads. Omit for your own channel.
{"videos": list[{"playlist_item_id": str, "video_id": str, "url": str, "title": str, "description": str, "playlist_id": str, "channel_id": str | None, "channel_title": str | None, "position": int, "added_at": str, "published_at": str | None, "note": str | None, "thumbnail_url": str | None, "privacy_status": str | None}], "next_page_token": str | None, "total_results": int | None}get_video()Get one video's metadata, status and statistics.
result = qbash.integrations.youtube.get_video(
video_id="…",
)
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
{"video_id": str, "url": str, "title": str, "description": str, "channel_id": str, "channel_title": str, "published_at": str, "tags": list[str] | None, "category_id": str, "thumbnail_url": str | None, "privacy_status": str, "upload_status": str | None, "publish_at": str | None, "license": str | None, "embeddable": bool | None, "made_for_kids": bool | None, "failure_reason": str | None, "rejection_reason": str | None, "view_count": int | None, "like_count": int | None, "comment_count": int | None, "duration": str | None, "definition": str | None, "caption": str | None}update_video()Update a video's metadata. Only the fields you pass change; everything else is preserved.
result = qbash.integrations.youtube.update_video(
video_id="…",
)
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
titlestr | NonedefaultNoneNew title, max 100 characters.
descriptionstr | NonedefaultNoneNew description, max 5000 characters.
tagslist | NonedefaultNoneReplacement tag list. Accepts a list or a comma-separated string. Pass an empty list to clear.
category_idstr | NonedefaultNoneNew YouTube category ID as a string.
privacy_statusstr | NonedefaultNoneprivate, public, or unlisted.
publish_atstr | NonedefaultNoneRFC 3339 scheduled publication timestamp. YouTube only accepts it on a private video, so pass privacy_status=private with it when the video is public or unlisted.
licensestr | NonedefaultNoneyoutube or creativeCommon.
embeddablebool | NonedefaultNoneWhether the video can be embedded on other sites.
public_stats_viewablebool | NonedefaultNoneWhether view counts are publicly visible on the watch page.
made_for_kidsbool | NonedefaultNoneDeclare the video as child-directed.
contains_synthetic_mediabool | NonedefaultNoneDisclose that the video contains altered or synthetic content.
{"video_id": str, "url": str, "title": str, "description": str, "channel_id": str, "channel_title": str, "published_at": str, "tags": list[str] | None, "category_id": str, "thumbnail_url": str | None, "privacy_status": str, "upload_status": str | None, "publish_at": str | None, "license": str | None, "embeddable": bool | None, "made_for_kids": bool | None, "failure_reason": str | None, "rejection_reason": str | None, "view_count": int | None, "like_count": int | None, "comment_count": int | None, "duration": str | None, "definition": str | None, "caption": str | None}delete_video()Permanently delete a video from the channel. This cannot be undone.
result = qbash.integrations.youtube.delete_video(
video_id="…",
)
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
{"deleted": bool, "video_id": str}rate_video()Like, dislike, or clear your rating on a video.
result = qbash.integrations.youtube.rate_video(
video_id="…",
rating="…",
)
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
ratingstrrequiredlike, dislike, or none (which removes an existing rating).
{"video_id": str, "rating": str}search_videos()Search YouTube. Bills to a dedicated 100-calls-per-day bucket separate from the main quota pool.
result = qbash.integrations.youtube.search_videos(
query="search terms",
type="…",
)
querystr | NonedefaultNoneSearch terms. Supports - to exclude and | for OR.
typestrdefault"video"video, channel, or playlist.
channel_idstr | NonedefaultNoneRestrict results to one channel.
for_minebooldefaultFalseRestrict to your own videos. Forces type to video. Prefer list_videos, which costs no search quota.
orderstr | NonedefaultNonerelevance, date, rating, title, videoCount, or viewCount.
max_resultsintdefault25Number of results, 1-50.
page_tokenstr | NonedefaultNonePagination cursor returned as next_page_token by a previous call.
published_afterstr | NonedefaultNoneRFC 3339 timestamp lower bound (e.g. 2026-01-01T00:00:00Z).
published_beforestr | NonedefaultNoneRFC 3339 timestamp upper bound.
video_durationstr | NonedefaultNoneshort (<4 min), medium (4-20 min), long (>20 min), or any.
region_codestr | NonedefaultNoneTwo-letter ISO 3166-1 country code.
{"results": list[{"id": str, "result_type": str, "url": str, "video_id": str | None, "channel_id": str | None, "playlist_id": str | None, "title": str, "description": str, "channel_title": str, "published_at": str, "thumbnail_url": str | None}], "next_page_token": str | None, "total_results": int | None}list_playlists()List playlists owned by the channel.
result = qbash.integrations.youtube.list_playlists(
max_results=10,
page_token="…",
)
max_resultsintdefault25Number of playlists to return, 1-50.
page_tokenstr | NonedefaultNonePagination cursor returned as next_page_token by a previous call.
channel_idstr | NonedefaultNoneList another channel's playlists. Omit for your own.
{"playlists": list[{"playlist_id": str, "url": str, "title": str, "description": str, "channel_id": str, "channel_title": str, "published_at": str, "thumbnail_url": str | None, "privacy_status": str | None, "item_count": int | None}], "next_page_token": str | None, "total_results": int | None}create_playlist()Create a playlist and return its ID.
result = qbash.integrations.youtube.create_playlist(
title="…",
)
titlestrrequiredPlaylist title, max 150 characters.
descriptionstr | NonedefaultNonePlaylist description, max 5000 characters.
privacy_statusstrdefault"private"private, public, or unlisted.
{"playlist_id": str, "url": str, "title": str, "description": str, "channel_id": str, "channel_title": str, "published_at": str, "thumbnail_url": str | None, "privacy_status": str | None, "item_count": int | None}delete_playlist()Delete a playlist. The videos in it are not deleted.
result = qbash.integrations.youtube.delete_playlist(
playlist_id="…",
)
playlist_idstrrequiredYouTube playlist ID.
{"deleted": bool, "playlist_id": str}list_playlist_items()List the videos in a playlist.
result = qbash.integrations.youtube.list_playlist_items(
playlist_id="…",
)
playlist_idstrrequiredYouTube playlist ID.
max_resultsintdefault25Number of items to return, 1-50.
page_tokenstr | NonedefaultNonePagination cursor returned as next_page_token by a previous call.
{"videos": list[{"playlist_item_id": str, "video_id": str, "url": str, "title": str, "description": str, "playlist_id": str, "channel_id": str | None, "channel_title": str | None, "position": int, "added_at": str, "published_at": str | None, "note": str | None, "thumbnail_url": str | None, "privacy_status": str | None}], "next_page_token": str | None, "total_results": int | None}add_video_to_playlist()Add a video to a playlist.
result = qbash.integrations.youtube.add_video_to_playlist(
playlist_id="…",
video_id="…",
)
playlist_idstrrequiredYouTube playlist ID.
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
positionint | NonedefaultNoneZero-based insertion index. Appends to the end when omitted.
notestr | NonedefaultNonePrivate note on the playlist item, max 280 characters.
{"playlist_item_id": str, "video_id": str, "url": str, "title": str, "description": str, "playlist_id": str, "channel_id": str | None, "channel_title": str | None, "position": int, "added_at": str, "published_at": str | None, "note": str | None, "thumbnail_url": str | None, "privacy_status": str | None}remove_video_from_playlist()Remove a video from a playlist. Removes every occurrence if the video appears more than once.
result = qbash.integrations.youtube.remove_video_from_playlist(
playlist_id="…",
video_id="…",
)
playlist_idstrrequiredYouTube playlist ID.
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
{"removed": bool, "playlist_id": str, "video_id": str, "removed_count": int}list_video_comments()List comment threads on a single video, with their replies.
result = qbash.integrations.youtube.list_video_comments(
video_id="…",
)
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
max_resultsintdefault20Number of threads to return, 1-100.
page_tokenstr | NonedefaultNonePagination cursor returned as next_page_token by a previous call.
orderstrdefault"time"time (newest first) or relevance.
search_termsstr | NonedefaultNoneOnly return comments containing these terms.
text_formatstrdefault"plainText"plainText or html.
{"comments": list[{"thread_id": str, "total_reply_count": int, "comment_id": str, "text": str, "author_display_name": str, "author_channel_id": str | None, "author_profile_image_url": str | None, "like_count": int | None, "published_at": str, "updated_at": str | None, "parent_id": str | None, "video_id": str | None, "can_reply": bool | None, "replies": list[{"comment_id": str, "text": str, "author_display_name": str, "author_channel_id": str | None, "author_profile_image_url": str | None, "like_count": int | None, "published_at": str, "updated_at": str | None, "parent_id": str | None, "video_id": str | None, "can_reply": bool | None}]}], "next_page_token": str | None, "total_results": int | None}list_channel_comments()List comment threads across every video on a channel. Use list_video_comments for a single video.
result = qbash.integrations.youtube.list_channel_comments(
channel_id="…",
max_results=10,
)
channel_idstr | NonedefaultNoneYouTube channel ID. Resolves your own channel when omitted.
max_resultsintdefault20Number of threads to return, 1-100.
page_tokenstr | NonedefaultNonePagination cursor returned as next_page_token by a previous call.
orderstrdefault"time"time (newest first) or relevance.
search_termsstr | NonedefaultNoneOnly return comments containing these terms.
text_formatstrdefault"plainText"plainText or html.
{"comments": list[{"thread_id": str, "total_reply_count": int, "comment_id": str, "text": str, "author_display_name": str, "author_channel_id": str | None, "author_profile_image_url": str | None, "like_count": int | None, "published_at": str, "updated_at": str | None, "parent_id": str | None, "video_id": str | None, "can_reply": bool | None, "replies": list[{"comment_id": str, "text": str, "author_display_name": str, "author_channel_id": str | None, "author_profile_image_url": str | None, "like_count": int | None, "published_at": str, "updated_at": str | None, "parent_id": str | None, "video_id": str | None, "can_reply": bool | None}]}], "next_page_token": str | None, "total_results": int | None}reply_to_comment()Reply to a comment thread as the authenticated channel.
result = qbash.integrations.youtube.reply_to_comment(
parent_id="…",
text="…",
)
parent_idstrrequiredID of the top-level comment to reply to. Use the thread_id or comment_id from list_video_comments.
textstrrequiredReply body, max 10000 characters.
{"comment_id": str, "text": str, "author_display_name": str, "author_channel_id": str | None, "author_profile_image_url": str | None, "like_count": int | None, "published_at": str, "updated_at": str | None, "parent_id": str | None, "video_id": str | None, "can_reply": bool | None}get_channel()Get channel details: subscriber count, total views, video count and the uploads playlist ID.
result = qbash.integrations.youtube.get_channel(
channel_id="…",
)
channel_idstr | NonedefaultNoneYouTube channel ID. Returns your own channel when omitted.
{"channel_id": str, "title": str, "description": str, "custom_url": str | None, "published_at": str, "country": str | None, "thumbnail_url": str | None, "subscriber_count": int | None, "subscriber_count_hidden": bool | None, "video_count": int | None, "view_count": int | None, "uploads_playlist_id": str | None}get_channel_analytics()Run a channel-level analytics report over a date range. Revenue metrics (estimatedRevenue, cpm, adImpressions, ...) are unavailable: this integration does not request the monetary scope.
result = qbash.integrations.youtube.get_channel_analytics(
start_date="…",
end_date="…",
)
start_datestrrequiredFirst day of the reporting period, YYYY-MM-DD (e.g. 2026-07-01).
end_datestrrequiredLast day of the reporting period, YYYY-MM-DD. Inclusive.
metricsstrdefault"views,estimatedMinutesWatched,averageViewDuration,subscribersGained"Comma-separated metrics, e.g. views, estimatedMinutesWatched, averageViewDuration, averageViewPercentage, subscribersGained, subscribersLost, likes, comments, shares.
dimensionsstr | NonedefaultNoneComma-separated grouping, e.g. day, month, country, ageGroup,gender, deviceType, insightTrafficSourceType.
filtersstr | NonedefaultNoneSemicolon-separated filters, e.g. country==US;deviceType==MOBILE.
sortstr | NonedefaultNoneComma-separated sort columns. Prefix with - for descending.
max_resultsint | NonedefaultNoneMaximum number of rows to return.
channel_idstr | NonedefaultNoneReport on a specific owned channel. Defaults to the authenticated one.
{"rows": list[dict], "column_headers": list[{"name": str, "data_type": str, "column_type": str}], "row_count": int}get_video_analytics()Run an analytics report scoped to one video. Revenue metrics (estimatedRevenue, cpm, adImpressions, ...) are unavailable: this integration does not request the monetary scope. Impressions and thumbnail click-through rate are not available from this API at all.
result = qbash.integrations.youtube.get_video_analytics(
video_id="…",
start_date="…",
end_date="…",
)
video_idstrrequiredYouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).
start_datestrrequiredFirst day of the reporting period, YYYY-MM-DD (e.g. 2026-07-01).
end_datestrrequiredLast day of the reporting period, YYYY-MM-DD. Inclusive.
metricsstrdefault"views,estimatedMinutesWatched,averageViewDuration,averageViewPercentage"Comma-separated metrics.
dimensionsstr | NonedefaultNoneComma-separated grouping, e.g. day, country, or elapsedVideoTimeRatio for retention curves.
filtersstr | NonedefaultNoneExtra semicolon-separated filters, merged with the video filter.
sortstr | NonedefaultNoneComma-separated sort columns. Prefix with - for descending.
max_resultsint | NonedefaultNoneMaximum number of rows to return.
{"rows": list[dict], "column_headers": list[{"name": str, "data_type": str, "column_type": str}], "row_count": int}get_top_videos()List the channel's best-performing videos for a period. Revenue metrics (estimatedRevenue, cpm, adImpressions, ...) are unavailable: this integration does not request the monetary scope.
result = qbash.integrations.youtube.get_top_videos(
start_date="…",
end_date="…",
)
start_datestrrequiredFirst day of the reporting period, YYYY-MM-DD (e.g. 2026-07-01).
end_datestrrequiredLast day of the reporting period, YYYY-MM-DD. Inclusive.
metricsstrdefault"views,estimatedMinutesWatched,averageViewDuration"Comma-separated metrics to report per video.
sortstrdefault"-views"Sort column, prefixed with - for descending.
max_resultsintdefault10Number of videos to return. YouTube caps this report at 200.
{"rows": list[dict], "column_headers": list[{"name": str, "data_type": str, "column_type": str}], "row_count": int}get_traffic_sources()Break views down by how viewers found the video: YouTube search, suggested videos, browse features, external sites and more.
result = qbash.integrations.youtube.get_traffic_sources(
start_date="…",
end_date="…",
)
start_datestrrequiredFirst day of the reporting period, YYYY-MM-DD (e.g. 2026-07-01).
end_datestrrequiredLast day of the reporting period, YYYY-MM-DD. Inclusive.
video_idstr | NonedefaultNoneScope to a single video. Omit for the whole channel.
metricsstrdefault"views,estimatedMinutesWatched"Comma-separated metrics.
max_resultsint | NonedefaultNoneMaximum number of rows to return.
{"rows": list[dict], "column_headers": list[{"name": str, "data_type": str, "column_type": str}], "row_count": int}get_current_user()Get the Zendesk user this connection acts as, with their role.
result = qbash.integrations.zendesk.get_current_user()
{"status": "success", "user": dict, "is_admin": bool, "subdomain": str}Internal notes and ticket changes made through this connector show in Zendesk as made by this user. The queues and brands in get_routing_options need an admin. Everything else works for agents, within the groups and tickets they can see.
get_ticket()Get one ticket with its status, routing fields, tags and custom field values.
result = qbash.integrations.zendesk.get_ticket(
ticket_id=0,
)
ticket_idintrequiredZendesk ticket id.
{"status": "success", "ticket": dict}Tickets have id, url (the agent workspace link), subject, status, custom_status_id, priority, type, requester_id, assignee_id, group_id, organization_id, brand_id, ticket_form_id, tags, channel, external_id, created_at, updated_at and custom_fields, a list of {id, title, value}. Zendesk's description field is left out because it repeats the first comment, which can be an internal note. Read the conversation with list_ticket_comments. Keys whose value is null are left out, so read optional fields with .get().
search_tickets()Search tickets with Zendesk search syntax, such as status:open group:Billing or custom_field_360001:QB-42.
result = qbash.integrations.zendesk.search_tickets(
query="search terms",
)
querystrrequiredZendesk search query, for example status<solved priority:urgent or requester:jo@example.com.
sort_bystr | NonedefaultNonecreated_at, updated_at, priority, status or ticket_type. Defaults to relevance.
sort_orderstr | NonedefaultNoneasc or desc.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "tickets": list[dict], "count": int, "total": int, "next_cursor": str | None}type:ticket is added to the query. To find a ticket by an engineering reference stored in a custom field, search custom_field_<field id>:<value>, with the field id from list_custom_fields. Zendesk's search index lags new changes by a few minutes, and Zendesk returns at most 1,000 results for a query, so narrow the query instead of paging past that. Tickets have id, url (the agent workspace link), subject, status, custom_status_id, priority, type, requester_id, assignee_id, group_id, organization_id, brand_id, ticket_form_id, tags, channel, external_id, created_at, updated_at and custom_fields, a list of {id, title, value}. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_ticket_comments()List a ticket's conversation, public replies and internal notes, each labeled with its visibility.
result = qbash.integrations.zendesk.list_ticket_comments(
ticket_id=0,
)
ticket_idintrequiredZendesk ticket id.
visibilitystrdefault"all"all, public or internal.
limitintdefault100Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "ticket_id": int, "comments": list[dict], "count": int, "next_cursor": str | None}Comments come oldest first. Each has id, public, visibility (public or internal), author_id, author_name, author_role, body (plain text), html_body, channel, created_at and attachments. Each attachment has id, file_name, content_type, size, inline, public, visibility and malware_scan_result, and shares its comment's visibility. visibility="public" returns only what the requester can see and visibility="internal" only internal notes. Keep internal notes and their attachments out of anything sent to the requester. Side conversations are separate, see list_side_conversations. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_side_conversations()List a ticket's side conversations, the internal threads with other teams or outside contacts.
result = qbash.integrations.zendesk.list_side_conversations(
ticket_id=0,
)
ticket_idintrequiredZendesk ticket id.
include_eventsbooldefaultFalseAlso read each side conversation's messages.
{"status": "success", "ticket_id": int, "side_conversations": list[dict], "count": int}The requester never sees side conversations, so each has visibility internal. Each has id, subject, preview_text, state, participants, created_at and updated_at. With include_events=True each also has events, its messages with subject, body, from, to and attachments, at one extra request per side conversation. Side conversations need Zendesk's Collaboration add-on or a plan that includes it. Without it Zendesk refuses the call.
list_ticket_audits()List a ticket's change history: who changed which fields, through which channel, and whether qbash made the change.
result = qbash.integrations.zendesk.list_ticket_audits(
ticket_id=0,
)
ticket_idintrequiredZendesk ticket id.
limitintdefault100Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "ticket_id": int, "audits": list[dict], "count": int, "next_cursor": str | None}Audits come oldest first. Each has id, created_at, author_id, channel, made_by_qbash and events. Events are Create and Change (field_name, value, previous_value), Comment (comment_id, public, visibility, body, attachments), CommentPrivacyChange, and other types with the rule that caused them. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
download_attachment()Download a ticket attachment into qbash file storage, to pass to a prompt or another connector.
result = qbash.integrations.zendesk.download_attachment(
attachment_id=0,
)
attachment_idintrequiredAttachment id from list_ticket_comments.
{"status": "success", "attachment": dict, "file": dict}file is a qbash file reference that prompts and file-accepting connector modes take. attachment has id, file_name, content_type, size, inline and malware_scan_result. Zendesk refuses deleted attachments, and qbash refuses attachments Zendesk flagged as malware and files over 50 MB. An attachment's visibility comes from its comment, so check public in list_ticket_comments before passing a file to the requester.
get_user()Get one Zendesk user: an end user, agent or admin.
result = qbash.integrations.zendesk.get_user(
user_id=0,
)
user_idintrequiredZendesk user id, such as a ticket's requester_id.
{"status": "success", "user": dict}The user has id, name, email, role, organization_id, phone, time_zone, locale, tags, external_id, details, notes, user_fields, active, suspended, created_at, updated_at and last_login_at. details and notes are visible to agents only, so keep them out of anything sent to the requester. Keys whose value is null are left out.
search_users()Search users by name, email, phone or external id.
result = qbash.integrations.zendesk.search_users(
query="search terms",
)
querystrrequiredName, email, phone number or external id to look for.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "users": list[dict], "count": int, "next_cursor": str | None}Matches partial names and emails. Users have the same shape as get_user. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
get_organization()Get one organization with its domains, tags, notes and custom fields.
result = qbash.integrations.zendesk.get_organization(
organization_id=0,
)
organization_idintrequiredZendesk organization id, such as a ticket's organization_id.
{"status": "success", "organization": dict}The organization has id, name, domain_names, external_id, group_id, tags, details, notes, organization_fields, shared_tickets, shared_comments, created_at and updated_at. details and notes are visible to agents only, so keep them out of anything sent to the requester.
search_organizations()Search organizations with Zendesk search syntax, such as a name or a domain.
result = qbash.integrations.zendesk.search_organizations(
query="search terms",
)
querystrrequiredZendesk search query, for example qbash or tags:enterprise.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "organizations": list[dict], "count": int, "total": int, "next_cursor": str | None}type:organization is added to the query. The search index lags new changes by a few minutes and returns at most 1,000 results. Organizations have the same shape as get_organization. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_custom_fields()List ticket, user or organization fields with their ids, types and dropdown options.
result = qbash.integrations.zendesk.list_custom_fields(
object_type="…",
)
object_typestrdefault"ticket"ticket, user or organization.
{"status": "success", "object_type": str, "fields": list[dict], "count": int}Each field has id, title, type, active, description, required, and options (name and value) for dropdowns. User and organization fields also have key. Ticket fields include Zendesk's system fields (system=True), which update_ticket sets through its own parameters. Use a ticket field's id in update_ticket's custom_fields, and in search_tickets as custom_field_<id>:<value>.
get_routing_options()Get the valid routing and status values: priorities, types, statuses, custom statuses, groups, forms, brands, skills and queues.
result = qbash.integrations.zendesk.get_routing_options()
{"status": "success", "priorities": list[str], "types": list[str], "statuses": list[str], "custom_statuses": list[dict], "groups": list[dict], "ticket_forms": list[dict], "brands": list[dict], "skills": list[dict], "queues": list[dict], "unavailable": dict}groups are the ones the connected user can assign tickets to. skills lists each skill with its values, whose ids update_ticket takes as skill_value_ids. queues are omnichannel routing queues with the conditions Zendesk uses to place tickets in them. A ticket can't be put in a queue directly. A section the account's plan or the user's role doesn't allow comes back empty and is named in unavailable with Zendesk's reason: brands and queues need an admin, and skills need skills-based routing. This reads several Zendesk endpoints, so read it once per run rather than once per ticket.
list_assignable_agents()List the agents who can be assigned tickets in a group.
result = qbash.integrations.zendesk.list_assignable_agents(
group_id=0,
)
group_idintrequiredGroup id from get_routing_options.
{"status": "success", "group_id": int, "agents": list[dict], "count": int}Each agent has user_id, name, email, role and default_group. update_ticket only assigns a ticket to an agent in the ticket's group.
list_views()List the active views the connected user can see.
result = qbash.integrations.zendesk.list_views(
limit=10,
cursor="…",
)
limitintdefault100Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "views": list[dict], "count": int, "next_cursor": str | None}Each view has id, title, description, active, position and updated_at. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_view_tickets()List the tickets in a view, in the view's order.
result = qbash.integrations.zendesk.list_view_tickets(
view_id=0,
)
view_idintrequiredView id from list_views.
limitintdefault25Items per page, from 1 through 100.
cursorstr | NonedefaultNonenext_cursor from the previous page. Omit for the first page.
{"status": "success", "view_id": int, "tickets": list[dict], "count": int, "next_cursor": str | None}Tickets have id, url (the agent workspace link), subject, status, custom_status_id, priority, type, requester_id, assignee_id, group_id, organization_id, brand_id, ticket_form_id, tags, channel, external_id, created_at, updated_at and custom_fields, a list of {id, title, value}. Keys whose value is null are left out, so read optional fields with .get(). Paging: pass next_cursor back as cursor until it is None.
list_ticket_changes()List tickets changed since a time or a previous call's cursor, oldest first, for incremental intake.
result = qbash.integrations.zendesk.list_ticket_changes(
since="…",
cursor="…",
)
sincestr | NonedefaultNoneISO 8601 time to start from on the first call, such as 2026-10-14T09:00:00Z. Defaults to 24 hours ago.
cursorstr | NonedefaultNonenext_cursor from the previous call. Takes precedence over since.
limitintdefault50Tickets per call, from 1 through 100.
exclude_own_changesbooldefaultTrueLeave out tickets whose only new changes this connector made.
{"status": "success", "tickets": list[dict], "count": int, "skipped_own_changes": int, "next_cursor": str, "has_more": bool, "truncated": bool}On the first call, pass since (an ISO 8601 time, at most 30 days back), or leave it out to start 24 hours back. Store next_cursor, for example with qbash.cache, and pass it as cursor next time. next_cursor is always set, and while has_more is True there are more changes to read now. Tickets changed in the last 15 seconds wait for the next call. With exclude_own_changes (the default), tickets whose only changes since the cursor came from this connector's update_ticket or add_internal_note, as the connected user, are left out and counted in skipped_own_changes, so a task that updates tickets doesn't trigger itself. That check costs one request per changed ticket. truncated is True when more than 2,000 tickets changed since the cursor and the oldest of them were skipped. Tickets have id, url (the agent workspace link), subject, status, custom_status_id, priority, type, requester_id, assignee_id, group_id, organization_id, brand_id, ticket_form_id, tags, channel, external_id, created_at, updated_at and custom_fields, a list of {id, title, value}.
add_internal_note()Add an internal note to a ticket. Agents see it, and the requester and CCs are not notified.
result = qbash.integrations.zendesk.add_internal_note(
ticket_id=0,
)
ticket_idintrequiredZendesk ticket id.
bodystr | NonedefaultNoneNote text. Pass this or html_body.
html_bodystr | NonedefaultNoneNote as HTML. Pass this or body.
dedupe_keystr | NonedefaultNoneSkips the write when a change with the same key is among the ticket's latest 100 audits, so a retried run doesn't repeat it. Up to 200 characters.
{"status": "success", "skipped": bool, "ticket_id": int, "comment_id": int | None, "audit_id": int | None, "updated_at": str | None, "reason": str | None}The note is always private. Pass the text as body (plain text) or html_body. With dedupe_key, the note is skipped (skipped=True) when a change with the same key is among the ticket's latest 100 audits, for example dedupe_key=f"escalation:{issue_key}". The note shows in Zendesk as written by the connected user. Zendesk allows 30 updates to one ticket per user every 10 minutes.
update_ticket()Change a ticket's status, priority, type, group, assignee, custom fields, tags or skills, with an optional internal note.
result = qbash.integrations.zendesk.update_ticket(
ticket_id=0,
)
ticket_idintrequiredZendesk ticket id.
statusstr | NonedefaultNonenew, open, pending, hold or solved.
custom_status_idint | NonedefaultNoneCustom status id from get_routing_options.
prioritystr | NonedefaultNoneurgent, high, normal or low.
typestr | NonedefaultNoneproblem, incident, question or task.
group_idint | NonedefaultNoneGroup id from get_routing_options.
assignee_idint | NonedefaultNoneAgent id from list_assignable_agents for the ticket's group.
custom_fieldsdict | NonedefaultNoneTicket field id (or exact title) to new value, such as {"360001": "QB-42"}.
add_tagslist[str] | NonedefaultNoneTags to add.
remove_tagslist[str] | NonedefaultNoneTags to remove.
skill_value_idslist[int] | NonedefaultNoneSkill value ids from get_routing_options. Replaces the ticket's skills.
internal_notestr | NonedefaultNoneInternal note to add with the change.
expected_updated_atstr | NonedefaultNoneThe ticket's updated_at from your earlier read. The update is refused if the ticket changed since.
dedupe_keystr | NonedefaultNoneSkips the write when a change with the same key is among the ticket's latest 100 audits, so a retried run doesn't repeat it. Up to 200 characters.
{"status": "success", "changed": bool, "changed_fields": list[str], "note_added": bool, "skipped": bool, "reason": str | None, "ticket": dict, "audit_id": int | None}Only the parameters you pass change. Each value is checked first against the account's current settings: statuses and custom statuses, the groups the connected user can assign to, the agents in the target group, dropdown options and skill values. When nothing would change, nothing is sent and changed is False. custom_fields maps a field id, or its exact title, to a value. Dropdowns take an option's value or name, and None clears a field. Store an engineering issue key or link in a custom field this way, then find the ticket again with search_tickets("custom_field_<id>:<key>"). add_tags and remove_tags leave the other tags alone. skill_value_ids replaces the ticket's skills. The write uses Zendesk's collision check: when the ticket changed after this call read it, Zendesk returns a 409 conflict. Pass expected_updated_at, the updated_at from your own earlier read, to also refuse when the ticket changed since then. dedupe_key works as in add_internal_note. Zendesk allows 30 updates to one ticket per user every 10 minutes.
search_meetings()Search past Zoom meetings by date range and optional topic keyword.
result = qbash.integrations.zoom.search_meetings(
from_date="…",
to_date="…",
)
from_datestrrequiredStart date (YYYY-MM-DD)
to_datestrrequiredEnd date (YYYY-MM-DD)
topicstrFilter by meeting topic keyword
limitintdefault30Max results to return
{"meetings": list[dict]}Meetings are Zoom objects passed through unchanged, and the list is the only key in the result, with no count and no page token. Pages of 300 are followed internally until limit meetings are collected or Zoom runs out, so limit is the only control over how many come back and the default stops at 30. When topic is set, the filter is applied here after fetching and reads at most 3000 meetings from the date range before limit is applied, so a match past that point in a wide range is missed. The endpoint lists previous meetings hosted by the user the credential belongs to, so meetings hosted by anyone else are not included. Use search_account_meetings for those.
get_meeting()Get details for a specific past Zoom meeting (topic, duration, host, time).
result = qbash.integrations.zoom.get_meeting(
meeting_id="…",
)
meeting_idstrrequiredZoom meeting ID or UUID
dictThe Zoom response body is returned unchanged with no wrapper key added here, so read it with .get() and check the Zoom API documentation for its fields. Two endpoints can answer. The past meeting record is tried first, and when Zoom has no past instance for this id the scheduled meeting record is returned from a second endpoint instead. Those two records carry different fields, so check for the keys you need rather than assuming which of the two you received.
list_participants()List participants of a past Zoom meeting with join/leave times.
result = qbash.integrations.zoom.list_participants(
meeting_id="…",
)
meeting_idstrrequiredZoom meeting ID or UUID
limitintdefault100Max participants to return
{"participants": list[dict]}Participants are Zoom objects passed through unchanged, and the list is the only key in the result, with no count and no page token. Pages are followed internally until limit participants are collected, so limit is the only control over how many come back and the default stops at 100. The endpoint reads the participant list Zoom keeps for a meeting that has already finished.
get_transcript()Fetch the transcript of a recorded Zoom meeting. Returns speaker-labeled text by default, or raw VTT with format='vtt'.
result = qbash.integrations.zoom.get_transcript(
meeting_id="…",
)
meeting_idstrrequiredZoom meeting ID or UUID
formatstrdefaulttextOutput format: 'text' (speaker-labeled) or 'vtt' (raw timestamps)
{"transcript": str} or {"vtt": str} when format is "vtt"The result has one key and its name depends on format. The default text format returns transcript, which is the VTT file parsed here into lines of the form Speaker: text joined by newlines, with consecutive lines from one speaker merged into one. A cue with no speaker prefix contributes its bare text, so not every line carries a label, and a file with no parseable cues gives an empty string rather than an error. Setting format to vtt returns vtt instead, holding the raw WebVTT file with its timestamps and cue numbers. A recording with no transcript file returns an error rather than an empty result, which is what a meeting recorded without transcription gives.
get_meeting_chat()Fetch the in-meeting chat log from a recorded Zoom meeting.
result = qbash.integrations.zoom.get_meeting_chat(
meeting_id="…",
)
meeting_idstrrequiredZoom meeting ID or UUID
{"chat": str}chat is the chat log file Zoom stored with the recording, returned as text with no parsing into messages here. A recording with no chat file returns an error rather than an empty string, so a meeting where nobody used the chat is an error case rather than an empty result.
get_meeting_polls()Get poll questions and participant responses from a past Zoom meeting.
result = qbash.integrations.zoom.get_meeting_polls(
meeting_id="…",
)
meeting_idstrrequiredZoom meeting ID or UUID
dictThe Zoom poll results body is returned unchanged with no wrapper key added here, and nothing in this integration reads or renames its fields. Read it with .get() and check the Zoom API documentation for the shape of the past meeting polls response.
get_meeting_qa()Get Q&A questions and answers from a past Zoom meeting or webinar.
result = qbash.integrations.zoom.get_meeting_qa(
meeting_id="…",
)
meeting_idstrrequiredZoom meeting ID or UUID
dictThe Zoom Q and A body is returned unchanged with no wrapper key added here, and nothing in this integration reads or renames its fields. Read it with .get() and check the Zoom API documentation for the shape of the past meeting Q and A response.
get_meeting_summary()Get the Zoom AI-generated meeting summary (requires AI Companion enabled in Zoom settings).
result = qbash.integrations.zoom.get_meeting_summary(
meeting_id="…",
)
meeting_idstrrequiredZoom meeting ID or UUID
dictThe Zoom AI Companion summary body is returned unchanged with no wrapper key added here, so read it with .get() and check the Zoom API documentation for its fields. A meeting with no summary returns an error naming AI Companion, so there is no empty summary shape to handle.
list_users()List users in the Zoom account. Requires an Admin or Server-to-Server credential.
result = qbash.integrations.zoom.list_users(
status="…",
limit=10,
)
statusstrdefaultactiveFilter by status: active, inactive, or pending
limitintdefault100Max users to return. Ignored when page_size or next_page_token is set.
page_sizeint | NonedefaultNoneUsers per page, from 1 to 300. When set, the call reads one page and adds next_page_token while there are more.
next_page_tokenstr | NonedefaultNoneThe next_page_token from the previous page. When set, the call reads that page, 300 users unless page_size is set.
{"users": list[dict]} or {"users": list[dict], "next_page_token": str}Users are Zoom objects passed through unchanged under users, with no count. Without page_size or next_page_token, pages of 300 are followed internally until limit users are collected and no page token comes back, so the default stops at 100 and an account with more users than limit is cut off with nothing in the result to say so. With page_size or next_page_token set, one request is made for that page and limit is ignored. next_page_token is added only while Zoom reports another page: pass it back as next_page_token, with the same status, to read the next page, and stop when the key is absent. status filters to one of active, inactive or pending, and active is used when it is omitted, so deactivated and pending users are absent unless you ask for them.
search_account_meetings()Search past meetings across all users in the Zoom account via the Dashboard API. Requires Business+ plan and an Admin or S2S credential.
result = qbash.integrations.zoom.search_account_meetings(
from_date="…",
to_date="…",
)
from_datestrrequiredStart date (YYYY-MM-DD)
to_datestrrequiredEnd date (YYYY-MM-DD)
typestrdefaultpastMeeting type: past, pastOne, or live
limitintdefault100Max meetings to return
{"meetings": list[dict]}Meetings come from the Zoom Dashboard API and are passed through unchanged, with the list the only key in the result and no count and no page token. Pages of 300 are followed internally until limit meetings are collected, so limit is the only control over how many come back and the default stops at 100. The topic filter that search_meetings applies is not available here, so filter the list in your own code. Dashboard records differ in shape from the meeting records search_meetings returns, so check for the keys you need before reading them.
search_user_meetings()Search past meetings for a specific user in the Zoom account. Requires an Admin or S2S credential.
result = qbash.integrations.zoom.search_user_meetings(
user_id="…",
from_date="…",
to_date="…",
)
user_idstrrequiredZoom user ID or email address
from_datestrrequiredStart date (YYYY-MM-DD)
to_datestrrequiredEnd date (YYYY-MM-DD)
topicstrFilter by meeting topic keyword
limitintdefault30Max meetings to return
{"meetings": list[dict]}Meetings are Zoom objects passed through unchanged, and the list is the only key in the result, with no count and no page token. Pages of 300 are followed internally until limit meetings are collected, so limit is the only control over how many come back and the default stops at 30. When topic is set, the filter is applied here after fetching and reads at most 3000 meetings from the date range before limit is applied, so a match past that point in a wide range is missed. user_id takes a Zoom user ID or the email address on the account.
search_contacts()Search for B2B contacts by name, title, company, or location.
result = qbash.integrations.zoominfo.search_contacts(
firstName="…",
lastName="…",
)
firstNamestr | NonedefaultNoneFirst name filter.
lastNamestr | NonedefaultNoneLast name filter.
emailstr | NonedefaultNoneEmail address filter.
jobTitlestr | NonedefaultNoneJob title filter.
companyNamestr | NonedefaultNoneCompany name filter.
companyIdstr | NonedefaultNoneZoomInfo company ID to restrict the search to.
departmentstr | NonedefaultNoneDepartment filter.
managementLevelstr | NonedefaultNoneManagement level filter (e.g. 'Director', 'VP', 'C-Level').
countrystr | NonedefaultNoneCountry filter.
statestr | NonedefaultNoneState/region filter.
pageint | Nonedefault1Page number for paginated results.
pageSizeint | Nonedefault25Results per page, clamped to 1-100.
{"status": "success", "contacts": list[dict], "total_results": int, "page": int}One page per call. "page" and "pageSize" choose the slice, pageSize is clamped to 1 through 100 and defaults to 25, and walking the result set is the caller's own loop. The "page" in the result echoes the page that was asked for rather than anything ZoomInfo reported. "total_results" is ZoomInfo's maxResults when the response carries one and falls back to the length of this page when it does not, so a total_results equal to the number of contacts returned can mean either that this is the only page or that the count was missing. A response shape this code does not recognise comes back as an empty "contacts" list with total_results 0 and page 1 and no error, so an empty list is not proof that nothing matched. Each contact is projected here onto a fixed field list, id, first_name, last_name, full_name, job_title, management_level, department, company_name, company_id, city, state, country, has_email, has_phone, has_direct_phone, accuracy_score and last_updated, and any of those whose value is null is dropped from the record rather than set to None, so read every field with .get(). Search records carry the has_email and has_phone flags rather than the values, so use enrich_contact to get an address or a number.
search_companies()Search for B2B companies by name, industry, revenue, or location.
result = qbash.integrations.zoominfo.search_companies(
companyName="…",
website="…",
)
companyNamestr | NonedefaultNoneCompany name filter.
websitestr | NonedefaultNoneCompany website filter.
industrystr | NonedefaultNoneIndustry filter.
revenueMinstr | NonedefaultNoneMinimum annual revenue filter.
revenueMaxstr | NonedefaultNoneMaximum annual revenue filter.
employeeCountMinint | NonedefaultNoneMinimum employee count filter.
employeeCountMaxint | NonedefaultNoneMaximum employee count filter.
countrystr | NonedefaultNoneCountry filter.
statestr | NonedefaultNoneState/region filter.
pageint | Nonedefault1Page number for paginated results.
pageSizeint | Nonedefault25Results per page, clamped to 1-100.
{"status": "success", "companies": list[dict], "total_results": int, "page": int}One page per call, with the same paging behaviour as search_contacts. "page" echoes the page that was asked for, and "total_results" is ZoomInfo's maxResults when the response carries one and the length of this page when it does not. A response shape this code does not recognise comes back as an empty "companies" list with total_results 0 and page 1 and no error. Each company is projected here onto a fixed field list, id, name, website, industry, sub_industry, employee_count, revenue, revenue_range, city, state, country, zip_code, phone, founded_year and company_status, and any of those whose value is null is dropped from the record rather than set to None, so read every field with .get(). enrich_company returns a wider field list for a company already found here.
search_intent()Search for buyer intent signals by topic.
result = qbash.integrations.zoominfo.search_intent(
topics=[...],
)
topicslistrequiredList of intent topic strings to search for. A comma-separated string is also accepted.
companyNamestr | NonedefaultNoneRestrict signals to this company name.
companyIdstr | NonedefaultNoneRestrict signals to this ZoomInfo company ID.
countrystr | NonedefaultNoneCountry filter.
pageint | Nonedefault1Page number for paginated results.
pageSizeint | Nonedefault25Results per page, clamped to 1-100.
{"status": "success", "signals": list[dict], "total_results": int, "page": int}One page per call, with the same paging behaviour as the other searches. "page" echoes the page that was asked for, and "total_results" is ZoomInfo's maxResults when the response carries one and the length of this page when it does not. A response shape this code does not recognise comes back as an empty "signals" list with total_results 0 and page 1 and no error. Each signal is projected here onto a fixed field list, company_id, company_name, topic, signal_score, audience_strength, signal_date and category, and any of those whose value is null is dropped from the record rather than set to None, so read every field with .get().
enrich_contact()Enrich a contact with full details including email, phone, and social profiles.
result = qbash.integrations.zoominfo.enrich_contact(
contactIds=[...],
email="…",
)
contactIdsstr | list | NonedefaultNoneZoomInfo person IDs to enrich, as a list or a comma-separated string. Takes precedence over the name, email, and company fields.
emailstr | NonedefaultNoneContact email address to enrich.
firstNamestr | NonedefaultNoneFirst name for matching.
lastNamestr | NonedefaultNoneLast name for matching.
companyNamestr | NonedefaultNoneCompany name for matching.
outputFieldsstr | list | NonedefaultNoneSpecific ZoomInfo fields to return instead of the default set, as a list or a comma-separated string.
{"status": "success", "contacts": list[dict], "count": int}At least one identifier is required: contactIds, email, or name plus company. Passing contactIds skips the match fields entirely. This mode reads ZoomInfo and changes nothing there, and it spends a credit for each record that matches. "count" is the length of "contacts". There is no page or total on this mode because enrichment returns what matched and nothing else, and nothing here caps or batches the ids that are sent, so a long list is subject to whatever per request limit ZoomInfo applies. A match that finds nobody, and any response shape this code does not recognise, both come back as an empty "contacts" list with no error, so an empty list is not proof the person is absent from ZoomInfo. Each contact is projected here onto a fixed field list, id, first_name, last_name, full_name, email, phone, direct_phone, mobile_phone, job_title, management_level, department, company_name, company_id, linkedin_url, city, state, country, accuracy_score and last_updated, and any of those whose value is null is dropped from the record rather than set to None, so read every field with .get(). outputFields can only narrow that list. A ZoomInfo field outside it is dropped by the projection even when it was asked for.
enrich_company()Enrich a company with full firmographic details.
result = qbash.integrations.zoominfo.enrich_company(
companyIds=[...],
companyName="…",
)
companyIdsstr | list | NonedefaultNoneZoomInfo company IDs to enrich, as a list or a comma-separated string. Takes precedence over companyName and website.
companyNamestr | NonedefaultNoneCompany name to enrich.
websitestr | NonedefaultNoneCompany website used for matching when no company ID is given.
outputFieldsstr | list | NonedefaultNoneSpecific ZoomInfo fields to return instead of the default set, as a list or a comma-separated string.
{"status": "success", "companies": list[dict], "count": int}At least one identifier is required: companyIds, companyName, or website. This mode reads ZoomInfo and changes nothing there, and it spends a credit for each record that matches. "count" is the length of "companies". There is no page or total on this mode, and nothing here caps or batches the ids that are sent, so a long list is subject to whatever per request limit ZoomInfo applies. A match that finds nothing, and any response shape this code does not recognise, both come back as an empty "companies" list with no error. Each company is projected here onto a fixed field list, id, name, website, industry, sub_industry, sic_code, naics_code, employee_count, revenue, revenue_range, street, city, state, country, zip_code, phone, fax, linkedin_url, facebook_url, twitter_url, founded_year, company_status and description, and any of those whose value is null is dropped from the record rather than set to None, so read every field with .get(). outputFields can only narrow that list. A ZoomInfo field outside it is dropped by the projection even when it was asked for.
enrich_intent()Get buyer intent signals for a specific company and topics.
result = qbash.integrations.zoominfo.enrich_intent(
topics=[...],
)
topicslistrequiredList of intent topic strings. A comma-separated string is also accepted.
companyIdstr | NonedefaultNoneZoomInfo company ID to get intent for.
companyNamestr | NonedefaultNoneCompany name to get intent for when no company ID is known.
companyWebsitestr | NonedefaultNoneCompany website to get intent for when no company ID is known.
{"status": "success", "signals": list[dict], "count": int}At least one company identifier is required: companyId, companyName, or companyWebsite, and at least one topic. "count" is the length of "signals" and this mode has no paging at all, so there is no page or total_results the way the search modes have. A company with no signals for those topics, and any response shape this code does not recognise, both come back as an empty "signals" list with no error. Signals are projected onto the same fixed field list search_intent uses, company_id, company_name, topic, signal_score, audience_strength, signal_date and category, with null values dropped, so read them with .get().
get_usage()Check ZoomInfo API usage and remaining credits.
result = qbash.integrations.zoominfo.get_usage()
{"status": "success", "usage": dict}"usage" is projected here onto a fixed field list, credits_used, credits_limit, requests_used, requests_limit, period_start and period_end, each read from the vendor key or keys this code looks for. Any of them whose value is missing is dropped from the map rather than set to None, so read every one with .get(), and "usage" comes back as an empty dict when none of those names are present in the response.