Skip to main content

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:

company.py
def normalize_name(value):
return value.strip()

Your main.py can import it by its filename without the extension:

main.py
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,
)
  • fnCallablerequired
  • itemsIterablerequired
  • concurrencyintdefault 5
  • max_per_minuteOptional[int]default None
  • continue_on_errorbooldefault False
  • checkpointOptional[str]default None
  • checkpoint_keyAnydefault None
  • store_resultbooldefault False
  • retryUnion[int, bool, RetryPolicy, None]default None
ParallelResult

retry: 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"},
)
  • titlestrrequired

    Chat thread name.

  • contentstr | dict | listrequired

    Assistant message body (str, or dict/list which will be JSON-stringified).

  • skip_if_emptybooldefault False

    If True and content is falsy, returns None without creating the chat.

  • fileslist | Nonedefault None

    Optional list of QBash file-reference objects (from qbash.files.write, qbash.inputs, etc.) to attach.

  • read_onlybooldefault True

    When 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 | Nonedefault None

    LLM 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 | Nonedefault None

    Optional 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 | Nonedefault None

    Optional override for the chat owner. Must be a user in the same company as the task with an active membership.

  • user_emailstr | Nonedefault None

    Optional 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 | Nonedefault None

    Optional 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 | Nonedefault None

    Optionally 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] | Nonedefault None

    Optional 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 | Nonedefault None

    Optional 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] | Nonedefault None

    Optional 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] | Nonedefault None

    Optional 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 | None

Chat 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="…",
)
  • valueanyrequired

    Any JSON-serializable value.

  • typestr | Nonedefault None

    Optional 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 | listrequired

    The user-facing instruction for the model. A dict or list is sent as JSON text.

  • fileslist | Nonedefault None

    Optional list of QBash file-reference objects to attach.

  • systemstr | Nonedefault None

    Optional system message. No system message is sent when omitted.

  • modelstr | Nonedefault None

    Optional model slug (e.g. "claude-haiku-4-5"). Defaults to "claude-sonnet-4-6" when omitted.

  • temperaturefloat | Nonedefault None

    Optional sampling temperature (0.0-2.0).

  • max_tokensint | Nonedefault None

    Optional cap on response tokens.

  • output_schemadict | Nonedefault None

    Optional 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] | Nonedefault None

    Optional 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

  • cachebooldefault True

    Enable 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 | Nonedefault None
str | dict

Jev (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)
Returns

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 | Nonedefault None

    The inline prompt's slug, or the old UUID of a saved prompt that was migrated into this task.

  • variablesdict | Nonedefault None

    Template variables as a dict.

  • fileslist | Nonedefault None

    Optional list of QBash file-reference objects to attach.

  • slugstr | Nonedefault None

    Alternative keyword form for slug (same as passing a slug as prompt_ref).

  • cachebooldefault True

    Enable prompt caching (default True). See ai.raw() for details.

  • connectorslist[str] | Nonedefault None

    The 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] | Nonedefault None

    The 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] | Nonedefault None

    Agent 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 | Nonedefault None

    Run 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 | PromptResult
Connectors

Read 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)
Agent mode

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)
Progress

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().

Returns

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_fetch()

Fetch and extract the text content of a URL using Anthropic.

result = qbash.ai.anthropic_web_fetch(
url="https://…",
)
  • urlstrrequired

    The URL to fetch.

str
qbash.ai.gemini_url_context()

Fetch and summarise one or more URLs using Gemini URL context.

result = qbash.ai.gemini_url_context(
urls=[...],
)
  • urlslist | strrequired

    One URL, or a list of URLs.

str
qbash.ai.models()

List models from Aisle's model catalog.

result = qbash.ai.models(
query="search terms",
provider="…",
)
  • querystr | Nonedefault None

    Words that must all appear in the id, name, provider or vendor, e.g. "sonnet 4.6".

  • providerstr | Nonedefault None

    Who serves the model: "anthropic", "openai", "gemini", "x-ai", "typesafe" or "openrouter".

  • vendorstr | Nonedefault None

    Who made the model, e.g. "anthropic", "google", "meta-llama". Finds that maker's models across providers.

  • familystr | Nonedefault None

    Every row that runs one model (rows serving the same model share a family).

  • modestr | Nonedefault None

    "chat", "image_generation" or "decision".

  • requireslist[str] | Nonedefault None

    Capabilities every model must have: "structured_output", "thinking", "image_input", "pdf_input", "citations", "agent_tasks".

  • min_contextint | Nonedefault None

    Smallest context window in tokens.

  • include_deprecatedbooldefault False

    Include deprecated models.

  • configuredbool | Nonedefault True

    True (the default) lists only models this company has configured and can run, False only the ones it has not, None the whole catalog.

  • sortstr | Nonedefault None

    "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_idstrrequired

    Catalog model id, e.g. "claude-sonnet-5-5" or "anthropic/claude-3.5-haiku"

dict | None

The 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.read()

Read messages from a specific chat thread.

result = qbash.chats.read(
thread_id="…",
)
  • thread_idstrrequired

    The chat thread UUID (from search results).

  • limitintdefault 50

    Max messages to return (default 50, max 100).

  • offsetintdefault 0

    Number of messages to skip from the start (default 0). For page 2 of 50, pass offset=50, limit=50.

dict

Returns messages in chronological order with pagination support. Only works for threads owned by the executing user.

Returns

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_idstrrequired

    The chat thread UUID.

  • querystrrequired

    Search term to find within the conversation.

  • limitintdefault 10

    Max matching messages to return (default 10, max 30).

dict

More efficient than paginating through a long conversation when looking for specific messages. Only works for threads owned by the executing user.

Returns

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_slugstrrequired

    The 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 | Nonedefault None

    Input 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).

any
Returns

The 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.

Raises

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 | Nonedefault None

    The automated task UUID

  • limitintdefault 10

    Max results (default 10, max 50)

dict

List 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="…",
)
  • fileanyrequired

    The 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.

dict

Converts 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.

Returns

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="…",
)
  • pageanyrequired

    The page file reference from a plan unit's ``page`` field.

dict

One LLM call - a natural ``qbash.parallel`` unit. Only worth calling for units :meth:`extraction_plan` returned with ``content: None``.

Returns

dict with key: text.

qbash.documents.split()

Split text into chunks and group them into contextualization windows.

result = qbash.documents.split(
text="…",
ref="…",
)
  • textstr | Nonedefault None

    The document text to split. Exactly one of ``text`` or ``ref`` is required.

  • refanydefault None

    A 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.

dict

Deterministic 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.

Returns

dict with keys: chunks_ref, chunks, chunk_count, window_count. ``chunks`` is [{seq, window}] - sequence numbers and window indexes only, no text.

Raises

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_refanyrequired

    The ``chunks_ref`` handle from :meth:`split`.

  • seqintrequired

    Which chunk to contextualize - a ``seq`` from ``split``'s ``chunks`` list.

dict

One 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.

Returns

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.get()

Fetch a memory record by name, or an inline file by stable slug

result = qbash.memories.get(
name="…",
)
  • namestrrequired
  • folderstr | Nonedefault None
  • folder_idstr | Nonedefault None
dict

when 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="…",
)
  • namestrrequired
  • contentstr | Nonedefault None
  • metadatadict | Nonedefault None
  • filedict | Nonedefault None
  • await_embeddingbooldefault False
  • embedding_timeoutint | Nonedefault None
  • folderstr | Nonedefault None
  • folder_idstr | Nonedefault None
dict

Text 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_idstrrequired
  • folderstr | Nonedefault None
  • folder_idstr | Nonedefault None
dict

already-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_idstrrequired
  • folderstr | Nonedefault None
  • folder_idstr | Nonedefault None
dict

Embeddings 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_idstrrequired
  • timeoutint | Nonedefault None
  • folderstr | Nonedefault None
  • folder_idstr | Nonedefault None
dict

Polls 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.update()

Update an existing memory record by ID.

result = qbash.memories.update(
memory_id="…",
content="…",
)
  • memory_idstrrequired
  • contentstrrequired
  • metadatadict | Nonedefault None
  • mergebooldefault True
  • folderstr | Nonedefault None
  • folder_idstr | Nonedefault None
dict

The 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 | Nonedefault None
  • metadatadict | Nonedefault None
  • folderstr | Nonedefault None
  • folder_idstr | Nonedefault None
list

folder 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 | Nonedefault None
  • folder_idstr | Nonedefault None
list

files). ``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_idstrrequired
  • folderstr | Nonedefault None
  • folder_idstr | Nonedefault None
list

targets 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://…",
)
  • methodstrrequired

    HTTP method, e.g. "GET" or "POST".

  • urlstrrequired

    Absolute http(s) URL.

  • headersdict | list | Nonedefault None

    Optional request headers: a dict, or a list of name and value pairs.

  • bodydict | list | str | Nonedefault None

    Optional request body. A dict is sent as JSON.

  • authdict | Nonedefault None

    Optional auth dict built with qbash.http.auth.*.

  • follow_redirectsbooldefault True

    Follow 3xx redirects. Pass False to receive the redirect response itself (status, Location header).

  • timeoutint | Nonedefault None

    Request timeout in milliseconds (default: 30000).

dict

Make 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
dict
qbash.http.auth.basic()

Authorization: Basic base64(username:password). Resolved before encoding.

result = qbash.http.auth.basic(
username="…",
password="…",
)
  • usernamestrrequired
  • passwordstrrequired
dict
qbash.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="…",
)
  • namestrrequired
  • valuestrrequired
dict
qbash.http.auth.query()

Append `name=value` to the URL query string, URL-encoded.

result = qbash.http.auth.query(
name="…",
value="…",
)
  • namestrrequired
  • valuestrrequired
dict

qbash.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="…",
)
  • refanyrequired
  • modeanydefault 'rt'
  • encodinganydefault 'utf-8'
  • errorsanydefault 'strict'
any

mode='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="…",
)
  • contentanyrequired
  • namestrrequired
  • content_typestrdefault 'text/plain'
dict

content: 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="…",
)
  • namestrrequired
  • content_typestrdefault 'application/octet-stream'
  • byte_sizeint | Nonedefault None
dict

Returns {"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
dict

Call 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="…",
)
  • refanyrequired
  • encodingstrdefault 'text'
any

encoding="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
str

Useful 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 | strrequired

    A QBash file-reference object, or a file_upload_id.

  • include_urlbooldefault False

    Also return a presigned download_url, waiting for the file's scan to clear.

dict

With 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 | strrequired

    A QBash file-reference object, or a file_upload_id.

list

Returns 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 | strrequired

    A QBash file-reference object, or a file_upload_id.

dict
Output format is determined by input type

.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
any

qbash.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="…",
)
  • namestrrequired
  • content_typestrdefault 'text/plain'
StreamingWriter

Returns 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="…",
)
  • urlstrrequired

    URL to download from (http or https).

  • filenamestrrequired

    Name for the stored file (e.g. "report.pdf").

  • content_typestr | Nonedefault None

    Override content type. Auto-detected from the response Content-Type header if not provided.

  • headersdict | list | Nonedefault None

    Optional request headers dict.

  • authdict | Nonedefault None

    Optional auth dict (same format as qbash.http.request).

  • timeoutint | Nonedefault None

    How long to wait for the next piece of the response, in milliseconds (default: 60000). It is not a limit on the whole download.

dict

Returns 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="…",
)
  • namestrrequired
  • defaultAnydefault None
Any
qbash.inputs.all()

Return all input params as a dict.

result = qbash.inputs.all()
dict

qbash.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="…",
)
  • namestrrequired
  • defaultstr | Nonedefault None
SecretRef | str | None

Same 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
bool
qbash.checkpoint.mark()

Record completion with no stored value.

result = qbash.checkpoint.mark(
key="my-key",
)
  • keystrrequired
any
qbash.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="…",
)
  • keystrrequired
  • valueAnyrequired
any
qbash.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,
)
  • keystrrequired
  • fnCallablerequired
Any

Not 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="…",
)
  • keystrrequired
  • valueAnyrequired
  • ttlanyrequired
any
qbash.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="…",
)
  • keystrrequired
  • fnCallablerequired
  • ttlanyrequired
Any

Not 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
any

qbash.run​

qbash.run - runtime metadata for the current task invocation.

  • qbash.run.execution_idstr

    Unique id for this run; correlate against logs and execution records.

  • qbash.run.task_idstr | None

    The AutomatedTask id this run belongs to; None for unsaved test runs.

  • qbash.run.is_testbool

    True when invoked as an unsaved draft from the task editor.

  • qbash.run.started_atdatetime

    UTC timestamp of when this run was enqueued (tz-aware).

  • qbash.run.last_run_atdatetime | None

    UTC 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()
date

Use 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()
datetime
qbash.dates.parse()

Parse a value into a tz-aware UTC datetime.

result = qbash.dates.parse(
value="…",
)
  • valueanyrequired
datetime
Accepts

- 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

Raises

- 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_bytesbytesrequired
  • datadictrequired
bytes

qbash.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_bytesbytesrequired

    the PDF document as bytes.

list

qbash.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.

Raises

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_bytesbytesrequired

    the template PDF as bytes.

  • datadictrequired

    mapping of field name (or label) to value.

  • flattenbooldefault False

    remove form fields after filling. Default False keeps the output editable.

bytes

qbash.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.

Raises

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="…",
)
  • fTextIOrequired
  • chunk_sizeintdefault 1000
  • has_headerbooldefault True
  • dialectstrdefault '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="…",
)
  • fBinaryIOrequired
  • chunk_sizeintdefault 1000
  • has_headerbooldefault True
  • sheetslist[str | int] | Nonedefault None
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="…",
)
  • fTextIOrequired
  • chunk_sizeintdefault 1000
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.

ActiveCampaign​

qbash.integrations.active_campaign83 methods · ActiveCampaign connector
list_campaigns()List campaigns with their engagement stats (opens, clicks, bounces, unsubscribes).
result = qbash.integrations.active_campaign.list_campaigns(
limit=10,
offset=0,
)
  • limitint | Nonedefault None

    Maximum number of campaigns to return (max 100, default 20).

  • offsetint | Nonedefault None

    Pagination offset.

  • statusstr | Nonedefault None

    Filter by status code (0=draft, 1=scheduled, 2=sending, 3=paused, 5=completed).

  • orders_sdatestr | Nonedefault None

    Sort by send date, either ASC or DESC.

  • cursorstr | Nonedefault None

    Opaque 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_idstrrequired

    Campaign 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_idstrrequired

    Campaign 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_idstrrequired

    Campaign 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 | Nonedefault None

    Maximum number of contacts to return (max 100, default 20).

  • offsetint | Nonedefault None

    Pagination offset.

  • emailstr | Nonedefault None

    Exact email match filter.

  • list_idstr | Nonedefault None

    Filter by list ID.

  • tag_idstr | Nonedefault None

    Filter by tag ID.

  • statusstr | Nonedefault None

    List-membership status (-1=any, 0=unconfirmed, 1=active, 2=unsubscribed, 3=bounced). Combine with list_id for one list.

  • searchstr | Nonedefault None

    Free-text search across name and email.

  • idsstr | Nonedefault None

    Comma-separated known contact IDs, fetched in one request.

  • id_greaterstr | Nonedefault None

    Only IDs above this value; combine with orders_id=ASC to walk large audiences.

  • segment_idstr | Nonedefault None

    Existing segment ID. When set, most other filters are ignored by ActiveCampaign; status and search still apply.

  • automation_idstr | Nonedefault None

    Only contacts associated with this automation (seriesid).

  • form_idstr | Nonedefault None

    Only contacts associated with this signup form.

  • created_afterstr | Nonedefault None

    Only contacts created after this date.

  • created_beforestr | Nonedefault None

    Only contacts created before this date.

  • updated_afterstr | Nonedefault None

    Only contacts modified after this date; this is not the time of unsubscribe.

  • updated_beforestr | Nonedefault None

    Only contacts modified before this date.

  • tag_namestr | Nonedefault None

    Filter by tag name instead of tag_id; do not combine them.

  • orders_idstr | Nonedefault None

    ASC or DESC by contact ID.

  • orders_udatestr | Nonedefault None

    ASC or DESC by contact modification date.

  • cursorstr | Nonedefault None

    Opaque 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_idstrrequired

    Contact 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_idstrrequired

    Contact ID.

  • afterstr | Nonedefault None

    ISO 8601 timestamp. Only activities after this point are returned.

  • limitint | Nonedefault None

    Maximum number of activities to return.

  • offsetint | Nonedefault None

    Pagination offset.

  • includestr | Nonedefault None

    Comma-separated documented include paths, e.g. reference.log.campaign,reference.link,notes.

  • typestr | Nonedefault None

    Activity types, comma-separated, e.g. log,link-data,reply,tracking-log.

  • cursorstr | Nonedefault None

    Opaque 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_idstrrequired

    Contact ID.

  • limitint | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque 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 | Nonedefault None

    Maximum number of lists to return.

  • offsetint | Nonedefault None

    Pagination offset.

  • namestr | Nonedefault None

    Filter lists by name (server-side substring match).

  • cursorstr | Nonedefault None

    Opaque 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 | Nonedefault None

    Maximum number of tags to return.

  • offsetint | Nonedefault None

    Pagination offset.

  • searchstr | Nonedefault None

    Filter tags by name (server-side substring match).

  • cursorstr | Nonedefault None

    Opaque 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_idstrrequired

    Contact ID.

  • tag_idstrrequired

    Tag 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_idstrrequired

    Contact ID.

  • tag_idstrrequired

    Tag 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="…",
)
  • eventstrrequired

    Event name as configured in Settings → Tracking.

  • eventdatastr | Nonedefault None

    Optional payload string for the event.

  • emailstr | Nonedefault None

    Contact 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_idstrrequired

    campaign id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    message id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    template id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    list id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    tag id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    field id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    field id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    contact id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    contact id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    contact id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    contact id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    contact id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    contact id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    contact id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    automation id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    automation id. Use the ID returned by the corresponding list operation.

  • limitint | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    automation id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    segment id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    deal id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    pipeline id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    stage id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    task id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    deal id. Use the ID returned by the corresponding list operation.

  • limitint | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    deal id. Use the ID returned by the corresponding list operation.

  • limitint | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    account id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    account id. Use the ID returned by the corresponding list operation.

  • limitint | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    account id. Use the ID returned by the corresponding list operation.

  • limitint | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    note id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    score id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    webhook id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    form id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    user id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    group id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    connection id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    customer id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    order id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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 | Nonedefault None

    Page size, 1–100; default 20.

  • offsetint | Nonedefault None

    Non-negative vendor offset. Use either offset or cursor.

  • cursorstr | Nonedefault None

    Opaque continuation returned as next_cursor; use instead of offset.

  • filtersdict | Nonedefault None

    Documented vendor filters, for example {"status": 0, "group": 2} for deals. Encoded as filters[field].

  • ordersdict | Nonedefault None

    Documented vendor order fields mapped to ASC or DESC, for example {"title": "ASC"}.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    schema id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    schema id. Use the ID returned by the corresponding list operation.

  • record_idstrrequired

    record id. Use the ID returned by the corresponding list operation.

  • includestr | Nonedefault None

    Documented 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_idstrrequired

    campaign id. Use the ID returned by the corresponding list operation.

  • message_idstr | Nonedefault None

    Message ID for a split campaign; omitted means the vendor default.

  • pageintdefault 1

    Legacy 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 | Nonedefault None

    ASC 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_idstrrequired

    campaign id. Use the ID returned by the corresponding list operation.

  • message_idstr | Nonedefault None

    Message 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_idstrrequired

    campaign id. Use the ID returned by the corresponding list operation.

  • message_idstr | Nonedefault None

    Message ID for a split campaign; omitted means the vendor default.

  • pageintdefault 1

    Legacy 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_idstrrequired

    campaign id. Use the ID returned by the corresponding list operation.

  • message_idstr | Nonedefault None

    Message ID for a split campaign; omitted means the vendor default.

  • pageintdefault 1

    Legacy 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_idstrrequired

    campaign id. Use the ID returned by the corresponding list operation.

  • message_idstr | Nonedefault None

    Message ID for a split campaign; omitted means the vendor default.

  • pageintdefault 1

    Legacy 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_idstrrequired

    campaign id. Use the ID returned by the corresponding list operation.

  • message_idstr | Nonedefault None

    Message 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_idstrrequired

    campaign id. Use the ID returned by the corresponding list operation.

  • message_idstr | Nonedefault None

    Message ID for a split campaign; omitted means the vendor default.

  • start_datestr | Nonedefault None

    ISO-8601 start of reporting window; requires end_date.

  • end_datestr | Nonedefault None

    ISO-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

Affinity​

qbash.integrations.affinity39 methods · Affinity connector
search_persons()Search for persons in Affinity by name or email.
result = qbash.integrations.affinity.search_persons(
term="…",
page_size=10,
)
  • termstr | Nonedefault None

    Search term (name or email) to filter persons.

  • page_sizeint | Nonedefault None

    Number of results per page.

  • page_tokenstr | Nonedefault None

    Pagination token from a previous response.

  • with_interaction_datesbooldefault False

    Include first and last interaction dates in results.

  • with_opportunitiesbooldefault False

    Include associated opportunities in results.

  • with_current_organizationsbooldefault False

    Include 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_idintrequired

    Affinity person ID.

  • with_interaction_datesbooldefault False

    Include first and last interaction dates.

  • with_opportunitiesbooldefault False

    Include associated opportunities.

  • with_current_organizationsbooldefault False

    Include 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 | Nonedefault None

    Search term (name or domain) to filter organizations.

  • exact_matchbooldefault False

    Match the term exactly instead of a partial/fuzzy match.

  • page_sizeint | Nonedefault None

    Number of results per page.

  • page_tokenstr | Nonedefault None

    Pagination token from a previous response.

  • with_interaction_datesbooldefault False

    Include first and last interaction dates.

  • with_interaction_personsbooldefault False

    Include 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_idintrequired

    Affinity organization ID.

  • with_interaction_datesbooldefault False

    Include first and last interaction dates.

  • with_interaction_personsbooldefault False

    Include 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_idint

    Organization ID

  • person_idint

    Person ID

  • list_entry_idint

    List entry ID

  • opportunity_idint

    Opportunity ID

  • field_idslist[int] | Nonedefault None

    Keep values with these field IDs.

  • field_nameslist[str] | Nonedefault None

    Resolve 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_idintrequired

    List ID

  • with_field_valuesbooldefault False

    Merge each entry's field values onto the entry.

  • field_nameslist[str] | Nonedefault None

    Keep only merged field values with these names. The match is case sensitive.

  • page_sizeint

    Results per page

  • page_tokenstr | None

    Pagination 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_idintrequired

    Organization ID

  • with_field_valuesbooldefault False

    Merge each entry's field values onto the entry, one extra call per entry.

  • field_nameslist[str] | Nonedefault None

    Keep 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 | Nonedefault None

    0 person, 1 organization, 8 opportunity.

  • value_typeint | Nonedefault None

    Vendor value type, 0–7.

  • with_modified_namesbooldefault False

    Prefix field names with their list name.

  • exclude_dropdown_optionsbooldefault False

    Omit dropdown options.

  • list_idint | Nonedefault None

    Optional 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_idintrequired

    Field ID

  • entity_idintrequired

    Entity ID (organization, person, or opportunity)

  • valuestr | int | float | dict | list | Nonerequired

    Field value (type depends on field)

  • list_entry_idint

    List 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_idintrequired

    Field value ID to update

  • valuestr | int | float | dict | list | Nonerequired

    New 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_idintrequired

    Field 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_idintrequired

    List ID

  • entity_idintrequired

    Entity 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_idintrequired

    List entry ID to remove

  • list_idintrequired

    List 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_idintrequired

    List 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 | Nonedefault None

    Search opportunity names.

  • page_sizeint | Nonedefault None

    Number of results per page.

  • page_tokenstr | Nonedefault None

    Pagination 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_idintrequired

    Opportunity 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_idintrequired

    Organization ID

  • page_sizeint | Nonedefault None

    Number of results per page.

  • page_tokenstr | Nonedefault None

    Pagination 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_idintrequired

    Person ID

  • page_sizeint | Nonedefault None

    Number of results per page.

  • page_tokenstr | Nonedefault None

    Pagination 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_idintrequired

    Organization ID

  • typestr | intrequired

    meeting/0, call/1, chat/2 or email/3.

  • start_timestr | Nonedefault None

    ISO 8601 lower time bound. Must precede end_time, at most one year apart.

  • end_timestr | Nonedefault None

    ISO 8601 upper time bound, at most one year after start_time.

  • page_sizeint | Nonedefault None

    Results per page, 1–100.

  • page_tokenstr | Nonedefault None

    Previous next_page_token with the same entity, type and range.

  • internal_person_idint | Nonedefault None

    Filter to interactions involving this internal person.

  • logging_typeint | Nonedefault None

    0 automatically logged, 1 manually logged.

  • directionint | Nonedefault None

    0 incoming, 1 outgoing for chat/email.

  • min_interaction_datestr | Nonedefault None

    Legacy alias for start_time. ISO 8601 timestamp.

  • max_interaction_datestr | Nonedefault None

    Legacy 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_idintrequired

    Organization ID

  • field_nameslist[str] | Nonedefault None

    Keep 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_idintrequired

    Person ID

  • field_nameslist[str] | Nonedefault None

    Keep 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_idintrequired

    Person ID

  • field_nameslist[str] | Nonedefault None

    Keep 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_idintrequired

    Affinity 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_idintrequired

    Affinity list_id.

  • list_entry_idintrequired

    Affinity 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_idintrequired

    Affinity opportunity_id.

  • page_sizeint | Nonedefault None

    Results per page, 1–500.

  • page_tokenstr | Nonedefault None

    Previous 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 | Nonedefault None

    Optional entity filter.

  • organization_idint | Nonedefault None

    Optional entity filter.

  • opportunity_idint | Nonedefault None

    Optional entity filter.

  • page_sizeint | Nonedefault None

    Results per page, 1–500.

  • page_tokenstr | Nonedefault None

    Previous 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 | Nonedefault None

    Optional entity filter.

  • organization_idint | Nonedefault None

    Optional entity filter.

  • opportunity_idint | Nonedefault None

    Optional entity filter.

  • creator_idint | Nonedefault None

    Internal person ID.

  • owner_idint | Nonedefault None

    Internal person ID.

  • completer_idint | Nonedefault None

    Internal person ID.

  • typeint | Nonedefault None

    0 one-time, 1 recurring.

  • reset_typeint | Nonedefault None

    0 email/meeting, 1 email, 2 meeting. Required for recurring type.

  • statusint | Nonedefault None

    0 completed, 1 active, 2 overdue.

  • due_beforestr | Nonedefault None

    ISO 8601 upper due-date bound.

  • due_afterstr | Nonedefault None

    ISO 8601 lower due-date bound.

  • page_sizeint | Nonedefault None

    Results per page, 1–500.

  • page_tokenstr | Nonedefault None

    Previous 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_idintrequired

    Affinity field_id.

  • person_idint | Nonedefault None

    Optional entity filter.

  • organization_idint | Nonedefault None

    Optional entity filter.

  • opportunity_idint | Nonedefault None

    Optional entity filter.

  • list_entry_idint | Nonedefault None

    Optional list entry filter.

  • action_typeint | Nonedefault None

    0 create, 1 delete, 2 update.

  • changed_afterstr | Nonedefault None

    Inclusive ISO 8601 timestamp.

  • order_bystrdefault "desc"

    asc or desc.

  • after_idint | Nonedefault None

    Last change ID. Requires changed_after and order_by=asc.

  • limitintdefault 100

    Maximum 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_idintrequired

    Affinity person_id.

  • typestr | intrequired

    meeting/0, call/1, chat/2 or email/3.

  • start_timestrrequired

    ISO 8601 lower time bound. Must precede end_time, at most one year apart.

  • end_timestrrequired

    ISO 8601 upper time bound, at most one year after start_time.

  • page_sizeint | Nonedefault None

    Results per page, 1–100.

  • page_tokenstr | Nonedefault None

    Previous next_page_token with the same entity, type and range.

  • internal_person_idint | Nonedefault None

    Filter to interactions involving this internal person.

  • logging_typeint | Nonedefault None

    0 automatically logged, 1 manually logged.

  • directionint | Nonedefault None

    0 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_idintrequired

    Affinity opportunity_id.

  • typestr | intrequired

    meeting/0, call/1, chat/2 or email/3.

  • start_timestrrequired

    ISO 8601 lower time bound. Must precede end_time, at most one year apart.

  • end_timestrrequired

    ISO 8601 upper time bound, at most one year after start_time.

  • page_sizeint | Nonedefault None

    Results per page, 1–100.

  • page_tokenstr | Nonedefault None

    Previous next_page_token with the same entity, type and range.

  • internal_person_idint | Nonedefault None

    Filter to interactions involving this internal person.

  • logging_typeint | Nonedefault None

    0 automatically logged, 1 manually logged.

  • directionint | Nonedefault None

    0 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_idintrequired

    Affinity list_id.

  • limitint | Nonedefault None

    Results per page, 1–100.

  • cursorstr | Nonedefault None

    Previous 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_idintrequired

    Affinity list_id.

  • view_idintrequired

    Affinity view_id.

  • limitint | Nonedefault None

    Results per page, 1–100.

  • cursorstr | Nonedefault None

    Previous 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_idintrequired

    Affinity person_id.

  • idslist[str] | Nonedefault None

    V2 field IDs, mutually exclusive with types.

  • typeslist[str] | Nonedefault None

    enriched, global or relationship-intelligence. Mutually exclusive with ids.

  • limitint | Nonedefault None

    Results per page, 1–100.

  • cursorstr | Nonedefault None

    Previous 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_idintrequired

    Affinity organization_id.

  • idslist[str] | Nonedefault None

    V2 field IDs, mutually exclusive with types.

  • typeslist[str] | Nonedefault None

    enriched, global or relationship-intelligence. Mutually exclusive with ids.

  • limitint | Nonedefault None

    Results per page, 1–100.

  • cursorstr | Nonedefault None

    Previous 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_idintrequired

    Affinity person_id.

  • filterstr | Nonedefault None

    Vendor V2 filter expression.

  • order_bylist[str] | Nonedefault None

    interactionScore or -interactionScore.

  • limitint | Nonedefault None

    Results per page, 1–100.

  • cursorstr | Nonedefault None

    Previous 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_idintrequired

    Affinity organization_id.

  • filterstr | Nonedefault None

    Vendor V2 filter expression.

  • order_bylist[str] | Nonedefault None

    interactionScore or -interactionScore.

  • limitint | Nonedefault None

    Results per page, 1–100.

  • cursorstr | Nonedefault None

    Previous 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.

Ahrefs​

qbash.integrations.ahrefs11 methods · Ahrefs connector
domain_rating()Get Domain Rating and Ahrefs Rank for a domain.
result = qbash.integrations.ahrefs.domain_rating(
target="…",
)
  • targetstrrequired

    Domain to analyse (e.g. example.com). Protocol prefix is stripped automatically.

  • datestr | Nonedefault None

    Historical 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="…",
)
  • targetstrrequired

    Domain to analyse.

  • datestr | Nonedefault None

    Historical date in YYYY-MM-DD format.

  • countrystr | Nonedefault None

    Two-letter country code (e.g. us).

  • target_modestr | Nonedefault None

    Target 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="…",
)
  • targetstrrequired

    Domain to analyse.

  • limitintdefault 100

    Maximum number of backlinks to return.

  • target_modestr | Nonedefault None

    Target Mode (domain / prefix / exact / subdomains).

  • order_bystr | Nonedefault None

    Order 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="…",
)
  • targetstrrequired

    Domain to analyse.

  • limitintdefault 100

    Maximum number of referring domains to return.

  • order_bystr | Nonedefault None

    Order 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="…",
)
  • targetstrrequired

    Domain to analyse.

  • countrystr | Nonedefault "us"

    Two-letter country code (e.g. us).

  • limitintdefault 100

    Maximum number of keywords to return.

  • order_bystr | Nonedefault None

    Order 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="…",
)
  • targetstrrequired

    Domain to analyse.

  • countrystr | Nonedefault "us"

    Two-letter country code (e.g. us).

  • limitintdefault 100

    Maximum 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="…",
)
  • targetstrrequired

    Domain to analyse.

  • countrystr | Nonedefault "us"

    Two-letter country code (e.g. us).

  • limitintdefault 100

    Maximum 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=[...],
)
  • keywordslistrequired

    Keywords 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="…",
)
  • targetstrrequired

    Domain or URL to analyse.

  • limitintdefault 100

    Maximum rows to return.

  • target_modestr | Nonedefault None

    Target scope: exact, prefix, domain, or subdomains.

  • aggregationstr | Nonedefault None

    Backlink grouping: similar_links, 1_per_domain, or all.

  • order_bystr | Nonedefault None

    Ahrefs 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="…",
)
  • targetstrrequired

    Domain or URL to analyse.

  • limitintdefault 100

    Maximum rows to return.

  • target_modestr | Nonedefault None

    Target scope: exact, prefix, domain, or subdomains.

  • historystr | Nonedefault None

    Backlink period: live, since:YYYY-MM-DD, or all_time.

  • order_bystr | Nonedefault None

    Ahrefs 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=[...],
)
  • keywordslistrequired

    Seed terms as a list of strings or a comma-separated string.

  • countrystrdefault "us"

    Two-letter ISO country code.

  • limitintdefault 100

    Maximum 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 | Nonedefault None

    Ahrefs 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.

Airtable​

qbash.integrations.airtable16 methods · Airtable connector
list_bases()List all accessible Airtable bases.
result = qbash.integrations.airtable.list_bases(
offset="…",
)
  • offsetstr | Nonedefault None

    Pagination 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_idstrrequired

    Airtable 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table ID or name to list records from.

  • filterByFormulastr | Nonedefault None

    Airtable formula to filter records, for example NOT to skip empty names.

  • fieldslist | Nonedefault None

    Field names to return on each record. Omit to return every field.

  • maxRecordsint | Nonedefault None

    Maximum total number of records to return. Omit to return all records, paginated at 100 per page via offset.

  • sortlist[dict] | Nonedefault None

    Sort specs applied by Airtable, in priority order.

    • fieldstrrequired

      Field name to sort by.

    • directionstrrequired

      Sort direction. One of asc or desc.

  • viewstr | Nonedefault None

    View name or ID to filter records by.

  • offsetstr | Nonedefault None

    Pagination cursor from a previous list_records response. Pass it to fetch the next page.

  • pageSizeint | Nonedefault None

    Number 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table ID or name to search.

  • searchTermstrrequired

    Text to match. Matching is case-insensitive substring, not whole word or exact.

  • fieldIdslist | Nonedefault None

    Field names to search in. Omit to search every text field, which costs one extra API call to read the table schema.

  • maxRecordsint | Nonedefault None

    Maximum total number of matching records to return. Omit to return all matches, paginated at 100 per page via offset.

  • offsetstr | Nonedefault None

    Pagination cursor from a previous search_records response. Pass it to fetch the next page of matches.

  • pageSizeint | Nonedefault None

    Number of records per page. Maximum 100. Omit to use the Airtable default of 100.

  • viewstr | Nonedefault None

    View 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table ID or name containing the record.

  • recordIdstrrequired

    Airtable 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table ID or name to create the record in.

  • fieldsdictrequired

    Map 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table ID or name to create records in.

  • recordslist[dict]required

    Records to create. Maximum 10. Each entry must be an object with a fields key.

    • fieldsdictrequired

      Map 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table ID or name containing the records to update.

  • recordslist[dict]required

    Records to update. Maximum 10. Each entry must have both id and fields.

    • idstrrequired

      Airtable record ID in recXXX format.

    • fieldsdictrequired

      Map 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table ID or name containing the records to delete.

  • recordIdslist[str]required

    Record 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_idstrrequired

    Airtable 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • namestrrequired

    Name for the new table.

  • tableFieldslist[dict]required

    Field definitions for the new table. Each entry needs a name and a type.

    • namestrrequired

      Field name.

    • typestrrequired

      Field type, one of the values list_field_types returns.

    • optionsdict | Nonedefault None

      Type-specific options. Required for singleSelect and multipleSelects, which need a choices list.

  • descriptionstr | Nonedefault None

    Description 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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table ID or name to add the field to.

  • fieldConfigdictrequired

    Field 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.

    • namestrrequired

      Field name.

    • typestrrequired

      Field type, one of the values list_field_types returns.

    • descriptionstr | Nonedefault None

      Description for the field.

    • optionsdict | Nonedefault None

      Type-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_idstrrequired

    Airtable base ID in appXXX format. Ignored when the credential API key already embeds a base ID.

  • tableIdstrrequired

    Table ID or name containing the field.

  • fieldIdstrrequired

    Field ID or name to update.

  • updatesdictrequired

    Updates 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 | Nonedefault None

      New field name.

    • descriptionstr | Nonedefault None

      New field description.

    • optionsdict | Nonedefault None

      Replacement 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.

Algolia​

qbash.integrations.algolia5 methods · Algolia connector
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_pageintdefault 100

    Maximum indices per page.

  • pageintdefault 0

    Zero-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_namestrrequired

    Algolia index name.

  • querystr | Nonedefault None

    Search query text.

  • hits_per_pageintdefault 20

    Maximum hits per page.

  • pageintdefault 0

    Zero-based result page.

  • filtersstr | Nonedefault None

    Algolia filters expression.

  • facet_filtersstr | list | Nonedefault None

    Facet filters as comma-separated string or list.

  • numeric_filtersstr | list | Nonedefault None

    Numeric filters as comma-separated string or list.

  • attributes_to_retrievestr | list | Nonedefault None

    Attributes to include in each hit.

  • restrict_searchable_attributesstr | list | Nonedefault None

    Search only these searchable attributes.

  • get_ranking_infobooldefault False

    Include Algolia ranking details.

  • typo_tolerancestr | bool | Nonedefault None

    Algolia 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=[...],
)
  • requestslistrequired

    List 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_namestrrequired

    Algolia index name.

  • querystr | Nonedefault None

    Optional browse query text.

  • cursorstr | Nonedefault None

    Browse cursor from a previous response.

  • hits_per_pageintdefault 20

    Maximum records per page.

  • filtersstr | Nonedefault None

    Algolia filters expression.

  • attributes_to_retrievestr | list | Nonedefault None

    Attributes 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_namestrrequired

    Algolia index name.

  • object_idstrrequired

    Algolia objectID value.

  • attributes_to_retrievestr | list | Nonedefault None

    Attributes 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.

Apollo.io​

qbash.integrations.apollo8 methods · Apollo.io connector
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_titleslistdefault None

    List of job titles to match (e.g. ["Founder","CEO","Owner"]). Fuzzy match by default. Entries that are not strings are dropped.

  • person_senioritieslistdefault None

    Seniority levels: owner, founder, c_suite, partner, vp, head, director, manager, senior, entry, intern.

  • person_locationslistdefault None

    List of city/state/country strings.

  • q_keywordsstrdefault None

    Free-text keyword filter applied to people and their orgs.

  • q_organization_domains_listlistdefault None

    List of company domains to restrict the search to (e.g. ["thoughtbot.com"]). Up to 1,000 per request.

  • organization_idslistdefault None

    List of Apollo organization IDs as strings. Entries that are not strings are dropped.

  • organization_num_employees_rangeslistdefault None

    Headcount ranges as "min,max" strings (e.g. ["1,10","11,50"]).

  • pageintdefault 1

    Page number (1-based).

  • per_pageintdefault 25

    Results 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 | Nonedefault None

    Person's first name.

  • last_namestr | Nonedefault None

    Person's last name.

  • namestr | Nonedefault None

    Full name (alternative to first/last).

  • emailstr | Nonedefault None

    Email address.

  • hashed_emailstr | Nonedefault None

    MD5 or SHA-256 hashed email.

  • organization_namestr | Nonedefault None

    Current employer name.

  • domainstr | Nonedefault None

    Employer domain (no www. or scheme).

  • idstr | Nonedefault None

    Apollo person ID.

  • linkedin_urlstr | Nonedefault None

    LinkedIn profile URL.

  • reveal_personal_emailsbooldefault False

    Reveal personal email addresses (extra credits).

  • reveal_phone_numberbooldefault False

    Reveal phone numbers. Costs extra credits and requires webhook_url.

  • webhook_urlstr | Nonedefault None

    Webhook Apollo posts phone reveal results to. Mandatory when reveal_phone_number is True.

  • run_waterfall_emailbooldefault False

    Use Apollo's waterfall to broaden email coverage across providers.

  • run_waterfall_phonebooldefault False

    Use 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=[...],
)
  • peoplelistrequired

    List 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_emailsbooldefault False

    Reveal personal emails for the whole batch.

  • reveal_phone_numberbooldefault False

    Reveal phone numbers for the whole batch.

  • webhook_urlstr | Nonedefault None

    Required when reveal_phone_number is True.

  • run_waterfall_emailbooldefault False

    Email waterfall for the batch.

  • run_waterfall_phonebooldefault False

    Phone 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 | Nonedefault None

    Company domain, the most reliable identifier. No www. or scheme.

  • linkedin_urlstr | Nonedefault None

    Company LinkedIn profile URL.

  • namestr | Nonedefault None

    Company name.

  • websitestr | Nonedefault None

    Full 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=[...],
)
  • orgslistrequired

    List 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_idstrrequired

    Apollo organization ID (returned by org_enrich).

  • pageintdefault 1

    Page number (1-based).

  • per_pageintdefault 25

    Results 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_idslistrequired

    List of Apollo organization IDs as strings. Entries that are not strings are dropped, and an empty result after that is an error.

  • categorieslistdefault None

    Event categories: hires, funding, investment, acquisition, layoffs, new_product, partnership.

  • published_at_minstr | Nonedefault None

    Lower date bound (YYYY-MM-DD).

  • published_at_maxstr | Nonedefault None

    Upper date bound (YYYY-MM-DD).

  • pageintdefault 1

    Page number (1-based).

  • per_pageintdefault 25

    Results 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.

Asana​

qbash.integrations.asana20 methods · Asana connector
list_workspaces()List all accessible Asana workspaces.
result = qbash.integrations.asana.list_workspaces(
limit=10,
offset="…",
)
  • limitint | Nonedefault None

    Maximum 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 | Nonedefault None

    Pagination 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_gidstrrequired

    GID of the workspace whose users are listed.

  • limitintdefault 50

    Maximum number of users to return per page. Asana accepts values from 1 to 100.

  • offsetstr | Nonedefault None

    Pagination 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_gidstrrequired

    GID of the workspace whose projects are listed.

  • limitintdefault 50

    Maximum number of projects to return per page. Asana accepts values from 1 to 100.

  • offsetstr | Nonedefault None

    Pagination 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_gidstrrequired

    GID 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_gidstrrequired

    GID of the project whose sections are listed.

  • limitint | Nonedefault None

    Maximum 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 | Nonedefault None

    Pagination 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 | Nonedefault None

    GID of the project whose tasks are listed. Either project_gid or section_gid is required.

  • section_gidstr | Nonedefault None

    GID of the section whose tasks are listed. Either project_gid or section_gid is required, and section_gid wins when both are given.

  • limitintdefault 50

    Maximum number of tasks to return per page. Asana accepts values from 1 to 100.

  • completed_sincestr | Nonedefault None

    Return 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 | Nonedefault None

    Pagination 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_gidstrrequired

    GID of the parent task.

  • limitintdefault 50

    Maximum 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_gidstrrequired

    GID 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_gidstrrequired

    GID of the task whose stories are listed.

  • limitintdefault 50

    Maximum number of stories to return per page. Asana accepts values from 1 to 100.

  • offsetstr | Nonedefault None

    Pagination 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_gidstrrequired

    GID of the workspace to search within.

  • textstrrequired

    Text matched against task names and descriptions.

  • limitintdefault 20

    Maximum number of results to return. Asana accepts values from 1 to 100.

  • completedbool | Nonedefault None

    Set 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="…",
)
  • namestrrequired

    Name of the task.

  • workspace_gidstr | Nonedefault None

    GID of the workspace to create the task in. Either workspace_gid or project_gid is required.

  • project_gidstr | Nonedefault None

    GID of the project to add the task to. Either workspace_gid or project_gid is required.

  • notesstr | Nonedefault None

    Plain text description of the task.

  • due_onstr | Nonedefault None

    Due date in YYYY-MM-DD format.

  • assigneestr | Nonedefault None

    GID 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_gidstrrequired

    GID of the parent task.

  • namestrrequired

    Name of the subtask.

  • notesstr | Nonedefault None

    Plain text description of the subtask.

  • due_onstr | Nonedefault None

    Due date in YYYY-MM-DD format.

  • assigneestr | Nonedefault None

    GID 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_gidstrrequired

    GID of the task to update.

  • namestr | Nonedefault None

    New name for the task.

  • notesstr | Nonedefault None

    New plain text description for the task.

  • due_onstr | Nonedefault None

    New due date in YYYY-MM-DD format.

  • assigneestr | Nonedefault None

    GID of the user to assign, or the literal string "me" for the authenticated user.

  • completedbool | Nonedefault None

    Set 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_gidstrrequired

    GID of the task to move.

  • section_gidstrrequired

    GID 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_gidstrrequired

    GID of the task that depends on the other tasks.

  • dependency_gidslist | strrequired

    Task 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_gidstrrequired

    GID 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_gidstrrequired

    GID of the task to comment on.

  • textstrrequired

    Plain 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]required

    Task 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]required

    Update objects, one per task.

    • task_gidstrrequired

      GID 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="…",
)
  • namestrrequired

    Name of the project.

  • workspace_gidstrrequired

    GID of the workspace to create the project in.

  • team_gidstr | Nonedefault None

    GID of the team that owns the project. Asana requires this when the workspace is an organization.

  • notesstr | Nonedefault None

    Plain text description of the project.

  • colorstr | Nonedefault None

    Project 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.

AWS​

qbash.integrations.aws1 method · AWS connector
knowledge_base()Search an AWS Bedrock knowledge base with one or more queries.
result = qbash.integrations.aws.knowledge_base(
knowledgeBaseId="…",
queries=[...],
)
  • knowledgeBaseIdstrrequired

    AWS Bedrock knowledge base ID.

  • querieslistrequired

    Search query strings, as a real list. An empty list is an error. Pass a list even when there is only one query.

  • contentTypeFilterslist | Nonedefault None

    Content 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 | Nonedefault None

    Content 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 | Nonedefault 10

    Maximum 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 | Nonedefault False

    Fetch 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.

Azure SQL​

qbash.integrations.mssql4 methods · Azure SQL connector
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",
)
  • querystrrequired

    SQL 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 | Nonedefault 30000

    Query timeout in milliseconds. A string of digits is parsed, and anything else falls back to 30000.

  • retryAttemptsint | str | Nonedefault 4

    Attempts to make on transient errors, deadlocks and connection failures, from 1 to 10. Values outside that range are clamped rather than rejected.

  • queryParamslist | Nonedefault None

    Values 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_value

Rows 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="…",
)
  • tablestrrequired

    Table 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.

Browserless​

qbash.integrations.browserless4 methods · Browserless connector
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://…",
)
  • urlstrrequired

    Absolute URL to open.

  • waitForSelectorstr | Nonedefault None

    CSS selector to wait for before extracting, for pages that render after load.

  • includeHiddenbool | Nonedefault False

    Include elements that are not visible. Off by default.

  • timeoutMsint | Nonedefault 60000

    Budget 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="…",
)
  • codestrrequired

    A 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 | Nonedefault None

    Values passed to the function as `context`. Put URLs and credentials here rather than inlining them in code.

  • timeoutMsint | Nonedefault 60000

    Budget 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://…",
)
  • urlstrrequired

    Absolute URL to open.

  • waitForSelectorstr | Nonedefault None

    CSS selector to wait for before capturing.

  • timeoutMsint | Nonedefault 60000

    Budget 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://…",
)
  • urlstrrequired

    Absolute URL to open.

  • fullPagebool | Nonedefault False

    Capture the full scrollable page rather than the viewport.

  • timeoutMsint | Nonedefault 60000

    Budget 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.

Bugsnag​

qbash.integrations.bugsnag8 methods · Bugsnag connector
list_organizations()List organizations for the authenticated user.
result = qbash.integrations.bugsnag.list_organizations(
per_page=0,
page_token="…",
)
  • per_pageint | Nonedefault None

    Number of organizations per page, from 1 to 100. Ignored with page_token.

  • page_tokenstr | Nonedefault None

    An 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_idstrrequired

    Organization ID.

  • per_pageint | Nonedefault None

    Number of projects per page, from 1 to 100. Ignored with page_token.

  • page_tokenstr | Nonedefault None

    An 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_idstrrequired

    Project ID.

  • sortstr | Nonedefault None

    Sort field (last_seen, first_seen, events, users, severity).

  • directionstr | Nonedefault None

    Sort direction: asc or desc.

  • per_pageint | Nonedefault None

    Number 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_idstrrequired

    Project ID.

  • error_idstrrequired

    Error 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_idstrrequired

    Project ID.

  • error_idstrrequired

    Error ID.

  • per_pageint | Nonedefault None

    Number 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_idstrrequired

    Project ID.

  • event_idstrrequired

    Event 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_idstrrequired

    Project ID.

  • error_idstrrequired

    Error ID.

  • buckets_countint | Nonedefault None

    Number 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_idstrrequired

    Project 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.

Cobalt​

qbash.integrations.cobalt34 methods · Cobalt connector
list_companies()List portfolio companies.
result = qbash.integrations.cobalt.list_companies(
sort="…",
limit="…",
)
  • sortstr | Nonedefault None

    Field to sort the companies by. The value is passed to Cobalt as given and is not checked here.

  • limitstr | Nonedefault None

    Maximum number of companies to return, passed to Cobalt as the limit query parameter. Cobalt decides the value when it is omitted.

  • offsetstr | Nonedefault None

    Number 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_uidstrrequired

    UID 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="…",
)
  • namestrrequired

    Company name.

  • currencystr | Nonedefault None

    Currency 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_uidstrrequired

    UID 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 | Nonedefault None

    New company name.

  • currencystr | Nonedefault None

    New 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_uidstrrequired

    UID 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_uidstrrequired

    UID 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,
)
  • namestrrequired

    Custom field name.

  • field_typeintrequired

    Cobalt field type identifier. The valid values are Cobalt's and are not checked here.

  • has_dateboolrequired

    Whether values of this field carry a date.

  • default_fundboolrequired

    Whether the field is applied to funds by default.

  • default_companyboolrequired

    Whether the field is applied to companies by default.

  • default_dealboolrequired

    Whether the field is applied to deals by default.

  • default_cashflowboolrequired

    Whether 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_uidstrrequired

    UID 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 | Nonedefault None

    New 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_uidstrrequired

    UID 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_uidsstrrequired

    Comma separated UIDs of the entities to read values for. Missing or empty fails before the request is sent.

  • custom_field_uidsstr | Nonedefault None

    Comma separated UIDs of the custom fields to restrict the read to. All fields on the entities are read when omitted.

  • as_of_datestr | Nonedefault None

    Date 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="…",
)
  • uidstrrequired

    UID 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_uidstrrequired

    UID of the custom field definition the value belongs to.

  • valuestrrequired

    Value to store.

  • company_uidstr | Nonedefault None

    UID 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 | Nonedefault None

    UID of the deal the value attaches to.

  • fund_uidstr | Nonedefault None

    UID of the fund the value attaches to.

  • cashflow_uidstr | Nonedefault None

    UID 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="…",
)
  • uidstrrequired

    UID 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 | Nonedefault None

    New value to store.

  • datestr | Nonedefault None

    Date 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_valueslistrequired

    List 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="…",
)
  • uidstrrequired

    UID 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 | Nonedefault None

    Restrict the deals to one portfolio by UID.

  • fund_uidstr | Nonedefault None

    Restrict the deals to one fund by UID.

  • company_uidstr | Nonedefault None

    Restrict the deals to one company by UID.

  • sortstr | Nonedefault None

    Field to sort the deals by. The value is passed to Cobalt as given and is not checked here.

  • limitstr | Nonedefault None

    Maximum number of deals to return, passed to Cobalt as the limit query parameter. Cobalt decides the value when it is omitted.

  • offsetstr | Nonedefault None

    Number 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_uidstrrequired

    UID 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_uidstrrequired

    UID of the company the deal belongs to.

  • fund_uidstrrequired

    UID of the fund the deal belongs to.

  • currency_symbolstr | Nonedefault None

    Currency 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=[...],
)
  • dealslistrequired

    List 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_uidstrrequired

    UID 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_uidsstrrequired

    Comma 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 | Nonedefault None

    Restrict the funds to one portfolio by UID.

  • sortstr | Nonedefault None

    Field to sort the funds by. The value is passed to Cobalt as given and is not checked here.

  • limitstr | Nonedefault None

    Maximum number of funds to return, passed to Cobalt as the limit query parameter. Cobalt decides the value when it is omitted.

  • offsetstr | Nonedefault None

    Number 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_uidstrrequired

    UID 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="…",
)
  • namestrrequired

    Fund name.

  • portfolio_uidstr | Nonedefault None

    UID of the portfolio the fund belongs to.

  • currencystr | Nonedefault None

    Currency 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_uidstrrequired

    UID 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 | Nonedefault None

    New 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_uidstrrequired

    UID 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 | Nonedefault None

    Maximum number of portfolios to return, passed to Cobalt as the limit query parameter. Cobalt decides the value when it is omitted.

  • offsetstr | Nonedefault None

    Number 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_uidstrrequired

    UID 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="…",
)
  • namestrrequired

    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 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_uidstrrequired

    UID 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 | Nonedefault None

    New 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_uidstrrequired

    UID 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.

DataForSEO​

qbash.integrations.dataforseo4 methods · DataForSEO connector
keyword_volume()Look up search volume, CPC, and competition for a known list of keywords.
result = qbash.integrations.dataforseo.keyword_volume(
keywords=[...],
)
  • keywordslistrequired

    List of keyword strings to look up (up to 1000). Accepts a comma-separated string as a fallback.

  • location_codeintdefault 2840

    DataForSEO 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_searchesbooldefault False

    Include 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=[...],
)
  • keywordslistrequired

    Seed keywords. List of strings or comma-separated string.

  • location_codeintdefault 2840

    DataForSEO location code (default 2840 = United States).

  • language_codestrdefault "en"

    ISO 639-1 language code.

  • limitintdefault 100

    Maximum 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",
)
  • keywordstrrequired

    Single seed keyword to expand.

  • location_codeintdefault 2840

    DataForSEO location code (default 2840 = United States).

  • language_codestrdefault "en"

    ISO 639-1 language code.

  • limitintdefault 100

    Maximum suggestions to return, from 1 to 1000.

  • offsetintdefault 0

    Pagination offset.

  • include_seed_keywordbooldefault True

    Whether 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",
)
  • keywordstrrequired

    Single seed keyword to expand.

  • location_codeintdefault 2840

    DataForSEO location code (default 2840 = United States).

  • language_codestrdefault "en"

    ISO 639-1 language code.

  • limitintdefault 100

    Maximum related keywords to return, from 1 to 1000.

  • offsetintdefault 0

    Pagination offset.

  • depthintdefault 1

    How 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().

DigitalOcean​

qbash.integrations.digitalocean30 methods · DigitalOcean connector
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,
)
  • pageintdefault 1

    Page number, starting at 1.

  • per_pageintdefault 50

    Items 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 | Nonedefault None

    Only resources with this tag.

  • pageintdefault 1

    Page number, starting at 1.

  • per_pageintdefault 50

    Items 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_idintrequired

    Droplet 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_idintrequired

    Droplet id, a number such as 3164444. list_droplets returns it.

  • metricstrrequired

    cpu, memory_free, memory_available, memory_total, load_1, load_5, load_15, filesystem_free, filesystem_size or bandwidth.

  • startstr | Nonedefault None

    Window start as an ISO 8601 time, such as 2026-10-14T09:00:00Z. Defaults to one hour before end.

  • endstr | Nonedefault None

    Window end as an ISO 8601 time. Defaults to now.

  • interfacestr | Nonedefault None

    public or private. Required for bandwidth.

  • directionstr | Nonedefault None

    inbound 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_idintrequired

    Droplet id, a number such as 3164444. list_droplets returns it.

  • typestrrequired

    reboot, 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,
)
  • pageintdefault 1

    Page number, starting at 1.

  • per_pageintdefault 50

    Items 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_idstrrequired

    App 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_idstrrequired

    App 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_idstrrequired

    App id from list_apps.

  • pageintdefault 1

    Page number, starting at 1.

  • per_pageintdefault 50

    Items 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_idstrrequired

    App id from list_apps.

  • deployment_idstr | Nonedefault None

    Deployment id from list_app_deployments. Defaults to the active deployment.

  • component_namestr | Nonedefault None

    One component's name from get_app, such as api. Omit for all components.

  • log_typestrdefault BUILD

    BUILD, 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_idstrrequired

    App id from list_apps.

  • force_buildbooldefault False

    True 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_idstrrequired

    App id from list_apps.

  • specdictrequired

    The full app spec, as get_app_spec returned it with your changes.

  • update_all_source_versionsbooldefault False

    True 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 | Nonedefault None

    Only 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_idstrrequired

    Database 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,
)
  • pageintdefault 1

    Page number, starting at 1.

  • per_pageintdefault 50

    Items 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_idstrrequired

    Cluster 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,
)
  • pageintdefault 1

    Page number, starting at 1.

  • per_pageintdefault 50

    Items 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="…",
)
  • domainstrrequired

    Domain name as DigitalOcean DNS lists it, such as example.com.

  • typestr | Nonedefault None

    Only records of this type, such as A, CNAME, MX or TXT.

  • namestr | Nonedefault None

    Only records with this full name, such as www.example.com.

  • pageintdefault 1

    Page number, starting at 1.

  • per_pageintdefault 50

    Items 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="…",
)
  • domainstrrequired

    Domain name as DigitalOcean DNS lists it, such as example.com.

  • typestrrequired

    Record type: A, AAAA, CAA, CNAME, MX, NS, SOA, SRV or TXT.

  • namestr | Nonedefault None

    Host name relative to the domain, such as www, or @ for the domain itself.

  • datastr | Nonedefault None

    Record value: an IP address for A and AAAA, a host name for CNAME, MX, NS and SRV, or the text for TXT.

  • priorityint | Nonedefault None

    Priority for MX and SRV records.

  • portint | Nonedefault None

    Port for SRV records.

  • ttlint | Nonedefault None

    Time to live in seconds.

  • weightint | Nonedefault None

    Weight for SRV records.

  • flagsint | Nonedefault None

    Flags for CAA records, usually 0.

  • tagstr | Nonedefault None

    Tag 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,
)
  • domainstrrequired

    Domain name as DigitalOcean DNS lists it, such as example.com.

  • record_idintrequired

    Record id from list_domain_records.

  • typestr | Nonedefault None

    New record type. Usually left out.

  • namestr | Nonedefault None

    Host name relative to the domain, such as www, or @ for the domain itself.

  • datastr | Nonedefault None

    Record value: an IP address for A and AAAA, a host name for CNAME, MX, NS and SRV, or the text for TXT.

  • priorityint | Nonedefault None

    Priority for MX and SRV records.

  • portint | Nonedefault None

    Port for SRV records.

  • ttlint | Nonedefault None

    Time to live in seconds.

  • weightint | Nonedefault None

    Weight for SRV records.

  • flagsint | Nonedefault None

    Flags for CAA records, usually 0.

  • tagstr | Nonedefault None

    Tag 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,
)
  • domainstrrequired

    Domain name as DigitalOcean DNS lists it, such as example.com.

  • record_idintrequired

    Record 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,
)
  • pageintdefault 1

    Page number, starting at 1.

  • per_pageintdefault 50

    Items 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_idstrrequired

    Uptime 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="…",
)
  • bucketstrrequired

    Spaces bucket name.

  • prefixstr | Nonedefault None

    Only keys that start with this prefix, such as logs/2026/.

  • markerstr | Nonedefault None

    next_marker from the previous call. Listing starts after this key. Omit for the first page.

  • max_keysintdefault 1000

    Objects 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",
)
  • bucketstrrequired

    Spaces bucket name.

  • keystrrequired

    Object key, the path inside the bucket, such as reports/2026-10.csv.

  • content_typestr | Nonedefault None

    MIME type override. Defaults to the type stored with the object, then the key's extension.

file

The 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",
)
  • bucketstrrequired

    Spaces bucket name.

  • keystrrequired

    Object key, the path inside the bucket, such as reports/2026-10.csv.

  • contentstr | Nonedefault None

    Text to upload. Pass content or file.

  • filefile | Nonedefault None

    A 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 | Nonedefault None

    MIME type to store with the object.

  • aclstrdefault private

    private 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",
)
  • bucketstrrequired

    Spaces bucket name.

  • keystrrequired

    Object 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.

Fathom​

qbash.integrations.fathom3 methods · Fathom connector
list_meetings()List recent meetings with optional date range and recorder filters.
result = qbash.integrations.fathom.list_meetings(
cursor="…",
recorded_by="…",
)
  • cursorstr | Nonedefault None

    Pagination cursor from a previous response.

  • recorded_bystr | Nonedefault None

    Recorded By (email) - filter to meetings recorded by this user.

  • created_afterstr | Nonedefault None

    Created After (ISO 8601) - earliest meeting to include.

  • created_beforestr | Nonedefault None

    Created Before (ISO 8601) - newest meeting to include.

  • teamsstr | Nonedefault None

    Teams (JSON array) - filter by team IDs.

  • meeting_typestr | Nonedefault None

    Meeting Type filter.

  • calendar_invitees_domainsstr | list | Nonedefault None

    Filter to meetings whose invitees belong to these email domains.

  • calendar_invitees_domains_typestr | Nonedefault None

    How to apply the domain filter: all, only_internal, or one_or_more_external.

  • include_transcriptbool | Nonedefault None

    Include Transcript in each result.

  • include_summarybool | Nonedefault None

    Include Summary in each result.

  • include_action_itemsbool | Nonedefault None

    Include Action Items in each result.

  • include_crm_matchesbool | Nonedefault None

    Include 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_termstrrequired

    Keyword to search in meeting titles.

  • cursorstr | Nonedefault None

    Pagination cursor from a previous response.

  • recorded_bystr | Nonedefault None

    Recorded By (email).

  • created_afterstr | Nonedefault None

    Created After (ISO 8601).

  • created_beforestr | Nonedefault None

    Created Before (ISO 8601).

  • teamsstr | Nonedefault None

    Teams (JSON array).

  • calendar_invitees_domainsstr | list | Nonedefault None

    Filter to meetings whose invitees belong to these email domains.

  • calendar_invitees_domains_typestr | Nonedefault None

    How to apply the domain filter: all, only_internal, or one_or_more_external.

  • meeting_typestr | Nonedefault None

    Meeting 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_idstrrequired

    Recording 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.

Fireflies.ai​

qbash.integrations.fireflies8 methods · Fireflies.ai connector
list_transcripts()Get one page of meeting transcripts.
result = qbash.integrations.fireflies.list_transcripts(
limit=10,
skip=0,
)
  • limitintdefault 10

    Maximum number of transcripts to return.

  • skipintdefault 0

    Number 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_idstrrequired

    Transcript 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_termstrrequired

    Keyword to find in transcripts.

  • limitintdefault 100

    Maximum number of results.

  • skipintdefault 0

    Pagination 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 | Nonedefault None

    User ID to filter analytics by.

  • start_datestr | Nonedefault None

    Start Date (ISO 8601).

  • end_datestr | Nonedefault None

    End 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_urlstrrequired

    Meeting URL (Google Meet, Zoom, Teams, etc.).

  • titlestr | Nonedefault None

    Title to assign to the meeting.

  • attendee_emailstr | Nonedefault None

    Attendee 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_idstrrequired

    Transcript ID.

  • titlestr | Nonedefault None

    New title for the meeting.

  • channel_idstr | Nonedefault None

    Channel ID to move the meeting to.

  • privacystr | Nonedefault None

    Privacy 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_idstrrequired

    Transcript 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="…",
)
  • questionstrrequired

    Question to ask the Fireflies AI.

  • transcript_idslist | Nonedefault None

    Transcript IDs to scope the question to (list of strings). Only used when creating a thread.

  • thread_idstr | Nonedefault None

    Thread 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".

GitHub​

qbash.integrations.github30 methods · GitHub connector
list_repos()List repositories accessible to the authenticated user.
result = qbash.integrations.github.list_repos(
page=0,
per_page=0,
)
  • pageint | Nonedefault None

    Page 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 | Nonedefault None

    Repositories 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • statestr | Nonedefault None

    Pull request state to filter by. One of open, closed or all. GitHub uses open when omitted.

  • sortstr | Nonedefault None

    Field to sort by. One of created, updated, popularity or long-running. GitHub uses created when omitted.

  • directionstr | Nonedefault None

    Sort order, either asc or desc. GitHub uses desc when sorting by created.

  • per_pageint | Nonedefault None

    Number of pull requests per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull request number as it appears in the pull request URL.

  • bodystrrequired

    Comment 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull request number as it appears in the pull request URL.

  • per_pageint | Nonedefault None

    Number of comments per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull request number as it appears in the pull request URL.

  • per_pageint | Nonedefault None

    Number of files per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pathstrrequired

    Path to the file or directory within the repository, for example lib/app/router.ex.

  • refstr | Nonedefault None

    Branch 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • shastr | Nonedefault None

    Commit SHA or branch name to start listing from. The repository default branch is used when omitted.

  • pathstr | Nonedefault None

    Only return commits that touch this file or directory path.

  • authorstr | Nonedefault None

    GitHub username or email address to filter commits by.

  • sincestr | Nonedefault None

    ISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only commits after this time are returned.

  • untilstr | Nonedefault None

    ISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only commits before this time are returned.

  • per_pageint | Nonedefault None

    Number of commits per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • shastrrequired

    Commit 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • basestrrequired

    Commit SHA, branch name or tag to compare from. Branch names containing slashes are accepted.

  • headstrrequired

    Commit 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",
)
  • querystrrequired

    Code search query. Supports qualifiers such as repo:owner/name, path:, language: and filename:.

  • sortstr | Nonedefault None

    Field to sort by. The only accepted value is indexed, which orders by when the file was last indexed. Omit for best match ordering.

  • orderstr | Nonedefault None

    Sort order, either asc or desc. Only applies when sort is set.

  • per_pageint | Nonedefault None

    Number of results per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • issue_numberintrequired

    Issue 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • statestr | Nonedefault None

    Issue state to filter by. One of open, closed or all. GitHub uses open when omitted.

  • labelsstr | list[str] | Nonedefault None

    Label 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 | Nonedefault None

    Login of the assigned user. Pass none for issues with no assignee or * for issues with any assignee.

  • creatorstr | Nonedefault None

    Login of the user who opened the issue.

  • mentionedstr | Nonedefault None

    Login of a user mentioned in the issue.

  • milestoneint | str | Nonedefault None

    Milestone number, as an integer or a string. Pass none for issues with no milestone or * for issues with any milestone.

  • sincestr | Nonedefault None

    ISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only issues updated at or after this time are returned.

  • sortstr | Nonedefault None

    Field to sort by. One of created, updated or comments. GitHub uses created when omitted.

  • directionstr | Nonedefault None

    Sort order, either asc or desc. GitHub uses desc when omitted.

  • include_pull_requestsbooldefault False

    Keep pull requests in the result. Each pull request carries a pull_request key.

  • per_pageint | Nonedefault None

    Number of items read from GitHub per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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",
)
  • querystrrequired

    Search 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 | Nonedefault None

    Field to sort by, for example created, updated, comments, reactions or interactions. Omit for best match ordering.

  • orderstr | Nonedefault None

    Sort order, either asc or desc. Only applies when sort is set. GitHub uses desc when omitted.

  • per_pageint | Nonedefault None

    Number of results per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • titlestrrequired

    Issue title.

  • bodystr | Nonedefault None

    Issue description. GitHub renders it as Markdown.

  • labelslist[str] | Nonedefault None

    Label names to apply. list_labels returns the names a repository has.

  • assigneeslist[str] | Nonedefault None

    Logins of the users to assign.

  • milestoneint | Nonedefault None

    Milestone 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • issue_numberintrequired

    Issue number as it appears in the issue URL.

  • titlestr | Nonedefault None

    New issue title.

  • bodystr | Nonedefault None

    New issue description. GitHub renders it as Markdown.

  • statestr | Nonedefault None

    Either open or closed.

  • state_reasonstr | Nonedefault None

    Why the state changed. One of completed, not_planned, duplicate or reopened.

  • labelslist[str] | Nonedefault None

    Label names that replace every label on the issue. Pass an empty list to remove them all.

  • assigneeslist[str] | Nonedefault None

    Logins that replace every assignee on the issue. Pass an empty list to remove them all.

  • milestoneint | Nonedefault None

    Milestone 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • issue_numberintrequired

    Issue or pull request number as it appears in the URL.

  • sincestr | Nonedefault None

    ISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only comments updated at or after this time are returned.

  • per_pageint | Nonedefault None

    Number of comments per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • issue_numberintrequired

    Issue or pull request number as it appears in the URL.

  • bodystrrequired

    Comment 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • per_pageint | Nonedefault None

    Number of labels per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • protectedbool | Nonedefault None

    True returns only protected branches and False only unprotected ones. Every branch is returned when omitted.

  • per_pageint | Nonedefault None

    Number of branches per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull request number as it appears in the pull request URL.

  • per_pageint | Nonedefault None

    Number of reviews per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull request number as it appears in the pull request URL.

  • sortstr | Nonedefault None

    Field to sort by, either created or updated. GitHub uses created when omitted.

  • directionstr | Nonedefault None

    Sort order, either asc or desc. Only applies when sort is set.

  • sincestr | Nonedefault None

    ISO 8601 timestamp such as 2024-01-31T00:00:00Z. Only comments updated at or after this time are returned.

  • per_pageint | Nonedefault None

    Number of comments per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • refstrrequired

    Commit SHA, branch name or tag. Branch names containing slashes are accepted.

  • check_namestr | Nonedefault None

    Only return check runs with this name.

  • statusstr | Nonedefault None

    Only return check runs in this status. One of queued, in_progress or completed.

  • filterstr | Nonedefault None

    latest returns the most recent run of each check and all returns every run. GitHub uses latest when omitted.

  • per_pageint | Nonedefault None

    Number of check runs per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • refstrrequired

    Commit SHA, branch name or tag. Branch names containing slashes are accepted.

  • per_pageint | Nonedefault None

    Number of statuses per page, maximum 100. GitHub uses 30 when omitted.

  • pageint | Nonedefault None

    Page 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • titlestrrequired

    Pull request title.

  • headstrrequired

    Branch holding the changes. For a branch in a fork, use owner:branch.

  • head_repostr | Nonedefault None

    Name of the fork holding head, without the owner. GitHub requires it when the fork and the base repository belong to the same organization.

  • basestrrequired

    Branch the changes should be merged into, for example main.

  • bodystr | Nonedefault None

    Pull request description. GitHub renders it as Markdown.

  • draftbool | Nonedefault None

    True opens the pull request as a draft.

  • maintainer_can_modifybool | Nonedefault None

    True 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull request number as it appears in the pull request URL.

  • titlestr | Nonedefault None

    New pull request title.

  • bodystr | Nonedefault None

    New pull request description. GitHub renders it as Markdown.

  • statestr | Nonedefault None

    Either open or closed.

  • basestr | Nonedefault None

    Branch 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,
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull request number as it appears in the pull request URL.

  • merge_methodstr | Nonedefault None

    One of merge, squash or rebase. GitHub uses merge when omitted.

  • commit_titlestr | Nonedefault None

    Title of the merge or squash commit. GitHub writes one when omitted.

  • commit_messagestr | Nonedefault None

    Extra detail appended to the merge or squash commit message.

  • shastr | Nonedefault None

    Head 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull request number as it appears in the pull request URL.

  • eventstrrequired

    One of APPROVE, REQUEST_CHANGES or COMMENT.

  • bodystr | Nonedefault None

    Review summary. Required for REQUEST_CHANGES and COMMENT. GitHub renders it as Markdown.

  • commentslist[dict] | Nonedefault None

    Inline comments to attach to lines of the diff.

    • pathstrrequired

      File path relative to the repository root.

    • bodystrrequired

      Comment text.

    • lineintrequired

      Line number in the file the comment applies to. For a range, the last line.

    • sidestr | Nonedefault None

      RIGHT for added or unchanged lines, LEFT for removed ones. RIGHT is sent when omitted.

    • start_lineint | Nonedefault None

      First line of a multi-line comment.

    • start_sidestr | Nonedefault None

      Side of start_line, either LEFT or RIGHT. Defaults to the comment's side when start_line is set.

  • commit_idstr | Nonedefault None

    SHA 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="…",
)
  • ownerstrrequired

    Repository owner, either a user or an organisation login.

  • repostrrequired

    Repository name without the owner prefix.

  • pr_numberintrequired

    Pull request number as it appears in the pull request URL.

  • comment_idintrequired

    Id of the inline review comment to answer.

  • bodystrrequired

    Reply 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.

Gmail​

qbash.integrations.gmail17 methods · Gmail connector
get_email()Fetch a Gmail message by ID.
result = qbash.integrations.gmail.get_email(
email_id={"key": "value"},
)
  • email_idstr | dict | listrequired

    Gmail 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 string

email 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 | Nonedefault None

    Gmail search query, for example from:user@example.com is:unread. Omit to list messages unfiltered.

  • max_resultsintdefault 10

    Maximum number of messages to return in one page.

  • label_idsstr | list | Nonedefault None

    Label IDs to restrict the search to. Accepts a list or a comma-separated string.

  • include_spam_trashbooldefault False

    Include messages in Spam and Trash.

  • page_tokenstr | Nonedefault None

    Page token from a previous response, returned there as nextPageToken.

  • fetch_fullbooldefault False

    Fetch 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"},
)
  • attachmentdictrequired

    Attachment object from a get-email result. It must have message_id or email_id, attachment_id, filename and mime_type.

file

The 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"},
)
  • emaildictrequired

    Email object from a get-email result. It must have an id field and an attachments list.

list[file], or [] when the email has no attachments

Each 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_idstrrequired

    Gmail message ID to modify.

  • add_label_idsstr | list | Nonedefault None

    Label IDs or names to add. Accepts a list or a comma-separated string.

  • remove_label_idsstr | list | Nonedefault None

    Label 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_idstrrequired

    Gmail message ID to label.

  • add_label_idsstr | listrequired

    Label 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_idstrrequired

    Gmail message ID to unlabel.

  • remove_label_idsstr | listrequired

    Label 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_idstrrequired

    Gmail 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_idstrrequired

    Gmail 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_idstrrequired

    Gmail 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_idstrrequired

    Gmail 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_idstrrequired

    Gmail 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_idstrrequired

    Gmail 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_namestrrequired

    Google Cloud Pub/Sub topic to publish notifications to, in the form projects/PROJECT_ID/topics/TOPIC_NAME.

  • label_idsstr | list | Nonedefault None

    Label 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_idstrrequired

    Start history ID from a previous response or watch notification.

  • label_idstr | Nonedefault None

    Label 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_idstrrequired

    History ID to poll from, received in a watch notification payload.

  • label_idstr | Nonedefault None

    Label ID to restrict polling to, for example INBOX.

  • max_messagesintdefault 500

    Safety ceiling on messages fetched in one poll.

  • max_api_callsintdefault 10

    Safety 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 hit

messages 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.

GoDaddy​

qbash.integrations.godaddy4 methods · GoDaddy connector
check_availability()Check if a domain name is available to register.
result = qbash.integrations.godaddy.check_availability(
domain="…",
)
  • domainstrrequired

    Domain 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=[...],
)
  • domainslistrequired

    List 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",
)
  • querystrrequired

    Keyword or phrase to generate domain suggestions for.

  • limitint | Nonedefault 20

    Number of suggestions to return (max 100, default 20).

  • tldslist | Nonedefault None

    Filter 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.

Gong.io​

qbash.integrations.gong9 methods · Gong.io connector
transcript()Fetch call transcripts from Gong.
result = qbash.integrations.gong.transcript(
dateFrom="…",
dateTo="…",
)
  • dateFromstr | Nonedefault None

    ISO 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 | Nonedefault None

    ISO 8601 end datetime to filter calls.

  • workspaceIdstr | Nonedefault None

    Gong workspace ID to filter calls by workspace.

  • callIdslist | Nonedefault None

    Specific 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 | Nonedefault 100

    Maximum 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 own

The 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_idstrrequired

    Gong 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_id

The 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 | Nonedefault None

    ISO 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 | Nonedefault None

    ISO 8601 end datetime to filter calls.

  • workspaceIdstr | Nonedefault None

    Gong workspace ID to filter calls by workspace.

  • cursorstr | Nonedefault None

    Cursor to start the walk from, taken from a Gong response obtained elsewhere. No continuation cursor is returned by this mode.

  • limitint | str | Nonedefault 100

    Maximum 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 own

The 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 | Nonedefault None

    Pagination cursor from a previous response, found under records.cursor.

  • include_avatarsbool | Nonedefault False

    Include avatar URLs in the returned user records.

  • limitint | str | Nonedefault 100

    Page 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_idslistrequired

    Gong call IDs to fetch highlights for. Blank entries are dropped, and a list left with nothing is an error.

  • from_date_timestr | Nonedefault None

    ISO 8601 start datetime filter applied alongside the call IDs.

  • to_date_timestr | Nonedefault None

    ISO 8601 end datetime filter applied alongside the call IDs.

list[dict] of Gong call objects, whose per record keys are Gong's own

The 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 | Nonedefault None

    Start date for filtering calls (ISO 8601).

  • call_to_datestr | Nonedefault None

    End date for filtering calls (ISO 8601).

  • reviewed_user_idslist | Nonedefault None

    List of user IDs whose scorecards to retrieve.

  • scorecard_idslist | Nonedefault None

    List of scorecard IDs to filter by.

  • cursorstr | Nonedefault None

    Pagination cursor returned by a previous response.

{"scorecards": [], "records": {"totalRecords": 0}} when Gong reports no results, or dict holding Gong's own scorecards body otherwise

The 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 | Nonedefault None

    Gong 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_idstrrequired

    ID of the library folder to fetch content from, taken from list_library_folders. A blank value is an error.

  • cursorstr | Nonedefault None

    Pagination 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 | Nonedefault None

    Gong 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.

Google Ads​

qbash.integrations.google_ads25 methods · Google Ads connector
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_accountsintdefault 10

    Accessible 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • querystrrequired

    GAQL, 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 | Nonedefault None

    Google next_page_token from an uncut page.

  • max_rowsintdefault 1000

    Maximum returned rows, 1 to 10000.

  • include_summary_rowbooldefault False

    Include Google summary row.

  • return_total_results_countbooldefault False

    Include 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 | Nonedefault None

    Resource name such as campaign, or provide fields.

  • fieldslist[str]default []

    Specific field names.

  • include_selectable_withbooldefault False

    Include 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    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. 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_failurebooldefault False

    True applies valid operations and returns per-item errors.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    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. campaign_budget: Create {name: Daily, amount_micros: 50000000, delivery_method: STANDARD, explicitly_shared: false}. Update {amount_micros: 80000000}.

  • partial_failurebooldefault False

    True applies valid operations and returns per-item errors.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    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. ad_group: Create {name: Core, campaign: customers/ID/campaigns/1, type: SEARCH_STANDARD, cpc_bid_micros: 2000000}. Update {status: PAUSED}.

  • partial_failurebooldefault False

    True applies valid operations and returns per-item errors.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    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. 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_failurebooldefault False

    True applies valid operations and returns per-item errors.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    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. 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_failurebooldefault False

    True applies valid operations and returns per-item errors.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    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. 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_failurebooldefault False

    True applies valid operations and returns per-item errors.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    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. 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_failurebooldefault False

    True applies valid operations and returns per-item errors.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    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. conversion_action: Create {name: Signup, type: UPLOAD_CLICKS, category: SIGNUP, status: ENABLED}. Update {primary_for_goal: false}.

  • partial_failurebooldefault False

    True applies valid operations and returns per-item errors.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    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. 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_failurebooldefault False

    True applies valid operations and returns per-item errors.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top 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_failurebooldefault False

    True applies valid operations and returns per-item errors. Cannot be true when creating experiment arms.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return mutable resource objects alongside resource names.

  • actionstrdefault "mutate"

    mutate, schedule, end, promote or graduate.

  • experimentstr | Nonedefault None

    customers/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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • mutate_operationslist[dict]required

    1 to 1000 raw operation objects in camelCase or snake_case. Negative IDs can reference previous creates.

  • partial_failurebooldefault False

    True applies valid operations and returns per-item errors. Cannot be true when creating experiment arms.

  • read_onlybooldefault False

    True validates with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • namestrrequired

    Campaign name.

  • daily_budget_microsintrequired

    Positive daily budget in micros.

  • biddingdictrequired

    One strategy, for example manual_cpc: {} or maximize_conversions: {}.

  • ad_groupslist[dict]required

    Named 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_onlybooldefault False

    True 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • conversionslist[dict]required

    conversion_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_onlybooldefault False

    True 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • operationslist[dict]required

    1 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_failurebooldefault False

    True 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_idstrrequired

    10 digit Google Ads customer ID. Dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Top manager ID from list_customers when accessing client accounts.

  • resource_nameslist[str]required

    1 to 100 recommendation resource names to dismiss.

  • partial_failurebooldefault False

    True 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_idstrrequired

    10-digit advertiser account ID; dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Manager ID from list_customers for manager access.

  • languagestr | Nonedefault None

    languageConstants/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_keywordsbooldefault False

    Include adult keyword results.

  • historical_metrics_optionsdict | Nonedefault None

    include_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 | Nonedefault None

    Use {aggregate_metric_types: [DEVICE]} for device-segmented searches.

  • keywordslist[str]default []

    Up to 20 seed keywords. May combine with url; exclusive with site.

  • urlstr | Nonedefault None

    HTTP(S) page URL to generate ideas from. May combine with keywords.

  • sitestr | Nonedefault None

    Domain such as qbash.com; exclusive with keywords and url.

  • page_sizeintdefault 1000

    Requested and returned results per page, 1 to 10000. A locally cut page suppresses its cursor.

  • page_tokenstr | Nonedefault None

    next_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_idstrrequired

    10-digit advertiser account ID; dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Manager ID from list_customers for manager access.

  • keywordslist[str]required

    1 to 10000 keywords to evaluate.

  • languagestr | Nonedefault None

    languageConstants/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_keywordsbooldefault False

    Include adult keyword results.

  • historical_metrics_optionsdict | Nonedefault None

    include_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 | Nonedefault None

    Use {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_idstrrequired

    10-digit advertiser account ID; dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Manager ID from list_customers for manager access.

  • keywordslist[str]required

    1 to 10000 keywords to group.

  • ad_groupslist[str]required

    1 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_idstrrequired

    10-digit advertiser account ID; dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Manager ID from list_customers for manager access.

  • campaigndictrequired

    Inline 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 | Nonedefault None

    {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 | Nonedefault None

    Optional 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 | Nonedefault None

    Restrict 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_idstrrequired

    10-digit advertiser account ID; dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Manager ID from list_customers for manager access.

  • page_tokenstr | Nonedefault None

    Google next_page_token from an uncut page.

  • max_rowsintdefault 1000

    Maximum 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_idstrrequired

    10-digit advertiser account ID; dashes are stripped.

  • login_customer_idstr | Nonedefault None

    Manager ID from list_customers for manager access.

  • operationslist[dict]required

    1 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_failurebooldefault False

    Apply valid operations and return per-item errors. Keep false for dependent creates using temporary resource names.

  • read_onlybooldefault False

    Validate with Google without applying changes.

  • return_resourcesbooldefault False

    Return 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.

Google Analytics​

qbash.integrations.google_analytics6 methods · Google Analytics connector
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_sizeintdefault 50

    Maximum account summaries to return, from 1 through 200.

  • page_tokenstr | Nonedefault None

    Token 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_idstrrequired

    Numeric GA4 property ID, with or without the properties/ prefix.

  • kindstrdefault "all"

    Metadata group: all, dimensions, metrics, or comparisons.

  • searchstr | Nonedefault None

    Optional 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_idstrrequired

    Numeric 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_idstrrequired

    Numeric GA4 property ID, with or without the properties/ prefix.

  • date_rangeslist[dict]required

    One to four date ranges.

    • start_datestrrequired

      Inclusive YYYY-MM-DD date or a GA4 relative date such as 28daysAgo.

    • end_datestrrequired

      Inclusive YYYY-MM-DD date or a GA4 relative date such as yesterday.

    • namestr | Nonedefault None

      Optional range name returned in report metadata.

  • dimensionslist[str]default []

    Up to 9 GA4 dimension API names.

  • metricslist[str]required

    From 1 through 10 GA4 metric API names.

  • dimension_filterslist[dict]default []

    Dimension filters combined by filter_logic.

    • field_namestrrequired

      Dimension API name, for example pagePath or sessionDefaultChannelGroup.

    • operatorstrdefault "exact"

      exact, begins_with, ends_with, contains, full_regexp, partial_regexp, in_list, or empty.

    • valuestr | Nonedefault None

      Single value for every operator except in_list and empty.

    • valueslist[str]default []

      Values for the in_list operator.

    • case_sensitivebooldefault False

      Whether string matching is case-sensitive.

  • metric_filterslist[dict]default []

    Post-aggregation metric filters combined by filter_logic.

    • field_namestrrequired

      Metric API name, for example sessions or activeUsers.

    • operatorstrdefault "equal"

      equal, less_than, less_than_or_equal, greater_than, or greater_than_or_equal.

    • valueint | floatrequired

      Numeric comparison value.

  • filter_logicstrdefault "and"

    Combine each filter list with and or or.

  • return_property_quotabooldefault False

    Include the property's current Data API quota state.

  • order_byslist[dict]default []

    Report ordering rules.

    • field_namestrrequired

      Dimension or metric API name to sort by.

    • typestrdefault "metric"

      Sort target type: metric or dimension.

    • descendingbooldefault False

      Sort 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.

  • limitintdefault 10000

    Maximum rows to return, from 1 through 250000.

  • offsetintdefault 0

    Zero-based row offset for pagination.

  • currency_codestr | Nonedefault None

    Optional ISO 4217 currency code.

  • keep_empty_rowsbooldefault False

    Keep 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_idstrrequired

    Numeric 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_agointrequired

      Start of the realtime range in minutes ago.

    • end_minutes_agointrequired

      End of the realtime range in minutes ago.

    • namestr | Nonedefault None

      Optional range name.

  • dimensionslist[str]default []

    GA4 realtime dimension API names.

  • metricslist[str]required

    One or more GA4 realtime metric API names.

  • dimension_filterslist[dict]default []

    Dimension filters combined by filter_logic.

    • field_namestrrequired

      Dimension API name, for example pagePath or sessionDefaultChannelGroup.

    • operatorstrdefault "exact"

      exact, begins_with, ends_with, contains, full_regexp, partial_regexp, in_list, or empty.

    • valuestr | Nonedefault None

      Single value for every operator except in_list and empty.

    • valueslist[str]default []

      Values for the in_list operator.

    • case_sensitivebooldefault False

      Whether string matching is case-sensitive.

  • metric_filterslist[dict]default []

    Post-aggregation metric filters combined by filter_logic.

    • field_namestrrequired

      Metric API name, for example sessions or activeUsers.

    • operatorstrdefault "equal"

      equal, less_than, less_than_or_equal, greater_than, or greater_than_or_equal.

    • valueint | floatrequired

      Numeric comparison value.

  • filter_logicstrdefault "and"

    Combine each filter list with and or or.

  • return_property_quotabooldefault False

    Include the property's current Data API quota state.

  • order_byslist[dict]default []

    Realtime report ordering rules.

    • field_namestrrequired

      Dimension or metric API name to sort by.

    • typestrdefault "metric"

      Sort target type: metric or dimension.

    • descendingbooldefault False

      Sort 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.

  • limitintdefault 10000

    Maximum 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_idstrrequired

    Numeric GA4 property ID, with or without the properties/ prefix.

  • date_rangeslist[dict]required

    One to four date ranges.

    • start_datestrrequired

      Inclusive YYYY-MM-DD date or a GA4 relative date such as 28daysAgo.

    • end_datestrrequired

      Inclusive YYYY-MM-DD date or a GA4 relative date such as yesterday.

    • namestr | Nonedefault None

      Optional range name returned in report metadata.

  • pivotslist[dict]required

    One or more pivot axes covering every requested dimension exactly once.

    • field_nameslist[str]required

      Dimensions shown by this pivot. A dimension may belong to only one pivot.

    • offsetintdefault 0

      Zero-based starting row or column for this pivot.

    • limitintdefault 100

      Maximum 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_namestrrequired

        Dimension or metric API name to sort by.

      • typestrdefault "metric"

        Sort target type: metric or dimension.

      • descendingbooldefault False

        Sort 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]required

    From 1 through 10 GA4 metric API names.

  • dimension_filterslist[dict]default []

    Dimension filters combined by filter_logic.

    • field_namestrrequired

      Dimension API name, for example pagePath or sessionDefaultChannelGroup.

    • operatorstrdefault "exact"

      exact, begins_with, ends_with, contains, full_regexp, partial_regexp, in_list, or empty.

    • valuestr | Nonedefault None

      Single value for every operator except in_list and empty.

    • valueslist[str]default []

      Values for the in_list operator.

    • case_sensitivebooldefault False

      Whether string matching is case-sensitive.

  • metric_filterslist[dict]default []

    Post-aggregation metric filters combined by filter_logic.

    • field_namestrrequired

      Metric API name, for example sessions or activeUsers.

    • operatorstrdefault "equal"

      equal, less_than, less_than_or_equal, greater_than, or greater_than_or_equal.

    • valueint | floatrequired

      Numeric comparison value.

  • filter_logicstrdefault "and"

    Combine each filter list with and or or.

  • return_property_quotabooldefault False

    Include the property's current Data API quota state.

  • currency_codestr | Nonedefault None

    Optional ISO 4217 currency code.

  • keep_empty_rowsbooldefault False

    Keep 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.

Google Drive​

qbash.integrations.google_drive14 methods · Google Drive connector
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 | Nonedefault None

    Name of the folder to find. Omit to list every folder in scope. Not used for a by_id lookup.

  • parent_idstr | Nonedefault None

    Parent folder ID to search within. Omit to search every accessible folder, including shared ones.

  • exact_matchbooldefault False

    Match 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 | Nonedefault None

    Folder ID to look up, required when search_type is by_id.

  • drive_idstr | Nonedefault None

    Shared Drive ID for targeted queries. Improves reliability when working with Shared Drives.

  • page_sizeint | Nonedefault None

    Maximum number of results to return per page (1-1000, default 100).

  • page_tokenstr | Nonedefault None

    Pagination 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_id

Each 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="…",
)
  • namestrrequired

    Name of the folder to create.

  • parent_idstr | Nonedefault None

    Parent 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="…",
)
  • namestrrequired

    Name of the folder to delete.

  • parent_idstr | Nonedefault None

    Parent folder ID to search within. Omit to search every accessible folder, including shared ones.

  • exact_matchbooldefault True

    Match the name exactly. False deletes every folder whose name contains the value.

  • drive_idstr | Nonedefault None

    Shared 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 | Nonedefault None

    Folder ID to list files in. Omit to search every accessible folder, including shared ones.

  • querystr | Nonedefault None

    Text 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_matchbooldefault False

    Match the query exactly. False matches any file whose name contains the value.

  • drive_querystr | Nonedefault None

    A 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 | Nonedefault None

    Shared Drive ID for targeted queries. Improves reliability when working with Shared Drives.

  • page_sizeint | Nonedefault None

    Maximum number of results to return per page (1-1000, default 100).

  • page_tokenstr | Nonedefault None

    Pagination 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_namestrrequired

    Exact name of the file to delete.

  • parent_idstr | Nonedefault None

    Folder ID to search within. Omit to search every accessible folder, including shared ones.

  • drive_idstr | Nonedefault None

    Shared 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 matched

Matches 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_namestrrequired

    Exact name of the source file to copy.

  • destination_file_namestrrequired

    Name for the copied file at the destination.

  • source_folder_idstr | Nonedefault None

    Folder ID of the source file. Omit, or pass root, to search every accessible folder.

  • destination_folder_idstr | Nonedefault None

    Folder ID for the copy. Defaults to the Drive root.

  • drive_idstr | Nonedefault None

    Shared 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 matched

Matches 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_namestrrequired

    Exact name of the file to move.

  • source_folder_idstr | Nonedefault None

    Current folder ID of the file. Omit, or pass root, to search every accessible folder.

  • destination_folder_idstr | Nonedefault None

    Target folder ID. Defaults to the Drive root.

  • destination_file_namestr | Nonedefault None

    New name for the file at the destination. Defaults to the current name.

  • drive_idstr | Nonedefault None

    Shared 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 matched

Matches 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 | Nonedefault None

    Google Drive file ID to download. Give either file_id or file_name.

  • file_namestr | Nonedefault None

    Exact file name to look up and download. Give either file_id or file_name.

  • parent_idstr | Nonedefault None

    Folder 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 | Nonedefault None

    MIME 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 matches

Stores 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_idstrrequired

    Google Drive ID of the file to update.

  • namestr | Nonedefault None

    New file name.

  • add_parentsstr | Nonedefault None

    Folder 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] | Nonedefault None

    Folder ID, or a list of them, to take the file out of.

  • propertiesdict | Nonedefault None

    String keys and values to set on the file, visible to every app. A value of None deletes that key.

  • app_propertiesdict | Nonedefault None

    String keys and values visible only to this app. Same rules as properties.

  • descriptionstr | Nonedefault None

    New 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_namestrrequired

    Name of the file to create.

  • create_typestrdefault "content"

    Creation type: content, upload, google_doc, or google_sheet.

  • contentstr | Nonedefault None

    Text content for the file. Required when create_type is content, optional for google_doc and google_sheet.

  • parent_idstr | Nonedefault None

    Parent 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 | Nonedefault None

    Base64-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]required

    File 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 | Nonedefault None

    Parent 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 list

Accepts 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_idstrrequired

    Google Doc ID or docs.google.com/document/d/ URL of the template.

  • output_namestrrequired

    Name for the new document.

  • valuesdictrequired

    Placeholder 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 | Nonedefault None

    Folder 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_idstrrequired

    Google Doc ID or docs.google.com/document/d/ URL.

  • fieldsstr | Nonedefault None

    Docs 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_idstrrequired

    Google Doc ID or docs.google.com/document/d/ URL.

  • requestslist[dict]required

    One to 100 Google Docs API batchUpdate request objects.

  • required_revision_idstr | Nonedefault None

    Revision 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.

Google Maps​

qbash.integrations.google_maps6 methods · Google Maps connector
geocode()Convert an address, city/state, or zip code to lat/lng coordinates.
result = qbash.integrations.google_maps.geocode(
address="…",
)
  • addressstrrequired

    Address, 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="…",
)
  • latstrrequired

    Latitude coordinate.

  • lngstrrequired

    Longitude 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="…",
)
  • originstrrequired

    Origin address or lat,lng.

  • destinationstrrequired

    Destination 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="…",
)
  • latstrrequired

    Latitude of the search centre.

  • lngstrrequired

    Longitude of the search centre.

  • radiusintdefault 1000

    Search radius in metres.

  • typestr | Nonedefault None

    Place type to filter by (e.g. restaurant).

  • keywordstr | Nonedefault None

    Keyword to filter results.

  • max_resultsintdefault 20

    Maximum 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_idstrrequired

    Google Maps place_id.

  • fieldsstr | Nonedefault None

    Comma-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="…",
)
  • latstrrequired

    Latitude coordinate.

  • lngstrrequired

    Longitude coordinate.

  • timestampint | Nonedefault None

    Unix 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 connector
list_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_urlstrrequired

    Exact 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_urlstrrequired

    Exact URL-prefix or sc-domain property identifier from list_sites.

  • start_datestrrequired

    Inclusive start date in YYYY-MM-DD format.

  • end_datestrrequired

    Inclusive 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.

    • dimensionstrrequired

      Dimension to filter.

    • operatorstrdefault "equals"

      One of contains, equals, notContains, notEquals, includingRegex, or excludingRegex.

    • expressionstrrequired

      Filter 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_limitintdefault 1000

    Rows per page, from 1 through 25000.

  • start_rowintdefault 0

    Zero-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_urlstrrequired

    Exact URL-prefix or sc-domain property identifier from list_sites.

  • inspection_urlstrrequired

    Absolute 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_urlstrrequired

    Exact URL-prefix or sc-domain property identifier from list_sites.

  • sitemap_indexstr | Nonedefault None

    Optional 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_urlstrrequired

    Exact URL-prefix or sc-domain property identifier from list_sites.

  • sitemap_urlstrrequired

    Absolute 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.

Hacker News​

qbash.integrations.hackernews5 methods · Hacker News connector
search()Search Hacker News by relevance.
result = qbash.integrations.hackernews.search(
query="search terms",
tags="…",
)
  • querystr | Nonedefault None

    Search query text.

  • tagsstr | Nonedefault None

    HN/Algolia tags such as story, comment, front_page, or author_pg.

  • numeric_filtersstr | list | Nonedefault None

    Algolia numeric filters, for example created_at_i>1781020800.

  • hits_per_pageintdefault 20

    Maximum hits per page.

  • pageintdefault 0

    Zero-based result page.

  • restrict_searchable_attributesstr | list | Nonedefault None

    Search only specific attributes such as title,url,author.

  • typo_tolerancestr | bool | Nonedefault None

    Algolia 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 | Nonedefault None

    Search query text.

  • tagsstr | Nonedefault None

    HN/Algolia tags such as story, comment, front_page, or author_pg.

  • numeric_filtersstr | list | Nonedefault None

    Algolia numeric filters, for example created_at_i>1781020800.

  • hits_per_pageintdefault 20

    Maximum hits per page.

  • pageintdefault 0

    Zero-based result page.

  • restrict_searchable_attributesstr | list | Nonedefault None

    Search only specific attributes such as title,url,author.

  • typo_tolerancestr | bool | Nonedefault None

    Algolia 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_pageintdefault 30

    Maximum front-page stories to return.

  • pageintdefault 0

    Zero-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,
)
  • idintrequired

    Hacker 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="…",
)
  • usernamestrrequired

    Hacker 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.

HubSpot​

qbash.integrations.hubspot16 methods · HubSpot connector
list_contacts()List contacts with cursor pagination.
result = qbash.integrations.hubspot.list_contacts(
limit=10,
after="…",
)
  • limitintdefault 50

    Max results per page, from 1 to 100.

  • afterstr | Nonedefault None

    Pagination cursor from paging.next.after of previous response.

  • propertiesstr | Nonedefault None

    Comma-separated property names (e.g. "email,firstname,phone"). Defaults to common contact properties.

  • archivedbooldefault False

    If 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="…",
)
  • idstrrequired

    Contact ID, or email address if id_property="email".

  • id_propertystr | Nonedefault None

    Set to "email" to look up by email address.

  • propertiesstr | Nonedefault None

    Comma-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 | Nonedefault None

    Contact email address (HubSpot's primary identifier).

  • firstnamestr | Nonedefault None

    First name.

  • lastnamestr | Nonedefault None

    Last name.

  • phonestr | Nonedefault None

    Phone number.

  • companystr | Nonedefault None

    Company name (free-text property on the contact).

  • propertiesdict | Nonedefault None

    Map of HubSpot property names to values for custom or additional standard fields.

  • company_idslist | Nonedefault None

    List 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"},
)
  • idstrrequired

    Contact ID.

  • propertiesdictrequired

    Map 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 | Nonedefault None

    HubSpot filterGroups (list of {filters: [...]} dicts). Each filter is {propertyName, operator, value}. Leave it out to search on query alone.

  • propertiesstr | Nonedefault None

    Comma-separated property names to return.

  • sortslist | Nonedefault None

    List of {propertyName, direction} sort specs.

  • querystr | Nonedefault None

    Free-text query applied across default searchable properties.

  • limitintdefault 10

    Max results per page (max 200).

  • afterstr | Nonedefault None

    Pagination 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="…",
)
  • limitintdefault 50

    Max results per page, from 1 to 100.

  • afterstr | Nonedefault None

    Pagination cursor.

  • propertiesstr | Nonedefault None

    Comma-separated property names. Defaults to common company properties.

  • archivedbooldefault False

    If 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 | Nonedefault None

    Company name.

  • domainstr | Nonedefault None

    Primary domain (e.g. acme.com).

  • industrystr | Nonedefault None

    Industry property value.

  • propertiesdict | Nonedefault None

    Map 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 | Nonedefault None

    HubSpot filterGroups list. Leave it out to search on query alone.

  • propertiesstr | Nonedefault None

    Comma-separated property names.

  • sortslist | Nonedefault None

    Sort specs.

  • querystr | Nonedefault None

    Free-text query applied across default searchable properties.

  • limitintdefault 10

    Max results per page (max 200).

  • afterstr | Nonedefault None

    Pagination 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="…",
)
  • limitintdefault 50

    Max results per page, from 1 to 100.

  • afterstr | Nonedefault None

    Pagination cursor.

  • propertiesstr | Nonedefault None

    Comma-separated property names. Defaults to common deal properties.

  • archivedbooldefault False

    If 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="…",
)
  • dealnamestrrequired

    Deal name (required by HubSpot).

  • amountfloat | Nonedefault None

    Deal amount.

  • pipelinestr | Nonedefault None

    Pipeline ID (default if omitted). Use list_pipelines to discover IDs.

  • dealstagestr | Nonedefault None

    Stage ID within the pipeline.

  • closedatestr | Nonedefault None

    Expected close date (ISO 8601, e.g. 2026-12-31).

  • propertiesdict | Nonedefault None

    Map of additional HubSpot property names to values.

  • contact_idslist | Nonedefault None

    Contact IDs to associate with this deal.

  • company_idslist | Nonedefault None

    Company 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"},
)
  • idstrrequired

    Deal ID.

  • propertiesdictrequired

    Map 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 | Nonedefault None

    HubSpot filterGroups list. Leave it out to search on query alone.

  • propertiesstr | Nonedefault None

    Comma-separated property names.

  • sortslist | Nonedefault None

    Sort specs.

  • querystr | Nonedefault None

    Free-text query applied across default searchable properties.

  • limitintdefault 10

    Max results per page (max 200).

  • afterstr | Nonedefault None

    Pagination 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="…",
)
  • bodystrrequired

    Note body text (becomes hs_note_body).

  • timestampstr | Nonedefault None

    ISO 8601 timestamp for when the note occurred. Defaults to now.

  • owner_idstr | Nonedefault None

    HubSpot owner ID for hubspot_owner_id.

  • contact_idslist | Nonedefault None

    Contact IDs to associate.

  • company_idslist | Nonedefault None

    Company IDs to associate.

  • deal_idslist | Nonedefault None

    Deal 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="…",
)
  • subjectstrrequired

    Task subject / title (becomes hs_task_subject).

  • bodystr | Nonedefault None

    Task description (becomes hs_task_body).

  • due_timestampstr | Nonedefault None

    ISO 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 | Nonedefault None

    HubSpot owner ID assigned to this task.

  • contact_idslist | Nonedefault None

    Contact IDs to associate.

  • company_idslist | Nonedefault None

    Company IDs to associate.

  • deal_idslist | Nonedefault None

    Deal 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="…",
)
  • limitintdefault 100

    Max results per page.

  • afterstr | Nonedefault None

    Pagination cursor.

  • emailstr | Nonedefault None

    Filter 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.

JIRA​

qbash.integrations.jira19 methods · JIRA connector
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_idstrrequired

    JIRA ticket ID (e.g. PROJ-123).

  • comment_limitintdefault 20

    Number of newest comments to include, from 0 to 100.

  • custom_fieldslist[str] | Nonedefault None

    Field 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 | Nonedefault None

    Start date for date_range query (YYYY-MM-DD).

  • end_datestr | Nonedefault None

    End date for date_range query (YYYY-MM-DD).

  • start_ticketstr | Nonedefault None

    First ticket ID for ticket_range query (e.g. PROJ-1).

  • end_ticketstr | Nonedefault None

    Last ticket ID for ticket_range query (e.g. PROJ-100).

  • max_resultsint | Nonedefault None

    Maximum 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_idstrrequired

    JIRA ticket ID to update (e.g. PROJ-123).

  • field_idstr | Nonedefault None

    JIRA field ID to update in the single-field form (e.g. labels, customfield_10001).

  • tag_namestr | Nonedefault None

    Value to set for field_id in the single-field form.

  • fieldsdict | Nonedefault None

    Fields to set, by field id or display name, such as {"customfield_10050": "Gold"}. Do not combine with field_id.

  • add_labelslist[str] | Nonedefault None

    Labels to add. The ticket's other labels are kept.

  • remove_labelslist[str] | Nonedefault None

    Labels to remove. The ticket's other labels are kept.

  • read_onlybooldefault False

    True 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_idstrrequired

    JIRA ticket ID to comment on (e.g. PROJ-123).

  • comment_bodystr | dictrequired

    Text of the comment, or an Atlassian Document Format document as a dict.

  • is_internalbooldefault False

    Mark the comment as internal (visible to agents only). Works on Jira Service Management requests.

  • visibilitydict | Nonedefault None

    Limit the comment to a role or group, as {"type": "role" or "group", "value": name}.

  • read_onlybooldefault False

    True 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_idstrrequired

    JIRA ticket ID to transition (e.g. PROJ-123).

  • transition_namestrdefault "Done"

    Name of the transition to apply (e.g. In Progress, Done).

  • transition_idstr | Nonedefault None

    ID of the transition to apply, from list_transitions. Do not combine with a transition_name.

  • resolution_namestr | Nonedefault None

    Optional resolution name to set when closing (e.g. Fixed, Won't Do).

  • fieldsdict | Nonedefault None

    Screen fields to set, by field id or display name.

  • commentstr | dict | Nonedefault None

    Comment to add with the transition, as text or an Atlassian Document Format dict.

  • read_onlybooldefault False

    True 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_idstrrequired

    JIRA ticket ID (e.g. PROJ-123).

  • orderstrdefault "newest"

    newest or oldest, by creation time.

  • limitintdefault 100

    Comments per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass 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_idstrrequired

    ID 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 | Nonedefault None

    Keep fields whose name or id contains this text, ignoring case.

  • custom_onlybooldefault False

    Return 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 | Nonedefault None

    A JQL query. Use it alone, without text, project or updated_within_days.

  • textstr | Nonedefault None

    Words to find in the text of tickets, matched after stemming.

  • projectstr | Nonedefault None

    Project key (e.g. PROJ) or numeric project ID to search in.

  • updated_within_daysint | Nonedefault None

    Only tickets updated within this many days, from 1 to 3650.

  • fieldslist[str] | Nonedefault None

    Extra field ids or display names to return under custom_fields.

  • limitintdefault 50

    Tickets per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass 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 | Nonedefault None

    Keep projects whose key or name matches this text.

  • creatable_onlybooldefault False

    Only projects where the user can create tickets.

  • limitintdefault 50

    Projects per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass 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="…",
)
  • projectstrrequired

    Project 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="…",
)
  • projectstrrequired

    Project key (e.g. PROJ) or numeric project ID.

  • issue_typestrrequired

    Issue 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_idstrrequired

    JIRA 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_idstrrequired

    JIRA 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]required

    Issue 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.

  • limitintdefault 100

    Page size sent to Jira, from 1 to 1000.

  • cursorstr | Nonedefault None

    Pass 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="…",
)
  • projectstrrequired

    Project key (e.g. PROJ) or numeric project ID.

  • issue_typestrrequired

    Issue type name (e.g. Bug) or numeric ID, from list_issue_types.

  • summarystrrequired

    Summary of the ticket, up to 255 characters.

  • descriptionstr | dict | Nonedefault None

    Description as text, or an Atlassian Document Format document as a dict.

  • labelslist[str] | Nonedefault None

    Labels to set. A label cannot contain a space.

  • prioritystr | Nonedefault None

    Priority name (e.g. High).

  • assignee_account_idstr | Nonedefault None

    Account ID of the assignee.

  • parentstr | Nonedefault None

    Issue key of the parent, for a subtask or a child of an epic.

  • fieldsdict | Nonedefault None

    Other fields, by field id or display name. See get_create_fields for what the project takes.

  • read_onlybooldefault False

    True 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_ticketstrrequired

    Key or numeric ID of the ticket the sentence starts with.

  • to_ticketstrrequired

    Key or numeric ID of the ticket the sentence ends with.

  • link_typestrrequired

    Link type name, outward wording or inward wording (e.g. Duplicate, duplicates, is duplicated by).

  • commentstr | Nonedefault None

    Comment to add with the link.

  • read_onlybooldefault False

    True 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_idstrrequired

    JIRA ticket ID (e.g. PROJ-123).

  • urlstrrequired

    http or https URL of the page to link to.

  • titlestrrequired

    Title shown for the link.

  • summarystr | Nonedefault None

    Short text shown under the title.

  • global_idstr | Nonedefault None

    Stable ID of the linked record, up to 255 characters. A repeat call with the same ID updates the link.

  • relationshipstr | Nonedefault None

    How the ticket relates to the page (e.g. causes, mentioned in).

  • application_namestr | Nonedefault None

    Name of the application the page belongs to (e.g. Zendesk).

  • icon_urlstr | Nonedefault None

    http or https URL of a 16 by 16 icon.

  • read_onlybooldefault False

    True 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.

Linear​

qbash.integrations.linear13 methods · Linear connector
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="…",
)
  • limitintdefault 50

    Results per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass next_cursor from the previous page.

  • include_archivedbooldefault False

    Include 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="…",
)
  • limitintdefault 50

    Results per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass next_cursor from the previous page.

  • include_archivedbooldefault False

    Include 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 | Nonedefault None

    Team UUID from list_teams. Omit to read accessible teams.

  • limitintdefault 50

    Results per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass next_cursor from the previous page.

  • include_archivedbooldefault False

    Include 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 | Nonedefault None

    Team UUID from list_teams. Omit to read accessible teams.

  • limitintdefault 50

    Results per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass next_cursor from the previous page.

  • include_archivedbooldefault False

    Include 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 | Nonedefault None

    Team UUID from list_teams. Omit to read accessible teams.

  • limitintdefault 50

    Results per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass next_cursor from the previous page.

  • include_archivedbooldefault False

    Include 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 | Nonedefault None

    Team UUID from list_teams. Omit to read accessible teams.

  • limitintdefault 50

    Results per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass next_cursor from the previous page.

  • include_archivedbooldefault False

    Include 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 | Nonedefault None

    Team UUID from list_teams. Omit to read accessible teams.

  • project_idstr | Nonedefault None

    Project UUID from list_projects.

  • assignee_idstr | Nonedefault None

    Assignee UUID from list_users or get_viewer.

  • state_typestr | Nonedefault None

    Workflow category: triage, backlog, unstarted, started, completed or canceled.

  • priorityint | Nonedefault None

    Priority: 0 none, 1 urgent, 2 high, 3 normal, 4 low.

  • updated_sincestr | Nonedefault None

    Inclusive ISO 8601 timestamp with timezone, for example 2026-10-01T00:00:00Z.

  • querystr | Nonedefault None

    Case-insensitive substring of the issue title. Does not search descriptions or comments.

  • limitintdefault 50

    Results per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass next_cursor from the previous page.

  • include_archivedbooldefault False

    Include 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_idstrrequired

    Issue 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_idstrrequired

    Issue UUID or identifier such as ENG-123.

  • limitintdefault 50

    Results per page, from 1 to 100.

  • cursorstr | Nonedefault None

    Pass next_cursor from the previous page.

  • include_archivedbooldefault False

    Include 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_idstrrequired

    Team UUID from list_teams.

  • titlestrrequired

    Issue title.

  • descriptionstr | Nonedefault None

    Issue description in Markdown.

  • assignee_idstr | Nonedefault None

    User UUID to assign the issue to.

  • project_idstr | Nonedefault None

    Project UUID to attach the issue to.

  • cycle_idstr | Nonedefault None

    Cycle UUID to attach the issue to.

  • state_idstr | Nonedefault None

    Workflow state UUID from list_workflow_states for this team.

  • priorityint | Nonedefault None

    Priority: 0 none, 1 urgent, 2 high, 3 normal, 4 low.

  • estimateint | Nonedefault None

    Nonnegative estimate using the team's estimation scale.

  • due_datestr | Nonedefault None

    Due date as YYYY-MM-DD.

  • label_idslist[str] | Nonedefault None

    Label 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_idstrrequired

    Issue UUID or identifier such as ENG-123.

  • titlestr | Nonedefault None

    Replacement issue title.

  • descriptionstr | Nonedefault None

    Issue description in Markdown.

  • assignee_idstr | Nonedefault None

    User UUID to assign the issue to.

  • project_idstr | Nonedefault None

    Project UUID to attach the issue to.

  • cycle_idstr | Nonedefault None

    Cycle UUID to attach the issue to.

  • state_idstr | Nonedefault None

    Workflow state UUID from list_workflow_states for this team.

  • priorityint | Nonedefault None

    Priority: 0 none, 1 urgent, 2 high, 3 normal, 4 low.

  • estimateint | Nonedefault None

    Nonnegative estimate using the team's estimation scale.

  • due_datestr | Nonedefault None

    Due date as YYYY-MM-DD.

  • label_idslist[str] | Nonedefault None

    Label 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_idstrrequired

    Issue UUID or identifier such as ENG-123.

  • bodystrrequired

    Comment 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.

LinkedIn​

qbash.integrations.linkedin34 methods · LinkedIn connector
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 | Nonedefault None

    Numeric LinkedIn organization ID (e.g. "1234567"). Provide this or vanity_name.

  • vanity_namestr | Nonedefault None

    LinkedIn 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 | Nonedefault None

    Full 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.

  • countintdefault 10

    Maximum number of posts to return (max 100).

  • startintdefault 0

    Pagination 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 | Nonedefault None

    A 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 | Nonedefault None

    Public URL to download the image from. Provide this or file.

  • owner_urnstr | Nonedefault None

    URN 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 | Nonedefault None

    A 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 | Nonedefault None

    Public URL to download the video from, up to 5 GB. Provide this or file.

  • owner_urnstr | Nonedefault None

    URN that owns the asset. Defaults to the authenticated user's person URN. Pass an org URN to upload for a company page.

  • wait_for_processingbooldefault True

    Poll until LinkedIn finishes processing the video. A post cannot reference a video that is still processing.

  • max_wait_secondsintdefault 60

    How 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 | Nonedefault None

    A 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 | Nonedefault None

    Public URL to download the document from. Provide this or file.

  • owner_urnstr | Nonedefault None

    URN 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="…",
)
  • textstrrequired

    Post content.

  • visibilitystrdefault "PUBLIC"

    Visibility: "PUBLIC" or "CONNECTIONS".

  • author_urnstr | Nonedefault None

    URN of the author. Defaults to the authenticated user's person URN. Pass an org URN to post as an organization.

  • media_urnstr | Nonedefault None

    Asset 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 | Nonedefault None

    Title shown with the media (mainly used for video).

  • media_typestr | Nonedefault None

    "image", "video" or "document". Inferred from media_urn when omitted; validated against it when supplied.

  • media_alt_textstr | Nonedefault None

    Alt text for screen readers. Images only.

  • article_urlstr | Nonedefault None

    URL to share as a rich article card. Required if any other article_* param is set. Cannot be combined with the media_* params.

  • article_titlestr | Nonedefault None

    Headline on the article card. LinkedIn does not scrape the URL, so without this the card renders without a title.

  • article_descriptionstr | Nonedefault None

    Description text on the article card.

  • article_thumbnail_urnstr | Nonedefault None

    Image URN from upload_image to use as the card thumbnail (e.g. "urn:li:image:...").

  • reshare_urnstr | Nonedefault None

    URN 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] | Nonedefault None

    2-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 | Nonedefault None

    Alt 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 | Nonedefault None

    Poll 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] | Nonedefault None

    2-4 poll answer options, each max 30 characters. Required with poll_question.

  • poll_durationstr | Nonedefault None

    One of "ONE_DAY", "THREE_DAYS", "SEVEN_DAYS", "FOURTEEN_DAYS". Required with poll_question.

  • target_geo_urnslist[str] | Nonedefault None

    Restrict 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] | Nonedefault None

    Restrict distribution to these seniority URNs. See target_geo_urns for facet semantics.

  • target_industrieslist[str] | Nonedefault None

    Restrict distribution to these industry URNs. See target_geo_urns for facet semantics.

  • target_job_functionslist[str] | Nonedefault None

    Restrict distribution to these job function URNs. See target_geo_urns for facet semantics.

  • target_organizationslist[str] | Nonedefault None

    Restrict distribution to followers of these organization URNs. See target_geo_urns for facet semantics.

  • target_staff_count_rangeslist[str] | Nonedefault None

    Restrict 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_urnstrrequired

    URN 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_urnstrrequired

    URN of the post to update (e.g. "urn:li:share:123").

  • textstr | Nonedefault None

    New post text (maps to LinkedIn's commentary field).

  • call_to_action_labelstr | Nonedefault None

    New call-to-action label shown on the post.

  • landing_page_urlstr | Nonedefault None

    New 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_urnstrrequired

    Organization URN (e.g. "urn:li:organization:1234567").

  • countintdefault 10

    Maximum number of results to return.

  • share_urnslist[str] | Nonedefault None

    Share 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] | Nonedefault None

    UGC 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_idstrrequired

    Numeric 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_urnstrrequired

    URN 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 | Nonedefault None

    URN 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_urnstrrequired

    URN of the post or comment to remove the reaction from.

  • actor_urnstr | Nonedefault None

    URN 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_urnstrrequired

    URN 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_urnstrrequired

    Asset 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_urnstrrequired

    URN of the post, ugcPost, or comment to comment on (e.g. "urn:li:share:123").

  • textstrrequired

    Comment text.

  • parent_comment_urnstr | Nonedefault None

    Composite 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 | Nonedefault None

    URN 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_urnstrrequired

    URN of the post, ugcPost, or comment the comment belongs to.

  • comment_idstrrequired

    ID 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_urnstrrequired

    URN of the post or ugcPost to list comments for.

  • countintdefault 10

    Maximum number of comments to return.

  • startintdefault 0

    Pagination 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_urnstrrequired

    URN 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_idstrrequired

    ID 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_urnstrrequired

    URN of the post, ugcPost, or comment the comment belongs to.

  • comment_idstrrequired

    ID of the comment to update.

  • textstrrequired

    New comment text.

  • actor_urnstr | Nonedefault None

    URN 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_urnstrrequired

    URN of the post, ugcPost, or comment the comment belongs to.

  • comment_idstrrequired

    ID of the comment to delete.

  • actor_urnstr | Nonedefault None

    URN 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_urnstrrequired

    URN 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_urnstrrequired

    URN of the post or comment to open or close comments on.

  • enabledboolrequired

    True to open comments, false to close them (destructive - see summary).

  • actor_urnstr | Nonedefault None

    URN 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]required

    Numeric 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_urnstrrequired

    Organization 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_urnstrrequired

    Organization URN whose followers to search (e.g. "urn:li:organization:1234567").

  • keywordsstr | Nonedefault None

    Name/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 | Nonedefault None

    Full 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_urnstrrequired

    Organization URN (e.g. "urn:li:organization:1234567").

  • time_granularitystr | Nonedefault None

    One 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 | Nonedefault None

    Start of the time range, epoch milliseconds. Required together with time_granularity and end_time.

  • end_timeint | Nonedefault None

    End 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 query

Reads /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_urnstrrequired

    Organization URN (e.g. "urn:li:organization:1234567").

  • time_granularitystr | Nonedefault None

    One 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 | Nonedefault None

    Start of the time range, epoch milliseconds. Required together with time_granularity and end_time.

  • end_timeint | Nonedefault None

    End 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_urnstrrequired

    UGC post URN of the video (e.g. "urn:li:ugcPost:123").

  • metricstrrequired

    One 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 | Nonedefault None

    Start of the time range, epoch milliseconds. Provide together with end_time.

  • end_timeint | Nonedefault None

    End 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 | Nonedefault None

    Start 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.

    • yearintrequired

      Four-digit year (e.g. 2026).

    • monthintrequired

      Month of the year, 1-12.

    • dayintrequired

      Day of the month, 1-31.

  • end_datestr | dict | Nonedefault None

    End of the date range (exclusive), same format as start_date. Provide together with start_date.

    • yearintrequired

      Four-digit year (e.g. 2026).

    • monthintrequired

      Month of the year, 1-12.

    • dayintrequired

      Day 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 range

Requires 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 | Nonedefault None

    A "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).

  • metricstrrequired

    One 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 | Nonedefault None

    Start 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.

    • yearintrequired

      Four-digit year (e.g. 2026).

    • monthintrequired

      Month of the year, 1-12.

    • dayintrequired

      Day of the month, 1-31.

  • end_datestr | dict | Nonedefault None

    End of the date range for a DAILY query, same format as start_date. Provide together with start_date.

    • yearintrequired

      Four-digit year (e.g. 2026).

    • monthintrequired

      Month of the year, 1-12.

    • dayintrequired

      Day 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 | Nonedefault None

    A "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.

  • metricstrrequired

    One 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 | Nonedefault None

    Start 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.

    • yearintrequired

      Four-digit year (e.g. 2026).

    • monthintrequired

      Month of the year, 1-12.

    • dayintrequired

      Day of the month, 1-31.

  • end_datestr | dict | Nonedefault None

    End of the date range for a DAILY query, same format as start_date. Provide together with start_date.

    • yearintrequired

      Four-digit year (e.g. 2026).

    • monthintrequired

      Month of the year, 1-12.

    • dayintrequired

      Day 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.

Massive​

qbash.integrations.massive13 methods · Massive connector
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="…",
)
  • tickerstrrequired

    Ticker symbol (e.g. "AAPL").

  • multiplierintrequired

    Size of the timespan multiplier (e.g. 5).

  • timespanstrrequired

    Window size: second/minute/hour/day/week/month/quarter/year.

  • from_strrequired

    Start date (YYYY-MM-DD) or unix-millis.

  • tostrrequired

    End date (YYYY-MM-DD) or unix-millis.

  • adjustedbooldefault True

    Whether bars are split-adjusted.

  • sortstrdefault None

    asc or desc by timestamp.

  • limitintdefault None

    Max 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="…",
)
  • tickerstrrequired

    Ticker symbol.

  • adjustedbooldefault True

    Split-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="…",
)
  • tickerstrrequired

    Ticker 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="…",
)
  • tickerstrrequired

    Ticker 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="…",
)
  • tickerstrrequired

    Ticker symbol.

  • datestrdefault None

    Point-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="…",
)
  • tickerstrdefault None

    Exact ticker filter.

  • ticker_gtestrdefault None

    Ticker >= value (paginate).

  • ticker_gtstrdefault None

    Ticker > value.

  • ticker_ltestrdefault None

    Ticker <= value.

  • ticker_ltstrdefault None

    Ticker < value.

  • typestrdefault None

    CS, ETF, ADRC, FUND, ... see Massive docs.

  • marketstrdefault None

    stocks, crypto, fx, otc, indices.

  • exchangestrdefault None

    MIC code (e.g. XNAS).

  • cusipstrdefault None

    CUSIP identifier.

  • cikstrdefault None

    SEC CIK identifier.

  • datestrdefault None

    Point-in-time YYYY-MM-DD.

  • searchstrdefault None

    Substring search across ticker + name.

  • activebooldefault True

    Filter active vs delisted.

  • orderstrdefault None

    asc or desc.

  • limitintdefault None

    Page size (≤1000).

  • sortstrdefault None

    Field 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="…",
)
  • tickerstrdefault None

    Ticker filter.

  • published_utc_gtestrdefault None

    Published >= ISO timestamp.

  • published_utc_gtstrdefault None

    Published > ISO timestamp.

  • published_utc_ltestrdefault None

    Published <= ISO timestamp.

  • published_utc_ltstrdefault None

    Published < ISO timestamp.

  • orderstrdefault None

    asc or desc.

  • limitintdefault None

    Page size (≤1000).

  • sortstrdefault None

    Field 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_assetstrrequired

    Underlying ticker (e.g. "AAPL").

  • strike_price_gtestrdefault None

    Strike >= value.

  • strike_price_gtstrdefault None

    Strike > value.

  • strike_price_ltestrdefault None

    Strike <= value.

  • strike_price_ltstrdefault None

    Strike < value.

  • expiration_date_gtestrdefault None

    Expiration >= YYYY-MM-DD.

  • expiration_date_gtstrdefault None

    Expiration > YYYY-MM-DD.

  • expiration_date_ltestrdefault None

    Expiration <= YYYY-MM-DD.

  • expiration_date_ltstrdefault None

    Expiration < YYYY-MM-DD.

  • contract_typestrdefault None

    call or put.

  • orderstrdefault None

    asc or desc.

  • limitintdefault None

    Page size (≤250).

  • sortstrdefault None

    Sort 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_assetstrrequired

    Underlying ticker.

  • option_contractstrrequired

    Options 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_tickerstrdefault None

    Underlying ticker filter.

  • contract_typestrdefault None

    call or put.

  • expiration_datestrdefault None

    Exact expiration YYYY-MM-DD.

  • expiration_date_gtestrdefault None

    Expiration >= YYYY-MM-DD.

  • expiration_date_gtstrdefault None

    Expiration > YYYY-MM-DD.

  • expiration_date_ltestrdefault None

    Expiration <= YYYY-MM-DD.

  • expiration_date_ltstrdefault None

    Expiration < YYYY-MM-DD.

  • strike_price_gtestrdefault None

    Strike >= value.

  • strike_price_gtstrdefault None

    Strike > value.

  • strike_price_ltestrdefault None

    Strike <= value.

  • strike_price_ltstrdefault None

    Strike < value.

  • as_ofstrdefault None

    Point-in-time YYYY-MM-DD.

  • expiredbooldefault False

    Include expired contracts.

  • orderstrdefault None

    asc or desc.

  • limitintdefault None

    Page size (≤1000).

  • sortstrdefault None

    Sort 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_classstrdefault None

    stocks, options, crypto, fx, indices.

  • localestrdefault None

    us 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="…",
)
  • datestrrequired

    Trading date YYYY-MM-DD.

  • adjustedbooldefault True

    Whether bars are split-adjusted.

  • include_otcbooldefault False

    Include 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.

Microsoft OneDrive​

qbash.integrations.microsoft_onedrive19 methods · Microsoft OneDrive connector
list_files()List files in a folder.
result = qbash.integrations.microsoft_onedrive.list_files(
folder_path="my-folder",
top=10,
)
  • folder_pathstr | Nonedefault None

    Folder path relative to the drive root, e.g. Documents (defaults to root).

  • topint | Nonedefault None

    Items per page, sent as Graph's $top (Graph's default is 200).

  • page_tokenstr | Nonedefault None

    An 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",
)
  • querystrrequired

    Search 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_pathstrrequired

    Destination path relative to the drive root, with no leading slash (e.g. Documents/report.txt).

  • contentstr | Nonedefault None

    File content to upload: plain text for text files, base64 for binary files. Pass content or file.

  • filefile | Nonedefault None

    A 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 | Nonedefault None

    MIME type (defaults to text/plain).

{"id": str, "name": str, "size": int, "webUrl": str} or {"status": "success"} when Graph answers with an empty body

The 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 | Nonedefault None

    OneDrive item ID (item_id or file_path is required).

  • file_pathstr | Nonedefault None

    File 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 | Nonedefault None

    OneDrive item ID (item_id or file_path is required).

  • file_pathstr | Nonedefault None

    File 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 | Nonedefault None

    OneDrive item ID (item_id or file_path is required).

  • file_pathstr | Nonedefault None

    Source file path (item_id or file_path is required).

  • destination_pathstr | Nonedefault None

    Destination folder path (destination_path or destination_folder_id is required).

  • destination_folder_idstr | Nonedefault None

    Destination folder ID, used in place of destination_path when given.

  • new_namestr | Nonedefault None

    Name 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 | Nonedefault None

    OneDrive item ID (item_id or file_path is required).

  • file_pathstr | Nonedefault None

    Source file path (item_id or file_path is required).

  • new_namestr | Nonedefault None

    New filename (new_name, destination_folder_id, or destination_path is required).

  • destination_folder_idstr | Nonedefault None

    Destination folder ID, used in place of destination_path when given.

  • destination_pathstr | Nonedefault None

    Destination 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_namestrrequired

    Name of the folder to create.

  • parent_pathstr | Nonedefault None

    Parent 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 | Nonedefault None

    OneDrive item ID (item_id or file_path is required).

  • file_pathstr | Nonedefault None

    File 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 | Nonedefault None

    OneDrive item ID (item_id or file_path is required).

  • file_pathstr | Nonedefault None

    File 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_namestrrequired

    File name for the new document.

  • contentstrrequired

    Document content.

  • folder_pathstr | Nonedefault None

    Folder 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 body

The 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 | Nonedefault None

    Workbook item ID (workbook_id or workbook_path is required).

  • workbook_pathstr | Nonedefault None

    Workbook 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 | Nonedefault None

    Workbook item ID (workbook_id or workbook_path is required).

  • workbook_pathstr | Nonedefault None

    Workbook file path (workbook_id or workbook_path is required).

  • worksheet_namestrrequired

    Worksheet name.

  • rangestrrequired

    Cell 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 | Nonedefault None

    Workbook item ID (workbook_id or workbook_path is required).

  • workbook_pathstr | Nonedefault None

    Workbook file path (workbook_id or workbook_path is required).

  • worksheet_namestrrequired

    Worksheet name.

  • rangestrrequired

    Cell range to write (e.g. A1:B2).

  • valueslistrequired

    2-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 | Nonedefault None

    Workbook item ID (workbook_id or workbook_path is required).

  • workbook_pathstr | Nonedefault None

    Workbook file path (workbook_id or workbook_path is required).

  • table_namestrrequired

    Table name.

  • valueslistrequired

    Row values to append.

  • worksheet_namestr | Nonedefault None

    Worksheet 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 | Nonedefault None

    Workbook item ID (workbook_id or workbook_path is required).

  • workbook_pathstr | Nonedefault None

    Workbook file path (workbook_id or workbook_path is required).

  • worksheet_namestr | Nonedefault None

    Worksheet 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 | Nonedefault None

    Workbook item ID (workbook_id or workbook_path is required).

  • workbook_pathstr | Nonedefault None

    Workbook file path (workbook_id or workbook_path is required).

  • table_namestrrequired

    Table name.

  • worksheet_namestr | Nonedefault None

    Worksheet holding the table (required for some workbook layouts).

  • topint | Nonedefault None

    Maximum number of rows to return.

  • skipint | Nonedefault None

    Number 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 | Nonedefault None

    Workbook item ID (workbook_id or workbook_path is required).

  • workbook_pathstr | Nonedefault None

    Workbook file path (workbook_id or workbook_path is required).

  • worksheet_namestrrequired

    Worksheet name.

  • cellstrrequired

    Cell 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_pathstrrequired

    Path (including filename) for the new workbook.

{"id": str, "name": str, "size": int, "webUrl": str} or {"status": "success"} when Graph answers with an empty body

The 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.

Microsoft Outlook Mail​

qbash.integrations.outlook_mail14 methods · Microsoft Outlook Mail connector
get_message()Get a specific Outlook message by ID.
result = qbash.integrations.outlook_mail.get_message(
message_id="…",
)
  • message_idstrrequired

    Outlook 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 | Nonedefault None

    OData filter expression to filter messages.

  • topint | Nonedefault 50

    Maximum number of messages to return.

  • skipint | Nonedefault 0

    Number of messages to skip before returning results.

  • has_attachmentsstr | Nonedefault None

    Filter by attachment presence. Pass with or without.

  • read_statusstr | Nonedefault None

    Filter 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 | strrequired

    Recipient email addresses, as a list or a comma-separated string.

  • subjectstrrequired

    Email subject line.

  • bodystr | Nonedefault None

    Email body content (HTML or plain text).

  • body_typestrdefault "HTML"

    Body content type, either HTML or Text (defaults to HTML).

  • cc_recipientslist | str | Nonedefault None

    CC recipient email addresses, as a list or a comma-separated string.

  • bcc_recipientslist | str | Nonedefault None

    BCC recipient email addresses, as a list or a comma-separated string.

  • attachmentslist[dict] | list[file] | Nonedefault None

    File 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 | Nonedefault None

      File name shown on the attachment. Defaults to the stored file's name, then "attachment".

    • content_typestr | Nonedefault None

      MIME type of the file. Defaults to the stored file's type, then application/octet-stream.

    • content_bytesstr | Nonedefault None

      Base64-encoded file contents. Pass content_bytes or file.

    • filefile | Nonedefault None

      A 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 | strrequired

    Recipient email addresses, as a list or a comma-separated string.

  • subjectstrrequired

    Email subject line.

  • bodystr | Nonedefault None

    Email body content (HTML or plain text).

  • body_typestrdefault "HTML"

    Body content type, either HTML or Text (defaults to HTML).

  • cc_recipientslist | str | Nonedefault None

    CC recipient email addresses, as a list or a comma-separated string.

  • bcc_recipientslist | str | Nonedefault None

    BCC recipient email addresses, as a list or a comma-separated string.

  • attachmentslist[dict] | list[file] | Nonedefault None

    File 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 | Nonedefault None

      File name shown on the attachment. Defaults to the stored file's name, then "attachment".

    • content_typestr | Nonedefault None

      MIME type of the file. Defaults to the stored file's type, then application/octet-stream.

    • content_bytesstr | Nonedefault None

      Base64-encoded file contents. Pass content_bytes or file.

    • filefile | Nonedefault None

      A 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_idstrrequired

    Outlook 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_idstrrequired

    Outlook message ID containing the attachment.

  • attachment_idstrrequired

    Attachment ID to download.

file

The 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_idstrrequired

    Outlook 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_idstrrequired

    Outlook message ID to move.

  • destination_folder_idstrrequired

    Target 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_idstrrequired

    Outlook message ID to update.

  • is_readboolrequired

    True 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_idstrrequired

    Outlook message ID to update.

  • categorieslist | Nonedefault None

    List of category names to assign to the message.

  • subjectstr | Nonedefault None

    New subject line (draft only).

  • bodystr | Nonedefault None

    New body content (draft only).

  • body_typestrdefault "HTML"

    Body content type used when body is given, either HTML or Text (defaults to HTML).

  • importancestr | Nonedefault None

    Message importance, either low, normal, or high.

  • is_readbool | Nonedefault None

    True to mark the message as read, False to mark it as unread.

  • is_delivery_receipt_requestedbool | Nonedefault None

    Whether a delivery receipt is requested.

  • is_read_receipt_requestedbool | Nonedefault None

    Whether a read receipt is requested.

  • inference_classificationstr | Nonedefault None

    Focused inbox classification, either focused or other.

  • to_recipientslist | str | Nonedefault None

    Recipient email addresses to replace the current ones, as a list or a comma-separated string (draft only).

  • cc_recipientslist | str | Nonedefault None

    CC recipient email addresses to replace the current ones, as a list or a comma-separated string (draft only).

  • bcc_recipientslist | str | Nonedefault None

    BCC recipient email addresses to replace the current ones, as a list or a comma-separated string (draft only).

  • reply_tolist | str | Nonedefault None

    Reply-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 | Nonedefault None

    Category 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 | Nonedefault None

    Parent folder ID to list child folders from.

  • filterstr | Nonedefault None

    Folder display name to match, or a full OData filter expression.

  • topint | Nonedefault None

    Folders per page, sent as Graph's $top (Graph's default is 10).

  • page_tokenstr | Nonedefault None

    An 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_idstrrequired

    Mail 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_linkstrrequired

    Delta 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.

Microsoft Teams​

qbash.integrations.microsoft_teams8 methods · Microsoft Teams connector
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 | Nonedefault None

    Team ID (team_id or team_name is required).

  • team_namestr | Nonedefault None

    Team name, resolved to ID automatically (team_id or team_name is required).

  • page_tokenstr | Nonedefault None

    An 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 | Nonedefault None

    Team ID (team_id or team_name is required).

  • team_namestr | Nonedefault None

    Team name (team_id or team_name is required).

  • channel_idstr | Nonedefault None

    Channel ID (channel_id or channel_name is required).

  • channel_namestr | Nonedefault None

    Channel name (channel_id or channel_name is required).

  • contentstrrequired

    Message 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_idstrrequired

    Chat ID.

  • contentstrrequired

    Message 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,
)
  • topintdefault 50

    Number 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 | Nonedefault None

    Team ID (team_id or team_name is required).

  • team_namestr | Nonedefault None

    Team name (team_id or team_name is required).

  • display_namestrrequired

    Channel display name.

  • descriptionstr | Nonedefault None

    Channel 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 | Nonedefault None

    Team ID (team_id or team_name is required).

  • team_namestr | Nonedefault None

    Team name (team_id or team_name is required).

  • channel_idstr | Nonedefault None

    Channel ID (channel_id or channel_name is required).

  • channel_namestr | Nonedefault None

    Channel name (channel_id or channel_name is required).

  • topintdefault 50

    Number 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 | Nonedefault None

    Team ID (team_id or team_name is required).

  • team_namestr | Nonedefault None

    Team name (team_id or team_name is required).

  • channel_idstr | Nonedefault None

    Channel ID (channel_id or channel_name is required).

  • channel_namestr | Nonedefault None

    Channel name (channel_id or channel_name is required).

  • message_idstrrequired

    Message ID to reply to.

  • contentstrrequired

    Reply 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.

Mixpanel​

qbash.integrations.mixpanel11 methods · Mixpanel connector
list_events()List all event names in a Mixpanel project.
result = qbash.integrations.mixpanel.list_events(
projectId=0,
)
  • projectIdintdefault None

    Mixpanel 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="…",
)
  • projectIdintdefault None

    Mixpanel project ID. Defaults to the Project ID stored on the credential.

  • eventNamestrrequired

    Name of the event to list properties for.

  • propertyNamestrrequired

    Property name to analyze.

  • fromDatestrrequired

    Start date for the query (YYYY-MM-DD).

  • toDatestrrequired

    End date for the query (YYYY-MM-DD).

  • typestrdefault "general"

    Property type: general or histogram.

  • unitstrdefault "day"

    Time unit for grouping: day, week, or month.

  • limitintdefault 255

    Max 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="…",
)
  • projectIdintdefault None

    Mixpanel project ID. Defaults to the Project ID stored on the credential.

  • eventNamestrrequired

    Event name to query.

  • propertyNamestrrequired

    Property name to get values for.

  • limitintdefault 255

    Max 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="…",
)
  • projectIdintdefault None

    Mixpanel project ID. Defaults to the Project ID stored on the credential.

  • eventstrrequired

    Event name to segment on.

  • fromDatestrrequired

    Start date for the query (YYYY-MM-DD).

  • toDatestrrequired

    End date for the query (YYYY-MM-DD).

  • typestrdefault "unique"

    Measurement type: unique, general, or average.

  • unitstrdefault "day"

    Time granularity: day, week, or month.

  • wherestr | Nonedefault None

    Filter expression (e.g. properties[\"$browser\"] == \"Chrome\").

  • onstr | Nonedefault None

    Property 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="…",
)
  • projectIdintdefault None

    Mixpanel project ID. Defaults to the Project ID stored on the credential.

  • fromDatestrrequired

    Start date for the query (YYYY-MM-DD).

  • toDatestrrequired

    End date for the query (YYYY-MM-DD).

  • funnelIdint | Nonedefault None

    Mixpanel funnel ID to query.

  • unitstrdefault "day"

    Time granularity: day, week, or month.

  • intervalint | Nonedefault None

    Conversion window length in days. Mixpanel uses 14 days when omitted.

  • wherestr | Nonedefault None

    Filter 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="…",
)
  • projectIdintdefault None

    Mixpanel project ID. Defaults to the Project ID stored on the credential.

  • fromDatestrrequired

    Start date for the query (YYYY-MM-DD).

  • toDatestrrequired

    End 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 | Nonedefault None

    Qualifying event for birth retention.

  • eventstr | Nonedefault None

    Return event name for the retention query.

  • wherestr | Nonedefault None

    Filter 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="…",
)
  • projectIdintdefault None

    Mixpanel project ID. Defaults to the Project ID stored on the credential.

  • wherestr | Nonedefault None

    Filter expression for profiles (e.g. properties[\"$country\"] == \"US\").

  • page_sizeintdefault 1000

    Number of profiles per page.

  • session_idstr | Nonedefault None

    Session ID for paginating through results.

  • pageint | Nonedefault None

    Page 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="…",
)
  • eventNamestrrequired

    Name 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=[...],
)
  • eventslistrequired

    List 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"},
)
  • distinctIdstrrequired

    Unique identifier for the user profile to update.

  • propertiesdictrequired

    Map 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"},
)
  • distinctIdstrrequired

    Unique identifier for the user profile to update.

  • propertiesdictrequired

    Map 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.

MySQL​

qbash.integrations.mysql4 methods · MySQL connector
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",
)
  • querystrrequired

    One 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 | Nonedefault 30000

    Query timeout in milliseconds. A string of digits is parsed, and anything else falls back to 30000.

  • query_paramslist | Nonedefault None

    Values 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_onlybooldefault False

    Run 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 | Nonedefault 1

    Attempts 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

One 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 | Nonedefault None

    Database 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="…",
)
  • tablestrrequired

    Table or view to describe. Missing or empty fails before any query runs.

  • schemastr | Nonedefault None

    Database 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.

Namecheap​

qbash.integrations.namecheap3 methods · Namecheap connector
check_availability()Check availability of up to 50 domain names at once.
result = qbash.integrations.namecheap.check_availability(
domains=[...],
)
  • domainslistrequired

    List 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 | Nonedefault None

    TLD 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="…",
)
  • domainstrrequired

    Domain 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 connector
list_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 | Nonedefault None

    Pagination cursor returned by a prior call (use the previous response's next_cursor).

  • order_bystr | Nonedefault None

    Sort field: domain_name, current_price, end_time, start_time, or start_price.

  • directionstr | Nonedefault None

    Sort direction: asc or desc.

  • idsstr | Nonedefault None

    Comma-separated list of sale IDs to fetch (e.g. "abc,def").

  • namestr | Nonedefault None

    Filter by domain name (e.g. "example.com").

  • pricestr | Nonedefault None

    Price range as "min_max", "min_", or "_max" (USD, e.g. "100_1000").

  • tldstr | Nonedefault None

    Comma-separated TLD filter (e.g. "com,net,org").

  • start_datestr | Nonedefault None

    Auction start-date range as Unix-seconds (e.g. "1700000000_1710000000").

  • end_datestr | Nonedefault None

    Auction end-date range as Unix-seconds (e.g. "1700000000_1710000000").

  • keywordsstr | Nonedefault None

    Comma-separated keyword filter.

  • agestr | Nonedefault None

    Domain age (years) range, e.g. "2_10".

  • backlinks_countstr | Nonedefault None

    Backlinks count range, e.g. "100_".

  • bid_countstr | Nonedefault None

    Bid count range, e.g. "1_".

  • extensions_takenstr | Nonedefault None

    Range for the number of other TLD extensions registered for the same SLD.

  • name_lengthstr | Nonedefault None

    SLD (second-level domain) character-length range, e.g. "3_6".

  • cloudflare_rankingstr | Nonedefault None

    Cloudflare Radar ranking range.

  • no_hyphensbool | Nonedefault None

    Exclude hyphenated domains.

  • no_numbersbool | Nonedefault None

    Exclude domains containing numbers.

  • only_numbersbool | Nonedefault None

    Only return numeric-only domains.

  • nsfwbool | Nonedefault None

    Include 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_idstrrequired

    The 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 | Nonedefault None

    Page number (1-indexed, default 1).

  • page_sizeint | Nonedefault None

    Items per page (default 100).

  • salestr | Nonedefault None

    Filter 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_idstrrequired

    Sale ID to bid on (from list_sales).

  • max_amountfloatrequired

    Maximum 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.

NetSuite​

qbash.integrations.netsuite6 methods · NetSuite connector
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",
)
  • querystrrequired

    SuiteQL SELECT statement. Put values in query_params as ? placeholders instead of formatting them into the text.

  • query_paramslist | Nonedefault None

    Values for the ? placeholders, in order.

  • limitintdefault 1000

    Rows per page, 1 to 1000. Keep it the same while paging.

  • cursorstr | Nonedefault None

    next_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_typestrrequired

    REST record type ID, for example salesOrder, vendorBill, itemFulfillment, customer or customrecord_item_alias.

  • record_idstr | Nonedefault None

    Internal ID of the record. Pass this or external_id.

  • external_idstr | Nonedefault None

    External ID of the record (letters, numbers, underscores and hyphens). Pass this or record_id.

  • fieldslist[str] | Nonedefault None

    Only return these body fields, by field ID. Omit for all.

  • expand_sub_resourcesbooldefault False

    Include 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 | Nonedefault None

    Only 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_typestrrequired

    REST record type ID, for example salesOrder, vendorBill, itemFulfillment, customer or customrecord_item_alias.

  • searchstr | Nonedefault None

    Only 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_typestrrequired

    REST record type ID, for example salesOrder, vendorBill, itemFulfillment, customer or customrecord_item_alias.

  • valuesdictrequired

    Field IDs and values for the new record, with sublists as {"items": [...]}.

  • external_idstr | Nonedefault None

    Stable key from the source system (letters, numbers, underscores and hyphens). Stored as the record's externalId and used to skip duplicates.

  • verifybooldefault True

    Read 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_typestrrequired

    REST record type ID, for example salesOrder, vendorBill, itemFulfillment, customer or customrecord_item_alias.

  • record_idstr | Nonedefault None

    Internal ID of the record. Pass this or external_id.

  • external_idstr | Nonedefault None

    External ID of the record (letters, numbers, underscores and hyphens). Pass this or record_id.

  • valuesdictrequired

    Fields to change, with sublist lines as {"items": [...]}. Include line to change an existing line.

  • replace_sublistslist[str] | Nonedefault None

    Sublists to rewrite in full, for example ["item"]. Lines not in the request are removed.

  • if_last_modifiedstr | Nonedefault None

    lastModifiedDate from an earlier read. The update is refused when the record's value differs.

  • verifybooldefault True

    Read 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.

Notion​

qbash.integrations.notion13 methods · Notion connector
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 | Nonedefault None

    Text to match against titles. Omit to list everything shared with the integration.

  • object_typestr | Nonedefault None

    "page" or "data_source" to return only that kind of result.

  • sort_directionstr | Nonedefault None

    "descending" or "ascending" to sort by last_edited_time. Omit for relevance order.

  • cursorstr | Nonedefault None

    The next_cursor of a previous call, to read the page after it. Omit to start at the first page.

  • page_sizeint | Nonedefault None

    Results 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_idstrrequired

    Database 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 | Nonedefault None

    Data source ID. Pass this or database_id. Get it from get_database or search.

  • database_idstr | Nonedefault None

    Database 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 | Nonedefault None

    Data source ID. Pass this or database_id. Get it from get_database or search.

  • database_idstr | Nonedefault None

    Database 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 | Nonedefault None

    Notion filter object, such as {"property": "Status", "status": {"equals": "Ready"}}.

  • sortslist[dict] | Nonedefault None

    Notion sort objects, such as [{"property": "Due", "direction": "ascending"}] or [{"timestamp": "created_time", "direction": "descending"}].

  • filter_propertieslist[str] | Nonedefault None

    Property ids to include on each page. Omit for all properties.

  • cursorstr | Nonedefault None

    The next_cursor of a previous call, to read the page after it. Omit to start at the first page.

  • page_sizeint | Nonedefault None

    Results 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_idstrrequired

    Page 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_idstrrequired

    Page 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_idstrrequired

    Page or block ID, or a notion.so page URL.

  • cursorstr | Nonedefault None

    The next_cursor of a previous call, to read the page after it. Omit to start at the first page.

  • page_sizeint | Nonedefault None

    Results 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 | Nonedefault None

    The next_cursor of a previous call, to read the page after it. Omit to start at the first page.

  • page_sizeint | Nonedefault None

    Results 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 | Nonedefault None

    Data source ID. Pass this or database_id. Get it from get_database or search.

  • database_idstr | Nonedefault None

    Database 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 | Nonedefault None

    Create the page under this page instead of in a database.

  • titlestr | Nonedefault None

    Page title. For a database row it fills the title property.

  • propertiesdict | Nonedefault None

    Property name to simple value, such as {"Status": "Ready", "Due": "2026-10-01", "Tags": ["blog"]}.

  • contentstr | Nonedefault None

    Page body as markdown.

  • iconstr | Nonedefault None

    An 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_idstrrequired

    Page ID, with or without dashes, or the page's notion.so URL.

  • propertiesdict | Nonedefault None

    Property name to simple value, such as {"Status": "Done", "Score": 8}.

  • titlestr | Nonedefault None

    New page title.

  • iconstr | Nonedefault None

    An 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_idstrrequired

    Page ID, with or without dashes, or the page's notion.so URL.

  • contentstrrequired

    Markdown 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_idstrrequired

    Page or block ID to append to, or a notion.so page URL.

  • childrenlistrequired

    Notion 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_idstrrequired

    Page ID, with or without dashes, or the page's notion.so URL.

  • restorebooldefault False

    True 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.

OpenSearch​

qbash.integrations.opensearch5 methods · OpenSearch connector
search()Execute a search query against an OpenSearch index.
result = qbash.integrations.opensearch.search(
index="…",
body={"key": "value"},
)
  • indexstrrequired

    Index name or pattern

  • bodydictrequired

    Full search request body, e.g. {"query": {"match_all": {}}}. May also include 'aggs', 'sort', '_source'. Set size to 0 for an aggregation without hits.

  • sizeint | Nonedefault 10

    Max results to return

  • request_timeoutint | Nonedefault 300000

    Request timeout (ms). For search it is also applied as the server-side query timeout.

  • connect_timeoutint | Nonedefault 10000

    Timeout (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 500KB

The 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 | Nonedefault 300000

    Request timeout (ms). For search it is also applied as the server-side query timeout.

  • connect_timeoutint | Nonedefault 10000

    Timeout (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 500KB

The 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="…",
)
  • indexstrrequired

    Index name

  • request_timeoutint | Nonedefault 300000

    Request timeout (ms). For search it is also applied as the server-side query timeout.

  • connect_timeoutint | Nonedefault 10000

    Timeout (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 500KB

The 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="…",
)
  • indexstrrequired

    Index name or pattern, e.g. logs-*

  • bodydict | Nonedefault None

    Query DSL body limiting what is counted, e.g. {"query": {"match": {"status": "error"}}}. Leave it out to count every document in the index.

  • request_timeoutint | Nonedefault 300000

    Request timeout (ms). For search it is also applied as the server-side query timeout.

  • connect_timeoutint | Nonedefault 10000

    Timeout (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 | Nonedefault 300000

    Request timeout (ms). For search it is also applied as the server-side query timeout.

  • connect_timeoutint | Nonedefault 10000

    Timeout (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.

Pangram​

qbash.integrations.pangram11 methods · Pangram connector
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="…",
)
  • textstrrequired

    The 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_secondsintdefault 60

    How 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_idstrrequired

    The task_id a detection mode returned.

  • max_wait_secondsintdefault 0

    How 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="…",
)
  • textstrrequired

    The 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_secondsintdefault 60

    How 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] | Nonedefault None

    The texts to check, when no IDs of your own are needed.

  • itemslist[dict] | Nonedefault None

    The texts to check with an ID of your own for each, such as a row or ticket ID.

    • textstrrequired

      The text to check.

    • idstr | Nonedefault None

      Your 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 | Nonedefault None

    A 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_idstrrequired

    The bulk_id submit_bulk returned.

  • max_wait_secondsintdefault 0

    How 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_idstrrequired

    The bulk_id submit_bulk returned.

  • cursorstr | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 100

    Submitted 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_idstrrequired

    The bulk_id submit_bulk returned.

  • cursorstr | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 100

    Submitted 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"],
)
  • filefilerequired

    The 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"],
)
  • filefilerequired

    The 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="…",
)
  • textstrrequired

    The 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.

Pipedrive​

qbash.integrations.pipedrive18 methods · Pipedrive connector
get_deal()Get a single deal by ID.
result = qbash.integrations.pipedrive.get_deal(
deal_id=0,
)
  • deal_idstr | intrequired

    Deal 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 | Nonedefault 10

    Maximum 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 | Nonedefault None

    Maximum 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 | Nonedefault None

    The next_cursor of a previous call, to read the page after it. Omit to start at the first page.

  • statusstr | Nonedefault None

    Filter 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 | Nonedefault None

    Field 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 | Nonedefault None

    Sort 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="…",
)
  • titlestrrequired

    Deal title.

  • person_idstr | int | Nonedefault None

    Person 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 | Nonedefault None

    Organisation 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 | Nonedefault None

    Deal value as a number. A string is parsed, and a value that is not fully numeric is dropped.

  • currencystr | Nonedefault None

    Three 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 | Nonedefault None

    Pipeline stage ID. A value that is not a whole number is dropped, and when no stage is sent Pipedrive places the deal itself.

  • statusstr | Nonedefault None

    Deal 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 | intrequired

    Deal ID.

  • titlestr | Nonedefault None

    New deal title.

  • person_idstr | int | Nonedefault None

    Person ID to link. A value that is not a whole number is dropped and the existing link is left alone.

  • org_idstr | int | Nonedefault None

    Organisation ID to link. A value that is not a whole number is dropped and the existing link is left alone.

  • valuestr | int | float | Nonedefault None

    Deal value as a number. A string is parsed, and a value that is not fully numeric is dropped.

  • currencystr | Nonedefault None

    Three letter currency code (e.g. USD).

  • stage_idstr | int | Nonedefault None

    Pipeline stage ID to move the deal to. A value that is not a whole number is dropped.

  • statusstr | Nonedefault None

    Deal 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 | intrequired

    Person 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 | Nonedefault 10

    Maximum 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="…",
)
  • namestrrequired

    Person name.

  • emailstr | Nonedefault None

    One email address, stored as the primary entry labelled work. A list is not accepted and is dropped.

  • phonestr | Nonedefault None

    One phone number, stored as the primary entry labelled work. A list is not accepted and is dropped.

  • org_idstr | int | Nonedefault None

    Organisation 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 | intrequired

    Organisation 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 | Nonedefault 10

    Maximum 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="…",
)
  • namestrrequired

    Organisation 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 | Nonedefault None

    Maximum 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 | Nonedefault None

    The 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 | Nonedefault None

    Maximum 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 | Nonedefault None

    The 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 | Nonedefault None

    Maximum 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 | Nonedefault None

    The 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 | Nonedefault None

    Maximum 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 | Nonedefault None

    The next_cursor of a previous call, to read the page after it. Omit to start at the first page.

  • pipeline_idstr | int | Nonedefault None

    Pipeline 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 | Nonedefault None

    Maximum 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 | Nonedefault None

    Cursor to start from, taken from a Pipedrive response obtained elsewhere. No continuation cursor is returned by this mode.

  • deal_idstr | int | Nonedefault None

    Filter by deal ID. A value that is not a whole number is ignored, returning activities for every deal.

  • person_idstr | int | Nonedefault None

    Filter by person ID. A value that is not a whole number is ignored, returning activities for every person.

  • org_idstr | int | Nonedefault None

    Filter 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="…",
)
  • subjectstrrequired

    Activity subject.

  • typestr | Nonedefault None

    Activity 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 | Nonedefault None

    Due date as YYYY-MM-DD. Pipedrive rejects any other format.

  • notestr | Nonedefault None

    Activity note. Accepts basic HTML.

  • deal_idstr | int | Nonedefault None

    Deal ID to link. A value that is not a whole number is dropped and the activity is created without the link.

  • person_idstr | int | Nonedefault None

    Person ID to link. A value that is not a whole number is dropped and the activity is created without the link.

  • org_idstr | int | Nonedefault None

    Organisation 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="…",
)
  • contentstrrequired

    Note content. Accepts basic HTML.

  • deal_idstr | int | Nonedefault None

    Deal ID to attach the note to. A value that is not a whole number is dropped.

  • person_idstr | int | Nonedefault None

    Person ID to attach the note to. A value that is not a whole number is dropped.

  • org_idstr | int | Nonedefault None

    Organisation 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.

PostgreSQL​

qbash.integrations.postgres4 methods · PostgreSQL connector
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",
)
  • querystrrequired

    SQL 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 | Nonedefault 30000

    Query timeout in milliseconds. A string of digits is parsed, and anything else falls back to 30000.

  • query_paramslist | Nonedefault None

    Values 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_onlybooldefault False

    Run 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 | Nonedefault 1

    Attempts 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="…",
)
  • tablestrrequired

    Table, 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.

Reddit​

qbash.integrations.reddit10 methods · Reddit connector
subreddit()Fetch posts from a subreddit.
result = qbash.integrations.reddit.subreddit(
subredditName="…",
)
  • subredditNamestrrequired

    Name 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.

  • limitintdefault 10

    Max number of posts to return.

  • afterstr | Nonedefault None

    Pagination cursor (after) from a previous response.

  • beforestr | Nonedefault None

    Pagination cursor (before) from a previous response.

  • flairstr | Nonedefault None

    Filter 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="…",
)
  • postIdstrrequired

    Reddit post ID (the part after /comments/ in the URL).

  • limitintdefault 100

    Max number of comments to return.

  • sortstrdefault "top"

    Comment sort order: top, new, best, controversial, or old.

  • contextintdefault 0

    Number of parent comments to include for context.

list[dict] of comments, with no wrapping object

The 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",
)
  • querystrrequired

    Search query string.

  • sortstrdefault "relevance"

    Sort order: relevance, hot, top, new, or comments.

  • timeFilterstrdefault "all"

    Time window: hour, day, week, month, year, or all.

  • limitintdefault 25

    Max number of results to return.

  • afterstr | Nonedefault None

    Pagination cursor (after).

  • beforestr | Nonedefault None

    Pagination 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",
)
  • subredditNamestrrequired

    Name of the subreddit to search within.

  • querystrrequired

    Search query string.

  • sortstrdefault "relevance"

    Sort order: relevance, hot, top, new, or comments.

  • timeFilterstrdefault "all"

    Time window: hour, day, week, month, year, or all.

  • limitintdefault 25

    Max number of results to return.

  • afterstr | Nonedefault None

    Pagination cursor (after).

  • beforestr | Nonedefault None

    Pagination 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="…",
)
  • usernamestrrequired

    Reddit 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="…",
)
  • usernamestrrequired

    Reddit username.

  • sortstrdefault "new"

    Sort order: new, hot, top, or controversial.

  • timeFilterstrdefault "all"

    Time window for top/controversial: hour, day, week, month, year, or all.

  • limitintdefault 25

    Max number of posts to return.

  • afterstr | Nonedefault None

    Pagination cursor (after).

  • beforestr | Nonedefault None

    Pagination 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="…",
)
  • usernamestrrequired

    Reddit username.

  • sortstrdefault "new"

    Sort order: new, hot, top, or controversial.

  • timeFilterstrdefault "all"

    Time window: hour, day, week, month, year, or all.

  • limitintdefault 25

    Max number of comments to return.

  • afterstr | Nonedefault None

    Pagination cursor (after).

  • beforestr | Nonedefault None

    Pagination 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="…",
)
  • subredditNamestrrequired

    Name 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="…",
)
  • subredditNamestrrequired

    Name of the subreddit (without r/).

list[dict] with keys short_name, description, kind, violation_reason and priority, with no wrapping object

The 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="…",
)
  • subredditNamestrrequired

    Name of the subreddit (without r/).

list[dict] with keys id, text, css_class and mod_only, with no wrapping object

The 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.

SERP​

qbash.integrations.serp1 method · SERP connector
search()Search the web via SerpAPI and return structured results.
result = qbash.integrations.serp.search(
search_string="…",
)
  • search_stringstrrequired

    Search 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 | Nonedefault None

    Geographic 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 | Nonedefault None

    Time 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 | Nonedefault None

    Map 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 | Nonedefault None

    Result ordering for the news engines, either date or relevance. Any other value, or any other engine, drops the ordering without raising.

  • glstr | Nonedefault None

    Two letter country code for the search locale (e.g. us).

  • hlstr | Nonedefault None

    Two 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.

SFTP / FTP​

qbash.integrations.sftp6 methods · SFTP / FTP connector
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 | Nonedefault 30

    Connection timeout in seconds.

  • retryAttemptsint | Nonedefault 3

    Attempts 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="…",
)
  • pathstrrequired

    Remote file path.

  • timeoutSecondsint | Nonedefault 30

    Connection timeout in seconds.

  • retryAttemptsint | Nonedefault 3

    Attempts 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="…",
)
  • pathstrrequired

    Remote CSV file path.

  • hasHeaderbool | Nonedefault True

    Whether the file has a header row.

  • chunkSizeint | Nonedefault 1000

    Row chunk size for building the seek index.

  • timeoutSecondsint | Nonedefault 30

    Connection timeout in seconds.

  • retryAttemptsint | Nonedefault 3

    Attempts 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,
)
  • pathstrrequired

    Remote CSV file path.

  • startRowintrequired

    Zero-based row index to start reading from.

  • rowCountintrequired

    Number of rows to read.

  • hasHeaderbool | Nonedefault True

    Whether the file has a header row.

  • timeoutSecondsint | Nonedefault 30

    Connection timeout in seconds.

  • retryAttemptsint | Nonedefault 3

    Attempts 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="…",
)
  • pathstrrequired

    Remote file path to download (max 5 GB).

  • contentTypestr | Nonedefault None

    MIME type override (auto-detected if omitted).

  • timeoutSecondsint | Nonedefault 120

    How long one read may wait, in seconds.

  • retryAttemptsint | Nonedefault 3

    Attempts to make when the server returns a transient error, from 1 to 10.

file

The 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,
)
  • pathstrrequired

    Absolute remote file path.

  • lineCountintrequired

    Maximum number of lines to read (capped at 200).

  • offsetint | Nonedefault 0

    Zero-based line number to start reading from. SFTP only: FTP always reads from the first line and ignores this.

  • timeoutSecondsint | Nonedefault 30

    Connection timeout in seconds.

  • retryAttemptsint | Nonedefault 3

    Attempts 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.

Shopify​

qbash.integrations.shopify13 methods · Shopify connector
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 | Nonedefault None

    Inclusive lower bound on created_at, ISO 8601 (2026-09-29T00:00:00-04:00). Include the offset for an exact day boundary.

  • created_beforestr | Nonedefault None

    Exclusive upper bound on created_at, ISO 8601. For one day pass the next day's midnight.

  • updated_sincestr | Nonedefault None

    Inclusive lower bound on updated_at, ISO 8601. Use it to sync orders changed since the last run.

  • updated_beforestr | Nonedefault None

    Exclusive upper bound on updated_at, ISO 8601.

  • financial_statusstr | Nonedefault None

    Financial status filter: paid, pending, authorized, partially_paid, partially_refunded, refunded, voided or expired.

  • fulfillment_statusstr | Nonedefault None

    Fulfillment status filter: shipped, partial, unshipped, unfulfilled, on_hold or scheduled.

  • statusstr | Nonedefault None

    Order status filter: open, closed or cancelled. Omit for all.

  • querystr | Nonedefault None

    Extra 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.

  • reversebooldefault True

    True sorts descending (newest first). Set False for oldest first.

  • include_line_itemsbooldefault False

    Include up to 20 line items per order. Caps limit at 10.

  • limitintdefault 50

    Orders per page, 1 to 50 (1 to 10 with include_line_items).

  • cursorstr | Nonedefault None

    next_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_idstrrequired

    Order 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 | Nonedefault None

    Shopify customer search, for example email:ann@example.com, tag:vip, country:Canada or orders_count:>5.

  • sort_keystr | Nonedefault None

    Sort field: CREATED_AT, UPDATED_AT, NAME, ID, LOCATION or RELEVANCE.

  • reversebooldefault False

    True sorts descending.

  • limitintdefault 50

    Customers per page, 1 to 100.

  • cursorstr | Nonedefault None

    next_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_idstrrequired

    Customer 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 | Nonedefault None

    Shopify product search, for example status:active, title:shirt*, sku:ABC-1 or inventory_total:<5.

  • include_variantsbooldefault True

    Include up to 10 variants per product. Caps limit at 25.

  • sort_keystr | Nonedefault None

    Sort field: TITLE, CREATED_AT, UPDATED_AT, INVENTORY_TOTAL, PRODUCT_TYPE, VENDOR or ID.

  • reversebooldefault False

    True sorts descending.

  • limitintdefault 25

    Products per page, 1 to 25 with variants or 1 to 100 without.

  • cursorstr | Nonedefault None

    next_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_idstrrequired

    Product 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 | Nonedefault None

    Shopify collection search, for example title:Summer* or collection_type:smart.

  • sort_keystr | Nonedefault None

    Sort field: TITLE, UPDATED_AT, ID or RELEVANCE.

  • reversebooldefault False

    True sorts descending.

  • limitintdefault 50

    Collections per page, 1 to 100.

  • cursorstr | Nonedefault None

    next_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_inactivebooldefault False

    Also return deactivated locations.

  • limitintdefault 50

    Locations per page, 1 to 250.

  • cursorstr | Nonedefault None

    next_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_idstrrequired

    Location GID (gid://shopify/Location/123) or numeric ID from list_locations.

  • max_availableint | Nonedefault None

    Keep only items whose available quantity is at or below this number.

  • limitintdefault 50

    Levels fetched per page, 1 to 100.

  • cursorstr | Nonedefault None

    next_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",
)
  • querystrrequired

    GraphQL query document. Must be a query operation.

  • variablesdict | Nonedefault None

    Variables 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=[...],
)
  • idstrrequired

    GID of the order, customer, product or draft order, or a numeric ID together with resource_type.

  • tagslist[str]required

    Tags 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=[...],
)
  • idstrrequired

    GID of the order, customer, product or draft order, or a numeric ID together with resource_type.

  • tagslist[str]required

    Tags 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.

Slack​

qbash.integrations.slack11 methods · Slack connector
create_message()Send a message to a channel.
result = qbash.integrations.slack.create_message(
channel="…",
text="…",
)
  • channelstrrequired

    Channel ID (e.g. C0123) or name (e.g. #general).

  • textstrrequired

    Message 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="…",
)
  • channelstrrequired

    Channel ID or name where the parent message lives.

  • textstrrequired

    Reply text. Supports Slack mrkdwn.

  • thread_tsstrrequired

    Timestamp 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_idstrrequired

    Slack 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="…",
)
  • emailstrrequired

    Email 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]required

    A Slack file object, or a list of them, as they appear in the files array of a message from get_conversation_history.

    • url_private_downloadstrrequired

      Authenticated download URL from the Slack file object. Must be an HTTPS URL on files.slack.com.

    • namestr | Nonedefault None

      File name to store the download under. Falls back to title, then to "attachment".

    • titlestr | Nonedefault None

      File title, used as the file name when name is absent.

    • mimetypestr | Nonedefault None

      Content 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="…",
)
  • channelstrrequired

    Channel ID (e.g. C0123). Use list_conversations to resolve a channel name to an ID.

  • oldeststr | int | float | Nonedefault None

    Earliest 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 | Nonedefault None

    Newest message to include, in the same formats as oldest.

  • limitintdefault 100

    Max messages to return (1-1000).

  • cursorstr | Nonedefault None

    Pagination cursor from a previous response.

  • inclusivebooldefault False

    Include 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="…",
)
  • channelstrrequired

    Channel ID (e.g. C0123) where the thread lives.

  • thread_tsstrrequired

    Timestamp of the parent message, the ts value from get_conversation_history.

  • oldeststr | int | float | Nonedefault None

    Earliest reply to include, as a Unix timestamp, a Slack message ts, an ISO 8601 datetime or an ISO 8601 date.

  • lateststr | int | float | Nonedefault None

    Newest reply to include, in the same formats as oldest.

  • limitintdefault 100

    Max replies to return (1-1000).

  • cursorstr | Nonedefault None

    Pagination cursor from a previous response.

  • inclusivebooldefault False

    Include 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="…",
)
  • limitintdefault 100

    Max users to return (1-1000).

  • cursorstr | Nonedefault None

    Pagination cursor from a previous response.

  • include_localebooldefault False

    Include 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.

  • limitintdefault 100

    Max conversations to return (1-1000).

  • cursorstr | Nonedefault None

    Pagination cursor from a previous response.

  • exclude_archivedbooldefault False

    Omit 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="…",
)
  • channelstrrequired

    Channel ID (e.g. C0123). Use list_conversations to resolve a channel name to an ID.

  • include_localebooldefault False

    Include locale information in the result.

  • include_num_membersbooldefault False

    Include 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="…",
)
  • channelstrrequired

    Channel ID (e.g. C0123). Use list_conversations to resolve a channel name to an ID.

  • limitintdefault 100

    Max member IDs to return (1-1000).

  • cursorstr | Nonedefault None

    Pagination 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.

Snowflake​

qbash.integrations.snowflake7 methods · Snowflake connector
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",
)
  • querystrrequired

    One SQL statement. Use ? placeholders for values.

  • query_paramslist | Nonedefault None

    Values 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 | Nonedefault 10000

    Most rows to return, up to 100000. The statement still runs in full in Snowflake.

  • query_timeoutint | str | Nonedefault 60000

    Milliseconds to wait for the statement before cancelling it, from 1000 to 3600000.

  • read_onlybooldefault False

    Refuse any statement that is not a read. Only the boolean True enables it.

  • warehousestr | Nonedefault None

    Warehouse to run on. Omitting it uses the credential's warehouse, then the user's default.

  • databasestr | Nonedefault None

    Database for unqualified table names. Omitting it uses the credential's database.

  • schemastr | Nonedefault None

    Schema for unqualified table names. Omitting it uses the credential's schema.

  • rolestr | Nonedefault None

    Role 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_value

Runs 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"},
)
  • tablestrrequired

    Table to insert into.

  • rowslist[dict]required

    Rows to insert, 1 to 1000 dicts keyed by column name.

  • databasestr | Nonedefault None

    Database holding the table. Omitting it uses the credential's database.

  • schemastr | Nonedefault None

    Schema holding the table. Omitting it uses the credential's schema.

  • warehousestr | Nonedefault None

    Warehouse to run on. Omitting it uses the credential's warehouse, then the user's default.

  • rolestr | Nonedefault None

    Role to run as. Omitting it uses the credential's role, then the user's default.

  • query_timeoutint | str | Nonedefault 60000

    Milliseconds 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 | Nonedefault None

    Database 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 | Nonedefault None

    Database holding the schema. Omitting it uses the credential's database.

  • schemastr | Nonedefault None

    Schema 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="…",
)
  • tablestrrequired

    Table or view to describe.

  • databasestr | Nonedefault None

    Database holding the table. Omitting it uses the credential's database.

  • schemastr | Nonedefault None

    Schema 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.

Stripe​

qbash.integrations.stripe23 methods · Stripe connector
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 | Nonedefault None

    Only this transaction type, for example charge, refund, payout, stripe_fee or adjustment.

  • payoutstr | Nonedefault None

    Only transactions settled in this automatic payout id (po_...).

  • sourcestr | Nonedefault None

    Only transactions created by this source id, such as a charge id.

  • currencystr | Nonedefault None

    Only this three-letter ISO currency, lowercase.

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    Exact email address to match.

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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_idstrrequired

    Customer 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",
)
  • querystrrequired

    Stripe search query, for example email:'jo@example.com' or status:'failed' AND created>1700000000. Strings take single quotes.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    Only objects for this customer id (cus_...).

  • payment_intentstr | Nonedefault None

    Only objects for this PaymentIntent id (pi_...).

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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",
)
  • querystrrequired

    Stripe search query, for example email:'jo@example.com' or status:'failed' AND created>1700000000. Strings take single quotes.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    Only objects for this customer id (cus_...).

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    Only objects for this customer id (cus_...).

  • pricestr | Nonedefault None

    Only subscriptions that include this price id (price_...).

  • statusstr | Nonedefault None

    active, past_due, unpaid, canceled, incomplete, incomplete_expired, trialing, paused, ended or all. Omitted means every status except canceled.

  • collection_methodstr | Nonedefault None

    charge_automatically or send_invoice.

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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_idstrrequired

    Subscription 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",
)
  • querystrrequired

    Stripe search query, for example email:'jo@example.com' or status:'failed' AND created>1700000000. Strings take single quotes.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    Only objects for this customer id (cus_...).

  • subscriptionstr | Nonedefault None

    Only invoices for this subscription id (sub_...).

  • statusstr | Nonedefault None

    draft, open, paid, uncollectible or void.

  • collection_methodstr | Nonedefault None

    charge_automatically or send_invoice.

  • due_date_gtestr | int | Nonedefault None

    Only invoices due at or after this time. Same formats as created_gte.

  • due_date_ltestr | int | Nonedefault None

    Only invoices due at or before this time. Same formats as created_lte.

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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_idstrrequired

    Invoice 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",
)
  • querystrrequired

    Stripe search query, for example email:'jo@example.com' or status:'failed' AND created>1700000000. Strings take single quotes.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    pending, paid, failed or canceled.

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    Only objects for this charge id (ch_...).

  • payment_intentstr | Nonedefault None

    Only objects for this PaymentIntent id (pi_...).

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    True for active products only, False for archived ones only.

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    Only prices for this product id (prod_...).

  • activebool | Nonedefault None

    True for active prices only, False for archived ones only.

  • typestr | Nonedefault None

    one_time or recurring.

  • currencystr | Nonedefault None

    Only this three-letter ISO currency, lowercase.

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    Only objects for this charge id (ch_...).

  • payment_intentstr | Nonedefault None

    Only objects for this PaymentIntent id (pi_...).

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    One event type, or a wildcard such as invoice.* .

  • typeslist[str] | Nonedefault None

    Up to 20 exact event types. Use instead of type.

  • created_gtestr | int | Nonedefault None

    Only 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 | Nonedefault None

    Only 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.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    Customer email.

  • namestr | Nonedefault None

    Customer or company name.

  • phonestr | Nonedefault None

    Phone number.

  • descriptionstr | Nonedefault None

    Internal description shown in the dashboard.

  • metadatadict | Nonedefault None

    String 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 | Nonedefault None

    Optional 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_idstrrequired

    Customer id (cus_...).

  • emailstr | Nonedefault None

    New email.

  • namestr | Nonedefault None

    New name.

  • phonestr | Nonedefault None

    New phone number.

  • descriptionstr | Nonedefault None

    New internal description.

  • metadatadict | Nonedefault None

    String 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_idstrrequired

    Invoice id (in_...).

  • idempotency_keystr | Nonedefault None

    Optional 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.

Supabase​

qbash.integrations.supabase14 methods · Supabase connector
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 | Nonedefault None

    Supabase 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="…",
)
  • orgIdstrrequired

    Supabase 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 | Nonedefault None

    Supabase 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 | Nonedefault None

    Supabase 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 | Nonedefault None

    Supabase 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=[...],
)
  • migrationNamestrrequired

    Name for the migration.

  • statementslistrequired

    List of SQL statement strings to apply.

  • versionstr | Nonedefault None

    Migration version identifier such as 20250101120000. Supabase assigns one when omitted.

  • projectRefstr | Nonedefault None

    Supabase 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",
)
  • querystrrequired

    SQL query to execute.

  • projectRefstr | Nonedefault None

    Supabase 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 | Nonedefault None

    Supabase project reference (uses credential default if omitted).

  • isoTimestampStartstr | Nonedefault None

    ISO 8601 start timestamp to filter logs (e.g. 2025-10-14T14:57:35Z).

  • isoTimestampEndstr | Nonedefault None

    ISO 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="…",
)
  • bucketNamestrrequired

    Name for the new storage bucket.

  • publicbooldefault False

    Whether the bucket should be publicly accessible.

  • fileSizeLimitint | Nonedefault None

    Maximum file size in bytes for uploads to this bucket.

  • allowedMimeTypesstr | Nonedefault None

    Comma-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="…",
)
  • bucketIdstrrequired

    Storage 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="…",
)
  • bucketIdstrrequired

    Storage bucket ID to list files from.

  • prefixstr | Nonedefault None

    Optional path prefix to filter files.

  • limitint | Nonedefault None

    Maximum number of files to return.

  • offsetint | Nonedefault None

    Offset 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.

Supadata.ai​

qbash.integrations.supadata4 methods · Supadata.ai connector
scrape_web()Scrape content from a single web page.
result = qbash.integrations.supadata.scrape_web(
url="https://…",
)
  • urlstrrequired

    URL of the web page to scrape.

dict or str

Reads 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://…",
)
  • urlstrrequired

    Root URL of the website to map.

dict or str

Reads 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="…",
)
  • videoIdstrrequired

    11-character YouTube video ID (e.g. 'dQw4w9WgXcQ').

  • langstr | Nonedefault None

    Preferred transcript language code (e.g. 'en'). Ignored when the video has no transcript in that language.

  • textbool | Nonedefault None

    Return plain text instead of timestamped segments. Defaults to segments.

  • chunkSizeint | Nonedefault None

    Split transcript into chunks of this many characters. Applies only when text is False.

dict or str

Reads 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="…",
)
  • channelIdstrrequired

    24-character YouTube channel ID starting with 'UC'.

  • limitint | Nonedefault None

    Maximum number of videos to return. Defaults to 30.

  • channelTypestr | Nonedefault None

    Format filter: all, video, short or live. Defaults to all.

dict or str

Reads 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.

Supermetrics​

qbash.integrations.supermetrics5 methods · Supermetrics connector
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 | Nonedefault None

    Data source ID such as AW for Google Ads. When set, config holds that source's report types and settings.

  • filterstr | Nonedefault None

    Keyword 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_idstrrequired

    Data source ID from data_source_discovery, such as AW for Google Ads.

  • filterstr | Nonedefault None

    Comma-separated terms. An account matches when its ID, name or group contains any term, ignoring case.

  • limitintdefault 100

    Most 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_idstrrequired

    Data source ID from data_source_discovery, such as AW for Google Ads.

  • filterstr | Nonedefault None

    Comma-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 | Nonedefault None

    Account IDs whose custom fields to include. Needs ds_user.

  • ds_userstr | Nonedefault None

    Data 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_idstrrequired

    Data source ID from data_source_discovery, such as AW for Google Ads or GAWA for Google Analytics 4.

  • ds_accountslist[str] | str | Nonedefault None

    Account IDs from accounts_discovery, a comma-separated string of them, or list.all_accounts. Most sources require it.

  • fieldslist[str] | str | Nonedefault None

    Field IDs from field_discovery, dimensions before metrics.

  • date_range_typestr | Nonedefault None

    Range 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 | Nonedefault None

    Start of a custom range as YYYY-MM-DD, @ plus a Unix timestamp, or a relative date such as -30 days.

  • end_datestr | Nonedefault None

    End of a custom range, in the same formats as start_date.

  • timezonestr | Nonedefault None

    IANA timezone for relative dates, such as America/New_York. Defaults to the team's query timezone setting.

  • filtersstr | Nonedefault None

    Filter 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 | Nonedefault None

    Supermetrics settings keyed by setting ID, including report_type and the source settings listed by data_source_discovery.

  • compare_typestr | Nonedefault None

    Comparison period: prev_range, prev_year, prev_year_weekday, or custom with compare_start_date and compare_end_date.

  • compare_showstr | Nonedefault None

    How comparison values appear: perc_change (the Supermetrics default), abs_change or value.

  • compare_start_datestr | Nonedefault None

    Start of a custom comparison range, in the same formats as start_date.

  • compare_end_datestr | Nonedefault None

    End of a custom comparison range, in the same formats as start_date.

  • max_rowsintdefault 1000

    Most rows Supermetrics fetches from the source, from 1 through 1,000,000. The license may allow fewer.

  • ds_userstr | Nonedefault None

    Data source username of the login to query with, from accounts_discovery.

  • order_rowslist[str] | str | Nonedefault None

    Up to two sort instructions such as cost desc.

  • cache_minutesint | Nonedefault None

    Oldest cached result Supermetrics may return, in minutes, from 0 through 10080.

  • offset_startintdefault 0

    Zero-based index of the first row in this response.

  • offset_endint | Nonedefault None

    Zero-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_idstrrequired

    schedule_id from data_query.

  • offset_startintdefault 0

    Zero-based index of the first row in this response.

  • offset_endint | Nonedefault None

    Zero-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.

Telegram​

qbash.integrations.telegram17 methods · Telegram connector
send_message()Send a text message to a chat.
result = qbash.integrations.telegram.send_message(
chat_id="…",
text="…",
)
  • chat_idstrrequired

    Chat ID or username.

  • textstrrequired

    Message text.

  • parse_modestr | Nonedefault None

    Formatting mode (Markdown / HTML).

  • disable_web_page_previewbool | Nonedefault None

    Disable link preview in the message.

  • disable_notificationbool | Nonedefault None

    Send silently without notification.

  • reply_to_message_idint | Nonedefault None

    Reply to a specific message ID.

  • reply_markupdict | Nonedefault None

    Inline 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_idstrrequired

    Chat ID or username.

  • photostrrequired

    Photo URL or file_id.

  • captionstr | Nonedefault None

    Photo caption.

  • parse_modestr | Nonedefault None

    Formatting mode (Markdown / HTML).

  • disable_notificationbool | Nonedefault None

    Send silently without notification.

  • reply_to_message_idint | Nonedefault None

    Reply 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_idstrrequired

    Chat ID or username.

  • documentstrrequired

    Document URL or file_id.

  • captionstr | Nonedefault None

    Document caption.

  • parse_modestr | Nonedefault None

    Formatting mode (Markdown / HTML).

  • disable_notificationbool | Nonedefault None

    Send silently without notification.

  • reply_to_message_idint | Nonedefault None

    Reply 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_idstrrequired

    Chat ID or username.

  • message_idintrequired

    ID of the message to edit.

  • textstrrequired

    New message text.

  • parse_modestr | Nonedefault None

    Formatting mode (Markdown / HTML).

  • reply_markupdict | Nonedefault None

    Updated 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_idstrrequired

    Chat ID or username.

  • message_idintrequired

    ID 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_idstrrequired

    Chat ID or username.

  • questionstrrequired

    Poll question.

  • optionslistrequired

    List of answer options (2-10 strings).

  • is_anonymousbool | Nonedefault None

    Whether the poll is anonymous.

  • typestr | Nonedefault None

    Poll type (regular / quiz).

  • correct_option_idint | Nonedefault None

    Zero-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 | Nonedefault None

    Update ID offset for pagination.

  • limitintdefault 100

    Maximum number of updates to return.

  • timeoutintdefault 0

    Long-polling timeout in seconds (0 for short polling).

  • allowed_updateslist | Nonedefault None

    List 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_idstrrequired

    Chat 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_idstrrequired

    Chat 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://…",
)
  • urlstrrequired

    HTTPS URL to send updates to.

  • max_connectionsint | Nonedefault None

    Maximum allowed number of simultaneous HTTPS connections.

  • allowed_updateslist | Nonedefault None

    List of update types to receive.

  • secret_tokenstr | Nonedefault None

    Secret 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_idstrrequired

    Chat ID or username.

  • message_idintrequired

    ID of the message to pin.

  • disable_notificationbool | Nonedefault None

    Pin 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_idstrrequired

    Chat ID or username.

  • message_idint | Nonedefault None

    ID 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_idstrrequired

    Chat ID or username.

  • titlestrrequired

    New 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_idstrrequired

    Chat 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_idstrrequired

    Chat 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_idstrrequired

    Chat ID or username.

  • actionstrrequired

    Action to broadcast (typing / upload_photo / upload_document / etc.).

{"status": "success", "result": bool, "chat_id": str, "action": str}

Typeform​

qbash.integrations.typeform18 methods · Typeform connector
list_workspaces()List all workspaces.
result = qbash.integrations.typeform.list_workspaces(
page=0,
page_size=10,
)
  • pageint

    Page number (1-indexed).

  • page_sizeintdefault 10

    Results per page (max 200).

  • searchstr

    Filter 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_idstrrequired

    Workspace 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_idstr

    Filter by workspace ID.

  • searchstr

    Search forms by title.

  • pageint

    Page number.

  • page_sizeintdefault 10

    Results 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_idstrrequired

    Form 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=[...],
)
  • titlestrrequired

    Form title.

  • typestrdefault form

    'form' or 'quiz'.

  • fieldslistrequired

    Array of field definitions. Each has: type, title, ref, properties, validations.

  • settingsdict

    Form settings (language, progress_bar, meta, etc.).

  • logiclist

    Logic jump definitions for conditional branching.

  • welcome_screenslist

    Welcome screen definitions.

  • thankyou_screenslist

    Thank-you screen definitions.

  • theme_hrefstr

    Theme API URL (e.g. https://api.typeform.com/themes/{id}).

  • workspace_hrefstr

    Workspace API URL to place the form in.

  • hiddenlist

    Hidden field names for URL parameter passthrough.

  • variablesdict

    Variable 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_idstrrequired

    Form ID to update.

  • titlestrrequired

    Form title (required for PUT).

  • typestr

    'form' or 'quiz' (omitting on PUT resets to 'form').

  • fieldslistrequired

    Full fields array. Required, and omitting it removes every field from the form.

  • settingsdict

    Form settings.

  • logiclist

    Logic jump definitions.

  • welcome_screenslist

    Welcome screen definitions.

  • thankyou_screenslist

    Thank-you screen definitions.

  • hiddenlist

    Hidden field names (omitting on PUT removes them).

  • variableslist

    Variable definitions (omitting on PUT removes them).

  • theme_hrefstr

    Theme API URL (omitting on PUT resets to default).

  • workspace_hrefstr

    Workspace 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_idstrrequired

    Form 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_idstrrequired

    Form ID.

  • page_sizeintdefault 25

    Results per page (max 1000).

  • sincestr

    Only responses after this ISO 8601 datetime.

  • untilstr

    Only responses before this ISO 8601 datetime.

  • afterstr

    Cursor token for forward pagination.

  • beforestr

    Cursor token for backward pagination.

  • completedbool

    If true, only completed responses.

  • querystr

    Search within response answers.

  • fieldsstr

    Comma-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_idstrrequired

    Form ID.

  • included_response_idsstrrequired

    Comma-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_idstrrequired

    Image 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_namestrrequired

    Image file name.

  • imagestr

    Base64-encoded image data (use this OR url, not both).

  • urlstr

    Public 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_idstrrequired

    Image 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,
)
  • pageint

    Page number.

  • page_sizeintdefault 10

    Results 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_idstrrequired

    Theme 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="…",
)
  • namestrrequired

    Theme name.

  • colorsdict

    Color settings: {question, answer, button, background}.

  • fontstr

    Font family name.

  • has_transparent_buttonbool

    If true, buttons are transparent.

  • backgrounddict

    Background 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_idstrrequired

    Theme ID to update.

  • namestrrequired

    Theme name (required for PUT).

  • colorsdictrequired

    Color settings. Required, and omitting it resets them to the defaults. Keys: question, answer, button, background.

  • fontstrrequired

    Font family name. Required, and omitting it resets the font to the default.

  • has_transparent_buttonbool

    If true, buttons are transparent.

  • backgrounddict

    Background config.

  • visibilitystr

    Theme visibility: 'private' or 'public' (omitting on PUT resets).

  • rounded_cornersstr

    Corner 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_idstrrequired

    Theme 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.

Vimeo​

qbash.integrations.vimeo13 methods · Vimeo connector
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 | Nonedefault None

    Only items whose name or description matches this text.

  • sortstr | Nonedefault None

    date, alphabetical, plays, likes, duration, modified_time, last_user_action_event_date or default.

  • directionstr | Nonedefault None

    asc or desc. Applies to sort.

  • filterstr | Nonedefault None

    One of embeddable, featured, live, nolive, playable, no_placeholder or screen_recorded.

  • filter_tagstr | Nonedefault None

    Only videos with this tag. Several tags can be comma separated and match any of them.

  • user_idstr | Nonedefault None

    Numeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.

  • cursorstr | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 25

    Items 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_idstrrequired

    Vimeo 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 | Nonedefault None

    Only items whose name or description matches this text.

  • sortstr | Nonedefault None

    date, name, modified_time, pinned_on or default.

  • directionstr | Nonedefault None

    asc or desc. Applies to sort.

  • user_idstr | Nonedefault None

    Numeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.

  • cursorstr | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 25

    Items 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_idstrrequired

    Folder ID from list_folders, or the folder's URL.

  • include_subfoldersbooldefault False

    Also list videos in the folder's subfolders.

  • querystr | Nonedefault None

    Only items whose name or description matches this text.

  • sortstr | Nonedefault None

    date, alphabetical, duration, last_user_action_event_date or default.

  • directionstr | Nonedefault None

    asc or desc. Applies to sort.

  • filter_tagstr | Nonedefault None

    Only videos with this tag. Several tags can be comma separated and match any of them.

  • user_idstr | Nonedefault None

    Numeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.

  • cursorstr | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 25

    Items 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 | Nonedefault None

    Only items whose name or description matches this text.

  • sortstr | Nonedefault None

    date, alphabetical, duration, last_modified or videos.

  • directionstr | Nonedefault None

    asc or desc. Applies to sort.

  • user_idstr | Nonedefault None

    Numeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.

  • cursorstr | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 25

    Items 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_idstrrequired

    Showcase ID from list_showcases, or the showcase's URL.

  • querystr | Nonedefault None

    Only items whose name or description matches this text.

  • sortstr | Nonedefault None

    manual (the showcase's own order), date, alphabetical, plays, likes, comments, duration, modified_time or default.

  • directionstr | Nonedefault None

    asc or desc. Applies to sort.

  • filter_tagstr | Nonedefault None

    Only videos with this tag. Several tags can be comma separated and match any of them.

  • user_idstr | Nonedefault None

    Numeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.

  • cursorstr | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 25

    Items 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_idstrrequired

    Vimeo video ID or URL, e.g. 76979871 or https://vimeo.com/76979871.

  • parent_comment_idstr | Nonedefault None

    A comment_id from a previous call, to list that comment's replies instead.

  • directionstr | Nonedefault None

    asc or desc. Applies to sort.

  • cursorstr | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 25

    Items 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_idstrrequired

    Vimeo 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_idstrrequired

    Vimeo video ID or URL, e.g. 76979871 or https://vimeo.com/76979871.

  • text_track_idstr | Nonedefault None

    A text_track_id from list_text_tracks. Takes precedence over language.

  • languagestr | Nonedefault None

    Language code to pick a track, e.g. en or es-MX.

  • include_segmentsbooldefault False

    Also 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_idstrrequired

    Vimeo 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",
)
  • querystrrequired

    Search text.

  • sortstr | Nonedefault None

    relevant, date, alphabetical, plays, likes, comments or duration.

  • directionstr | Nonedefault None

    asc or desc. Applies to sort.

  • filterstr | Nonedefault None

    A 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 | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 25

    Items 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="…",
)
  • dimensionstrrequired

    How to group rows: video, country, region, city, device_type, os, browser, embed_domain, embed_domain_path, streaming_type or total.

  • start_datestrrequired

    First day of the period, YYYY-MM-DD (UTC) or a full ISO 8601 datetime.

  • end_datestrrequired

    Last day of the period, YYYY-MM-DD (UTC, inclusive) or a full ISO 8601 datetime.

  • time_intervalstr | Nonedefault None

    Split each row by day, week, month or year. Omit for one total per dimension value.

  • video_idslist[str] | Nonedefault None

    Only these videos (IDs or URLs).

  • countrieslist[str] | Nonedefault None

    Only these countries, as ISO 3166 alpha-2 codes such as US.

  • device_typeslist[str] | Nonedefault None

    Only these device types, e.g. desktop, mobile, tablet or tv.

  • embed_domainslist[str] | Nonedefault None

    Only plays embedded on these domains.

  • sortstr | Nonedefault None

    Metric to sort by, e.g. views, impressions, finishes or total_seconds_watched.

  • directionstr | Nonedefault None

    asc or desc. Applies to sort.

  • user_idstr | Nonedefault None

    Numeric Vimeo user ID of a team owner, to read that team's content as a team member. Omit for the connected account.

  • cursorstr | Nonedefault None

    next_cursor from the previous page. Omit for the first page.

  • limitintdefault 100

    Rows 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().

WordPress​

qbash.integrations.wordpress16 methods · WordPress connector
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 | Nonedefault None

    Only items matching this text.

  • afterstr | Nonedefault None

    Only items published after this ISO 8601 datetime, in the site's timezone.

  • beforestr | Nonedefault None

    Only items published before this ISO 8601 datetime, in the site's timezone.

  • modified_afterstr | Nonedefault None

    Only items modified after this ISO 8601 datetime. Pair with orderby="modified" and order="asc" for incremental syncs.

  • authorint | Nonedefault None

    Only items by this user id.

  • categorieslist[int] | Nonedefault None

    Only posts in any of these category ids.

  • tagslist[int] | Nonedefault None

    Only posts with any of these tag ids.

  • orderbystr | Nonedefault None

    date (the default), modified, title, id, slug, relevance (with search) or menu_order (pages).

  • orderstr | Nonedefault None

    desc (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_contentbooldefault False

    Add each item's rendered content. Off by default to keep pages small.

  • cursorstr | Nonedefault None

    next_cursor from the previous page (a page number). Omit for the first page.

  • limitintdefault 25

    Items 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_idintrequired

    Post 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 | Nonedefault None

    Only items matching this text.

  • afterstr | Nonedefault None

    Only items published after this ISO 8601 datetime, in the site's timezone.

  • beforestr | Nonedefault None

    Only items published before this ISO 8601 datetime, in the site's timezone.

  • modified_afterstr | Nonedefault None

    Only items modified after this ISO 8601 datetime. Pair with orderby="modified" and order="asc" for incremental syncs.

  • authorint | Nonedefault None

    Only items by this user id.

  • parentint | Nonedefault None

    Only children of this page id. 0 lists top-level pages.

  • orderbystr | Nonedefault None

    date (the default), modified, title, id, slug, relevance (with search) or menu_order (pages).

  • orderstr | Nonedefault None

    desc (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_contentbooldefault False

    Add each item's rendered content. Off by default to keep pages small.

  • cursorstr | Nonedefault None

    next_cursor from the previous page (a page number). Omit for the first page.

  • limitintdefault 25

    Items 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_idintrequired

    Page 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 | Nonedefault None

    Only comments on this post id.

  • statusstrdefault "approve"

    approve, hold or spam.

  • searchstr | Nonedefault None

    Only items matching this text.

  • afterstr | Nonedefault None

    Only items published after this ISO 8601 datetime, in the site's timezone.

  • parentint | Nonedefault None

    Only 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 | Nonedefault None

    next_cursor from the previous page (a page number). Omit for the first page.

  • limitintdefault 25

    Items 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 | Nonedefault None

    Only items matching this text.

  • parentint | Nonedefault None

    Only children of this category id. 0 lists top-level categories.

  • hide_emptybooldefault False

    Leave out terms with no posts.

  • cursorstr | Nonedefault None

    next_cursor from the previous page (a page number). Omit for the first page.

  • limitintdefault 25

    Items 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 | Nonedefault None

    Only items matching this text.

  • hide_emptybooldefault False

    Leave out terms with no posts.

  • cursorstr | Nonedefault None

    next_cursor from the previous page (a page number). Omit for the first page.

  • limitintdefault 25

    Items 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 | Nonedefault None

    Only items matching this text.

  • roleslist[str] | Nonedefault None

    Only 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 | Nonedefault None

    next_cursor from the previous page (a page number). Omit for the first page.

  • limitintdefault 25

    Items 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 | Nonedefault None

    Only items matching this text.

  • media_typestr | Nonedefault None

    image, video, text, application or audio.

  • parentint | Nonedefault None

    Only media attached to this post id.

  • afterstr | Nonedefault None

    Only items published after this ISO 8601 datetime, in the site's timezone.

  • cursorstr | Nonedefault None

    next_cursor from the previous page (a page number). Omit for the first page.

  • limitintdefault 25

    Items 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",
)
  • querystrrequired

    Text to search for.

  • typestrdefault "post"

    post, term or post-format.

  • subtypestrdefault "any"

    post, page or any. With type="term": category or tag.

  • cursorstr | Nonedefault None

    next_cursor from the previous page (a page number). Omit for the first page.

  • limitintdefault 25

    Items 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="…",
)
  • titlestrrequired

    Post title.

  • statusstrdefault "draft"

    draft, pending, private, publish or future. publish and future need Author or above.

  • contentstr | Nonedefault None

    Post body as HTML or block markup.

  • excerptstr | Nonedefault None

    Manual excerpt.

  • categorieslist[int] | Nonedefault None

    Category ids from list_categories. Replaces the post's categories.

  • tagslist[int] | Nonedefault None

    Tag ids from list_tags. Replaces the post's tags.

  • slugstr | Nonedefault None

    URL slug.

  • datestr | Nonedefault None

    Publish date as an ISO 8601 datetime in the site's timezone. A future date with status future schedules the post.

  • featured_mediaint | Nonedefault None

    Media 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_idintrequired

    Post id.

  • titlestr | Nonedefault None

    New title.

  • statusstr | Nonedefault None

    draft, pending, private, publish or future. publish and future need Author or above.

  • contentstr | Nonedefault None

    Post body as HTML or block markup.

  • excerptstr | Nonedefault None

    Manual excerpt.

  • categorieslist[int] | Nonedefault None

    Category ids from list_categories. Replaces the post's categories.

  • tagslist[int] | Nonedefault None

    Tag ids from list_tags. Replaces the post's tags.

  • slugstr | Nonedefault None

    URL slug.

  • datestr | Nonedefault None

    Publish date as an ISO 8601 datetime in the site's timezone. A future date with status future schedules the post.

  • featured_mediaint | Nonedefault None

    Media 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_idintrequired

    Post id to comment on.

  • contentstrrequired

    Comment text. Basic HTML is allowed.

  • parentint | Nonedefault None

    Comment 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_idintrequired

    Comment id.

  • statusstrrequired

    approve, 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 | Nonedefault None

    Last day of the range as YYYY-MM-DD. Defaults to today in the site's timezone.

  • numintdefault 1

    Number of periods to cover, from 1 through 90.

  • maxintdefault 10

    top_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.

X​

qbash.integrations.x13 methods · X connector
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="…",
)
  • textstrrequired

    Tweet text (max 280 characters).

  • reply_to_tweet_idstr | Nonedefault None

    Tweet ID to reply to.

  • media_idslist[str] | Nonedefault None

    Media 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"],
)
  • filefilerequired

    The 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_secondsintdefault 60

    How 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_idstrrequired

    The 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="…",
)
  • usernamestrrequired

    X 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_idstrrequired

    Numeric 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_idstrrequired

    Numeric 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_idstrrequired

    Numeric user ID string.

  • max_resultsintdefault 10

    Max tweets to return (5 to 100).

  • pagination_tokenstr | Nonedefault None

    Pagination token from a previous response.

  • start_timestr | Nonedefault None

    ISO 8601 datetime for the earliest tweet to include.

  • end_timestr | Nonedefault None

    ISO 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",
)
  • querystrrequired

    X API v2 search query string.

  • max_resultsint | Nonedefault 10

    Number of results to return (10 to 100).

  • allowed_x_handlesstr | Nonedefault None

    Comma-separated handles to restrict results to.

  • excluded_x_handlesstr | Nonedefault None

    Comma-separated handles to exclude from results.

  • from_datestr | Nonedefault None

    Start date in YYYY-MM-DD format.

  • to_datestr | Nonedefault None

    End 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="…",
)
  • usernamestrrequired

    X username without the @ prefix.

  • max_resultsint | Nonedefault 10

    Number 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",
)
  • querystrrequired

    X 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="…",
)
  • usernamestrrequired

    X username without the @ prefix.

  • max_resultsint | Nonedefault 10

    Number of tweets to return (5 to 100).

  • excludestr | Nonedefault None

    Comma-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 | Nonedefault 1

    Where 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.

qbash.integrations.grok_search1 method · XAI Grok Search connector
model_search()Search X posts using Grok AI via the xAI Responses API.
result = qbash.integrations.grok_search.model_search(
query="search terms",
)
  • querystrrequired

    Search query to run against X posts.

  • modelstr | Nonedefault "grok-4.20-0309-non-reasoning"

    xAI model to use for the search.

  • allowed_x_handlesstr | Nonedefault None

    Comma-separated X handles to restrict the search to. Up to 10 are used.

  • excluded_x_handlesstr | Nonedefault None

    Comma-separated X handles to exclude from the search. Up to 10 are used.

  • from_datestr | Nonedefault None

    Earliest post date to search, in YYYY-MM-DD format.

  • to_datestr | Nonedefault None

    Latest 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.

Xero​

qbash.integrations.xero21 methods · Xero connector
create_invoice()Create a new sales invoice.
result = qbash.integrations.xero.create_invoice(
contact_id="…",
)
  • contact_idstrrequired

    Contact ID.

  • line_itemslist | Nonedefault None

    Line Items (JSON array), e.g. [{"description":"...","quantity":1,"unit_amount":100,"account_code":"200"}].

  • due_datestr | Nonedefault None

    Due Date (YYYY-MM-DD).

  • invoice_numberstr | Nonedefault None

    Invoice Number.

  • referencestr | Nonedefault None

    Reference.

{"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_idstrrequired

    Invoice ID.

  • statusstr | Nonedefault None

    New status (DRAFT / SUBMITTED / AUTHORISED / VOIDED / PAID).

  • due_datestr | Nonedefault None

    Due 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_idstrrequired

    Invoice 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 | Nonedefault None

    Filter by status (DRAFT / SUBMITTED / AUTHORISED / PAID / VOIDED).

  • contact_idstr | Nonedefault None

    Filter by Contact ID.

  • pagestr | Nonedefault None

    Page 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_idstrrequired

    Invoice 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_idstrrequired

    Invoice 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_idstrrequired

    Contact ID.

  • line_itemslist | Nonedefault None

    Line Items (JSON array).

  • due_datestr | Nonedefault None

    Due Date (YYYY-MM-DD).

  • statusstr | Nonedefault None

    Status (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 | Nonedefault None

    Page 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="…",
)
  • namestrrequired

    Contact name.

  • email_addressstr | Nonedefault None

    Email Address.

  • phonestr | Nonedefault None

    Phone number.

  • is_customerstr | Nonedefault None

    Is Customer (true/false).

  • is_supplierstr | Nonedefault None

    Is 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_idstrrequired

    Contact ID.

  • namestr | Nonedefault None

    New name.

  • email_addressstr | Nonedefault None

    Email 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_idstrrequired

    Contact 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 | Nonedefault None

    Where (filter) expression (e.g. IsCustomer==true).

  • pagestr | Nonedefault None

    Page number for pagination.

  • page_sizeint | Nonedefault None

    Contacts per page (1-1000). Xero uses 100 when omitted.

  • search_termstr | Nonedefault None

    Case 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_idstrrequired

    Invoice ID.

  • account_idstrrequired

    Account ID (bank account to pay from).

  • amountstrrequired

    Payment amount.

  • datestr | Nonedefault None

    Payment date (YYYY-MM-DD, defaults to today).

  • referencestr | Nonedefault None

    Reference.

{"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 | Nonedefault None

    Filter by status (AUTHORISED / DELETED).

  • pagestr | Nonedefault None

    Page 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_idstrrequired

    Contact ID.

  • line_itemslist | Nonedefault None

    Line Items (JSON array).

  • typestr | Nonedefault None

    Credit 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 | Nonedefault None

    Filter 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_idstrrequired

    Contact ID.

  • line_itemslist | Nonedefault None

    Line Items (JSON array).

  • delivery_datestr | Nonedefault None

    Delivery 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_idstrrequired

    Purchase 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="…",
)
  • codestrrequired

    Item code (unique identifier).

  • namestrrequired

    Item name.

  • descriptionstr | Nonedefault None

    Item description.

  • sales_unit_pricestr | Nonedefault None

    Sales Unit Price.

  • purchase_unit_pricestr | Nonedefault None

    Purchase 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 | Nonedefault None

    Account 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.

YouTube​

qbash.integrations.youtube24 methods · YouTube connector
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="…",
)
  • titlestrrequired

    Video title, max 100 characters.

  • file_idfile | Nonedefault None

    The 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 | Nonedefault None

    Base64-encoded video bytes, as an alternative to file_id. Bounded by the 50 MB request cap, so prefer file_id.

  • descriptionstr | Nonedefault None

    Video description, max 5000 characters.

  • tagslist | Nonedefault None

    Keyword 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 | Nonedefault None

    RFC 3339 timestamp to schedule publication. Requires privacy_status=private.

  • made_for_kidsbool | Nonedefault None

    Declare the video as child-directed.

  • notify_subscribersbooldefault True

    Whether to send a new-upload notification to subscribers.

  • content_typestr | Nonedefault None

    MIME 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_idstrrequired

    YouTube 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_idstrrequired

    YouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).

  • file_idfile | Nonedefault None

    The 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 | Nonedefault None

    Base64-encoded image bytes, as an alternative to file_id.

  • content_typestr | Nonedefault None

    image/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_idstrrequired

    YouTube 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 | Nonedefault None

    RFC 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_resultsintdefault 25

    Number of videos to return, 1-50.

  • page_tokenstr | Nonedefault None

    Pagination cursor returned as next_page_token by a previous call.

  • channel_idstr | Nonedefault None

    List 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_idstrrequired

    YouTube 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_idstrrequired

    YouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).

  • titlestr | Nonedefault None

    New title, max 100 characters.

  • descriptionstr | Nonedefault None

    New description, max 5000 characters.

  • tagslist | Nonedefault None

    Replacement tag list. Accepts a list or a comma-separated string. Pass an empty list to clear.

  • category_idstr | Nonedefault None

    New YouTube category ID as a string.

  • privacy_statusstr | Nonedefault None

    private, public, or unlisted.

  • publish_atstr | Nonedefault None

    RFC 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 | Nonedefault None

    youtube or creativeCommon.

  • embeddablebool | Nonedefault None

    Whether the video can be embedded on other sites.

  • public_stats_viewablebool | Nonedefault None

    Whether view counts are publicly visible on the watch page.

  • made_for_kidsbool | Nonedefault None

    Declare the video as child-directed.

  • contains_synthetic_mediabool | Nonedefault None

    Disclose 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_idstrrequired

    YouTube 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_idstrrequired

    YouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).

  • ratingstrrequired

    like, 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 | Nonedefault None

    Search terms. Supports - to exclude and | for OR.

  • typestrdefault "video"

    video, channel, or playlist.

  • channel_idstr | Nonedefault None

    Restrict results to one channel.

  • for_minebooldefault False

    Restrict to your own videos. Forces type to video. Prefer list_videos, which costs no search quota.

  • orderstr | Nonedefault None

    relevance, date, rating, title, videoCount, or viewCount.

  • max_resultsintdefault 25

    Number of results, 1-50.

  • page_tokenstr | Nonedefault None

    Pagination cursor returned as next_page_token by a previous call.

  • published_afterstr | Nonedefault None

    RFC 3339 timestamp lower bound (e.g. 2026-01-01T00:00:00Z).

  • published_beforestr | Nonedefault None

    RFC 3339 timestamp upper bound.

  • video_durationstr | Nonedefault None

    short (<4 min), medium (4-20 min), long (>20 min), or any.

  • region_codestr | Nonedefault None

    Two-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_resultsintdefault 25

    Number of playlists to return, 1-50.

  • page_tokenstr | Nonedefault None

    Pagination cursor returned as next_page_token by a previous call.

  • channel_idstr | Nonedefault None

    List 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="…",
)
  • titlestrrequired

    Playlist title, max 150 characters.

  • descriptionstr | Nonedefault None

    Playlist 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_idstrrequired

    YouTube 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_idstrrequired

    YouTube playlist ID.

  • max_resultsintdefault 25

    Number of items to return, 1-50.

  • page_tokenstr | Nonedefault None

    Pagination 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_idstrrequired

    YouTube playlist ID.

  • video_idstrrequired

    YouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).

  • positionint | Nonedefault None

    Zero-based insertion index. Appends to the end when omitted.

  • notestr | Nonedefault None

    Private 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_idstrrequired

    YouTube playlist ID.

  • video_idstrrequired

    YouTube 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_idstrrequired

    YouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).

  • max_resultsintdefault 20

    Number of threads to return, 1-100.

  • page_tokenstr | Nonedefault None

    Pagination cursor returned as next_page_token by a previous call.

  • orderstrdefault "time"

    time (newest first) or relevance.

  • search_termsstr | Nonedefault None

    Only 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 | Nonedefault None

    YouTube channel ID. Resolves your own channel when omitted.

  • max_resultsintdefault 20

    Number of threads to return, 1-100.

  • page_tokenstr | Nonedefault None

    Pagination cursor returned as next_page_token by a previous call.

  • orderstrdefault "time"

    time (newest first) or relevance.

  • search_termsstr | Nonedefault None

    Only 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_idstrrequired

    ID of the top-level comment to reply to. Use the thread_id or comment_id from list_video_comments.

  • textstrrequired

    Reply 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 | Nonedefault None

    YouTube 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_datestrrequired

    First day of the reporting period, YYYY-MM-DD (e.g. 2026-07-01).

  • end_datestrrequired

    Last 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 | Nonedefault None

    Comma-separated grouping, e.g. day, month, country, ageGroup,gender, deviceType, insightTrafficSourceType.

  • filtersstr | Nonedefault None

    Semicolon-separated filters, e.g. country==US;deviceType==MOBILE.

  • sortstr | Nonedefault None

    Comma-separated sort columns. Prefix with - for descending.

  • max_resultsint | Nonedefault None

    Maximum number of rows to return.

  • channel_idstr | Nonedefault None

    Report 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_idstrrequired

    YouTube video ID (the 11-character code, e.g. dQw4w9WgXcQ).

  • start_datestrrequired

    First day of the reporting period, YYYY-MM-DD (e.g. 2026-07-01).

  • end_datestrrequired

    Last day of the reporting period, YYYY-MM-DD. Inclusive.

  • metricsstrdefault "views,estimatedMinutesWatched,averageViewDuration,averageViewPercentage"

    Comma-separated metrics.

  • dimensionsstr | Nonedefault None

    Comma-separated grouping, e.g. day, country, or elapsedVideoTimeRatio for retention curves.

  • filtersstr | Nonedefault None

    Extra semicolon-separated filters, merged with the video filter.

  • sortstr | Nonedefault None

    Comma-separated sort columns. Prefix with - for descending.

  • max_resultsint | Nonedefault None

    Maximum 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_datestrrequired

    First day of the reporting period, YYYY-MM-DD (e.g. 2026-07-01).

  • end_datestrrequired

    Last 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_resultsintdefault 10

    Number 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_datestrrequired

    First day of the reporting period, YYYY-MM-DD (e.g. 2026-07-01).

  • end_datestrrequired

    Last day of the reporting period, YYYY-MM-DD. Inclusive.

  • video_idstr | Nonedefault None

    Scope to a single video. Omit for the whole channel.

  • metricsstrdefault "views,estimatedMinutesWatched"

    Comma-separated metrics.

  • max_resultsint | Nonedefault None

    Maximum number of rows to return.

{"rows": list[dict], "column_headers": list[{"name": str, "data_type": str, "column_type": str}], "row_count": int}

Zendesk​

qbash.integrations.zendesk19 methods
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_idintrequired

    Zendesk 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",
)
  • querystrrequired

    Zendesk search query, for example status<solved priority:urgent or requester:jo@example.com.

  • sort_bystr | Nonedefault None

    created_at, updated_at, priority, status or ticket_type. Defaults to relevance.

  • sort_orderstr | Nonedefault None

    asc or desc.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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_idintrequired

    Zendesk ticket id.

  • visibilitystrdefault "all"

    all, public or internal.

  • limitintdefault 100

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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_idintrequired

    Zendesk ticket id.

  • include_eventsbooldefault False

    Also 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_idintrequired

    Zendesk ticket id.

  • limitintdefault 100

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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_idintrequired

    Attachment 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_idintrequired

    Zendesk 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",
)
  • querystrrequired

    Name, email, phone number or external id to look for.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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_idintrequired

    Zendesk 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",
)
  • querystrrequired

    Zendesk search query, for example qbash or tags:enterprise.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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_idintrequired

    Group 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="…",
)
  • limitintdefault 100

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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_idintrequired

    View id from list_views.

  • limitintdefault 25

    Items per page, from 1 through 100.

  • cursorstr | Nonedefault None

    next_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 | Nonedefault None

    ISO 8601 time to start from on the first call, such as 2026-10-14T09:00:00Z. Defaults to 24 hours ago.

  • cursorstr | Nonedefault None

    next_cursor from the previous call. Takes precedence over since.

  • limitintdefault 50

    Tickets per call, from 1 through 100.

  • exclude_own_changesbooldefault True

    Leave 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_idintrequired

    Zendesk ticket id.

  • bodystr | Nonedefault None

    Note text. Pass this or html_body.

  • html_bodystr | Nonedefault None

    Note as HTML. Pass this or body.

  • dedupe_keystr | Nonedefault None

    Skips 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_idintrequired

    Zendesk ticket id.

  • statusstr | Nonedefault None

    new, open, pending, hold or solved.

  • custom_status_idint | Nonedefault None

    Custom status id from get_routing_options.

  • prioritystr | Nonedefault None

    urgent, high, normal or low.

  • typestr | Nonedefault None

    problem, incident, question or task.

  • group_idint | Nonedefault None

    Group id from get_routing_options.

  • assignee_idint | Nonedefault None

    Agent id from list_assignable_agents for the ticket's group.

  • custom_fieldsdict | Nonedefault None

    Ticket field id (or exact title) to new value, such as {"360001": "QB-42"}.

  • add_tagslist[str] | Nonedefault None

    Tags to add.

  • remove_tagslist[str] | Nonedefault None

    Tags to remove.

  • skill_value_idslist[int] | Nonedefault None

    Skill value ids from get_routing_options. Replaces the ticket's skills.

  • internal_notestr | Nonedefault None

    Internal note to add with the change.

  • expected_updated_atstr | Nonedefault None

    The ticket's updated_at from your earlier read. The update is refused if the ticket changed since.

  • dedupe_keystr | Nonedefault None

    Skips 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.

Zoom​

qbash.integrations.zoom11 methods · Zoom connector
search_meetings()Search past Zoom meetings by date range and optional topic keyword.
result = qbash.integrations.zoom.search_meetings(
from_date="…",
to_date="…",
)
  • from_datestrrequired

    Start date (YYYY-MM-DD)

  • to_datestrrequired

    End date (YYYY-MM-DD)

  • topicstr

    Filter by meeting topic keyword

  • limitintdefault 30

    Max 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_idstrrequired

    Zoom meeting ID or UUID

dict

The 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_idstrrequired

    Zoom meeting ID or UUID

  • limitintdefault 100

    Max 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_idstrrequired

    Zoom meeting ID or UUID

  • formatstrdefault text

    Output 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_idstrrequired

    Zoom 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_idstrrequired

    Zoom meeting ID or UUID

dict

The 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_idstrrequired

    Zoom meeting ID or UUID

dict

The 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_idstrrequired

    Zoom meeting ID or UUID

dict

The 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,
)
  • statusstrdefault active

    Filter by status: active, inactive, or pending

  • limitintdefault 100

    Max users to return. Ignored when page_size or next_page_token is set.

  • page_sizeint | Nonedefault None

    Users 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 | Nonedefault None

    The 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_datestrrequired

    Start date (YYYY-MM-DD)

  • to_datestrrequired

    End date (YYYY-MM-DD)

  • typestrdefault past

    Meeting type: past, pastOne, or live

  • limitintdefault 100

    Max 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_idstrrequired

    Zoom user ID or email address

  • from_datestrrequired

    Start date (YYYY-MM-DD)

  • to_datestrrequired

    End date (YYYY-MM-DD)

  • topicstr

    Filter by meeting topic keyword

  • limitintdefault 30

    Max 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.

ZoomInfo​

qbash.integrations.zoominfo7 methods
search_contacts()Search for B2B contacts by name, title, company, or location.
result = qbash.integrations.zoominfo.search_contacts(
firstName="…",
lastName="…",
)
  • firstNamestr | Nonedefault None

    First name filter.

  • lastNamestr | Nonedefault None

    Last name filter.

  • emailstr | Nonedefault None

    Email address filter.

  • jobTitlestr | Nonedefault None

    Job title filter.

  • companyNamestr | Nonedefault None

    Company name filter.

  • companyIdstr | Nonedefault None

    ZoomInfo company ID to restrict the search to.

  • departmentstr | Nonedefault None

    Department filter.

  • managementLevelstr | Nonedefault None

    Management level filter (e.g. 'Director', 'VP', 'C-Level').

  • countrystr | Nonedefault None

    Country filter.

  • statestr | Nonedefault None

    State/region filter.

  • pageint | Nonedefault 1

    Page number for paginated results.

  • pageSizeint | Nonedefault 25

    Results 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 | Nonedefault None

    Company name filter.

  • websitestr | Nonedefault None

    Company website filter.

  • industrystr | Nonedefault None

    Industry filter.

  • revenueMinstr | Nonedefault None

    Minimum annual revenue filter.

  • revenueMaxstr | Nonedefault None

    Maximum annual revenue filter.

  • employeeCountMinint | Nonedefault None

    Minimum employee count filter.

  • employeeCountMaxint | Nonedefault None

    Maximum employee count filter.

  • countrystr | Nonedefault None

    Country filter.

  • statestr | Nonedefault None

    State/region filter.

  • pageint | Nonedefault 1

    Page number for paginated results.

  • pageSizeint | Nonedefault 25

    Results 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=[...],
)
  • topicslistrequired

    List of intent topic strings to search for. A comma-separated string is also accepted.

  • companyNamestr | Nonedefault None

    Restrict signals to this company name.

  • companyIdstr | Nonedefault None

    Restrict signals to this ZoomInfo company ID.

  • countrystr | Nonedefault None

    Country filter.

  • pageint | Nonedefault 1

    Page number for paginated results.

  • pageSizeint | Nonedefault 25

    Results 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 | Nonedefault None

    ZoomInfo person IDs to enrich, as a list or a comma-separated string. Takes precedence over the name, email, and company fields.

  • emailstr | Nonedefault None

    Contact email address to enrich.

  • firstNamestr | Nonedefault None

    First name for matching.

  • lastNamestr | Nonedefault None

    Last name for matching.

  • companyNamestr | Nonedefault None

    Company name for matching.

  • outputFieldsstr | list | Nonedefault None

    Specific 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 | Nonedefault None

    ZoomInfo company IDs to enrich, as a list or a comma-separated string. Takes precedence over companyName and website.

  • companyNamestr | Nonedefault None

    Company name to enrich.

  • websitestr | Nonedefault None

    Company website used for matching when no company ID is given.

  • outputFieldsstr | list | Nonedefault None

    Specific 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=[...],
)
  • topicslistrequired

    List of intent topic strings. A comma-separated string is also accepted.

  • companyIdstr | Nonedefault None

    ZoomInfo company ID to get intent for.

  • companyNamestr | Nonedefault None

    Company name to get intent for when no company ID is known.

  • companyWebsitestr | Nonedefault None

    Company 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.