API reference Build guide ← Back to apps
Quality Standard · 85 governed operations

One API for how the firm actually operates.

This API is a governed mirror of Quality Standard's operations — the pipeline, the decisions, the outreach, and the audit log of everything that happened. Every call is authenticated, audited, and identity-stamped; the same rules apply whether a partner runs an operation in chat or an app calls it at 3am.

For agents — MCP

Connect an AI agent (Claude, or any MCP client) and every operation below is a tool. Discovery is built in: the API describes itself.

https://qualitystandard.onrubicon.com/mcp

For apps & code — HTTP

POST any operation by name with an rk_live_ bearer key (or the signed-in session inside a hosted app). Same dispatch, same audit row.

POST /v1/call/<operation>
Golden rule

Call any operation with dry_run: true to get a free, schema-exact sample request and response — no side effects, no quota, no writes. Do it before wiring anything; the sample is generated from the same contract the server enforces.

  • Retry-safe by default. State-changing operations are idempotent — the platform derives a key from the call itself, so a network retry never double-fires. Supplying your own idempotency_key is optional; the same key with different input is rejected with 409.
  • Attribution is automatic. The signed-in identity is stamped server-side on every call. Decisions name their actor explicitly (acting_as) — a human or a registered AI model, never an anonymous system.
  • Writes are two-phase or fail loud. Field writes go preview → confirm with a signed token; operation writes validate everything up front and throw on refusal. Nothing lands silently.
  • Demo vs live. In demo mode external systems (Salesforce, Grata, M365…) are stubbed — zero quota, no real writes. Live is a deliberate switch.

Build an app

Hosted apps are capsules: a small React front end plus a tiny server that is the only thing allowed to touch the API. You author three files; the platform builds, type-checks, hosts, and versions the rest.

  1. Ask for the template

    One dry run returns a complete, working capsule — copy it, rename it, extend it. It also carries the hard rules the build enforces.

    deploy_app_source { dry_run: true }
  2. Author three files

    The manifest declares every operation your server calls; the rest of the boilerplate (index.html, bundling, deps) is scaffolded for you.

    rubicon.app.jsonsrc/App.tsxserver/index.ts
  3. The browser talks only to its own routes

    Front-end code calls ./api/* and nothing else — your Hono routes in server/index.ts are auto-mounted under /api.

    fetch("./api/leads")app.get("/leads", …)
  4. Every server effect goes through c.env.rubicon

    Reads return refusals as data — check r.ok === false and surface it, never render an empty page over one. Writes throw — always try/catch.

    await c.env.rubicon.read("…")await c.env.rubicon.write("…")
  5. Background work is a worker, not a browser loop

    Declare export const workers and drive them with startJob / getJob — durable, server-side, survives the tab closing.

    export const workers = { … }rubicon.startJob("name", args)rubicon.getJob(job_id)
  6. Iterate safely, roll back instantly

    Fetch the app's map, patch just the files you change — a failed build never touches the live version. Every version keeps its author, summary, and a one-call restore.

    get_app_contextupdate_app_sourcelist_app_versionsrollback_app

Operations

Every operation, grouped by kind. Click a row to expand its inputs, an example call, and the response shape — or dry-run it for the exact live contract.

No operations match that filter.

Discovery the API that describes itself

Usually the first calls an agent makes: ask what exists, then get the exact contract before acting.

list_operations · list_signals · list_decision_types · list_models · list_capabilities · describe_handoffReadAsk what exists, then get one op's full contract.

Ask what exists before you call it — operations, governed writes, recordable decisions, the LLM menu, and which platform features are switched on — then get the exact contract for any one operation with describe_handoff.

Inputs
OpInputsReturns
list_operationsdomain req (e.g. sourcing), include_summaries?, include_eligibility?Operations + what each can write/decide.
list_signalsdomain?, object_type?signal_key → target field mappings.
list_decision_typesdomain?, include_evidence_schemas?Decision types + evidence schema + allowed actors.
list_models— (none)The infer model catalog.
list_capabilities— (none)Which platform features are on, and the current + your pinned API version.
describe_handoffoperation req, include_past_examples?One op's full overlay: semantics, evidence shape, allowed actors, attributed past examples.
Example request
await rubicon.call('describe_handoff', { operation: 'op_screen_leads', include_past_examples: true })

Remember: every operation also answers dry_run: true with a vetted sample request + response.

Reads live state, never a stale export

Your real pipeline and records at call time. Reads are lean by default and return refusals as data.

list_pipeline_by_stageReadThe app workhorse — leads at a stage, newest first.

Lists the pipeline rows sitting at one sourcing stage — the read most apps are built on. Lean by default and sorted newest-first; each row carries a stable candidate_ref and a canonical block of display fields.

Inputs
FieldTypeNotes
sourcing_stagestringrequirede.g. needs-triage, ready-for-review, outreach-drafted.
industrystring | string[]optRestrict to bucket(s). Omitted → all.
limitintegeroptCap the page. Rows come newest-first.
Example request
const r = await c.env.rubicon.read('list_pipeline_by_stage', { sourcing_stage: 'needs-triage', limit: 50 });
if (r.ok === false) return c.json({ leads: [], error: r.code }, 502);  // refusals are data — surface them
return c.json({ leads: r.leads ?? [] });
get_pipeline_stateReadCounts by stage, what fires next, time-travel.

Counts by stage, an optional preview of what fires next per entity, and time-travel — reconstruct the pipeline as of any timestamp straight from the audit log (no snapshot store).

Inputs
FieldTypeNotes
pipeline_domainstringrequirede.g. sourcing — picks the stage taxonomy.
filterobjectopte.g. {stage:'partner_review'}, {industry:'…'}.
as_of_isostringoptTime-travel point — "the pipeline as of March 1".
include_what_fires_nextbooleanoptPer entity, preview the next signals.
Example request
await rubicon.call('get_pipeline_state', { pipeline_domain: 'sourcing', include_what_fires_next: true })
Example response
{
  "counts_by_stage": { "sourced": 120, "ready-for-review": 31,
    "outreach-drafted": 14, "outreach-approved": 9 },
  "what_fires_next": [{ "website": "https://acme-cad.com", "next": "op_partner_review" }]
}
get_entityReadOne business object, live from the source system.

One business object read live from the source system, with optional per-field provenance, write-eligibility hints, and resolved links to related records.

Inputs
FieldTypeNotes
entity_typeenumrequiredaccountopportunitycontactleadcadenceemail_messagecalendar_eventcalendly_action
entity_idstringrequiredSource-system Id.
fieldsstring[]optSubset; defaults to a curated set per type.
include_per_field_provenance · include_governance_hints · include_linksbooleanoptAttach source/last-modified, write-eligibility, related entities.
Example request
await rubicon.call('get_entity', { entity_type: 'opportunity', entity_id: '006Ab00000XyZ', include_links: true })
search_external_signalReadSearch any registered external source.

A wrapped search over any registered external source — find or confirm a signal before you act on it. Criteria are validated per-source by the adapter.

Inputs
FieldTypeNotes
sourceenumrequiredgratahuntersalesforcesalesloftwebsitepipelinesharepointoutlook
query_typestringrequiredPer-source (e.g. company_search, email_finder).
criteriaobjectrequiredOpen shape — validated by the adapter.
Example request
await rubicon.call('search_external_signal', {
  source: 'hunter', query_type: 'email_finder',
  criteria: { domain: 'acme-cad.com', first_name: 'Dana', last_name: 'Reyes' }
})
audit_trailReadQuery the accountability log.

Query the accountability log: every governed action, filterable and aggregatable. Answers "why did we contact company X?" or "which partner has the highest screen-to-LOI rate?"

Inputs
FieldTypeNotes
filterobjectAND-combined: entity_id, acting_as, via_app, decision_type, signal_key, occurred_after/before, and outcomepermitted_writestage_decisionpermitted_readrefused_readerror
aggregationsarrayGroup-by + count, e.g. [{group_by:'acting_as', count:'decision_type'}].
limit · cursorinteger · stringPagination.
Example request
await rubicon.call('audit_trail', { filter: { entity_id: 'https://acme-cad.com' }, limit: 20 })
scrape_kickoff · scrape_statusReadAsync website/LinkedIn enrichment — start and poll.

scrape_kickoff starts the deduped background website/LinkedIn enrichment for a scope of rows; scrape_status polls it by per-website job id (poll freely — status checks aren't metered). Dry-run either for the exact contract.

Preps the backlog before each judgment

Read-only worklists — the legwork before each human (or named-AI) gate. All share the same filters.

prep_for_screen_leads · prep_for_partner_review · prep_for_draft_outreach · prep_for_approve_outreachReadThe exact rows still needing that step.

Each returns the exact backlog for the gate that follows it — the rows that still need that step — enriched and ready to decide on. They differ only in which rows they surface:

OpReturns rows where…
prep_for_screen_leadsfreshly sourced, not yet screened.
prep_for_partner_reviewPartner Review is still blank.
prep_for_draft_outreachPartner Review = yes — approved, ready to draft. Brings the proven cadence copy.
prep_for_approve_outreachPartner Review = yes AND Email Draft = draft — drafted, awaiting approval.
Inputs (shared — all optional, OR-combined; accept either spelling)
FieldTypeNotes
inputs.industry / industriesstring | string[]One Tier-2 bucket or a list. Omitted → scan all.
inputs.owner / ownersstring | string[]Owning partner(s) — actor id or display name.
inputs.websites / website_urlsstring | string[]Restrict to specific rows (the website PK).
inputs.enrichbooleanDefault true — backfill website/LinkedIn enrichment on returned rows.
inputs.include_dumpsbooleanResponse shaping only (default false) — attach raw scrape blobs.
Example request
await rubicon.call('prep_for_partner_review', {
  inputs: { industry: 'Public Sector Software', owner: 'human:beau@qualitystandard.com' }
})
Example response
{
  "status": "applied",
  "count": 12,
  "leads": [
    { "website": "https://acme-cad.com", "company_name": "Acme CAD",
      "sourcing_stage": "ready-for-review", "employees": 42,
      "enrichment": { "hq": "Austin, TX", "linkedin": "…" } }
  ]
}

Actions the sourcing workflow + governed verbs

A target from cold to first contact — plus the generic write, decision, model, and comms verbs underneath.

op_search_for_leadsWriteSource new candidates from Grata into an industry bucket.

Sources new candidate companies from Grata — one parallel search per term (up to 10) — dedupes each by website against both your Salesforce accounts/opportunities and the destination bucket, scrapes the site + LinkedIn for context, and files the survivors into the right industry bucket. The messy bit it owns: Grata's industry labels aren't QS's verticals, so every candidate is re-bucketed into your taxonomy before it lands.

Inputs
FieldTypeNotes
inputs.search_termsstring | string[]requiredGrata keyword(s), max 10. One parallel search each.
inputs.industrystring | objectoptWhere to file results. A QS Tier-2 bucket, or {tier2, tier3}. Omitted → resolved from the terms, else Misc.
inputs.min_employees / max_employeesintegeroptHeadcount band. Defaults 10150 when omitted.
inputs.year_foundedintegeroptFounded on/after this year.
inputs.location{city,state,country}optGeo filter. Defaults to US.
inputs.limitintegeroptMax results per term (default 50).
inputs.dedupe_against_salesforcebooleanoptDefault true — drops anything already in SF.
Example request
await rubicon.call('op_search_for_leads', {
  inputs: {
    search_terms: ['public safety records management', 'CAD dispatch software'],
    industry: 'Public Sector Software',
    min_employees: 15, max_employees: 120,
    location: { country: 'United States' }
  }
})
Example response
{
  "status": "applied",
  "outputs": {
    "counts": { "sourced": 63, "deduped_out": 18, "filed": 45 },
    "industry_bucket": "2-Public Sector Software",
    "candidates_sourced": [
      { "website": "https://acme-cad.com", "company_name": "Acme CAD",
        "employees": 42, "hq": "Austin, TX", "grata_industry": "Public Safety" }
    ]
  },
  "audit_chain_root": "a1b2c3d4-…"
}
op_screen_leadsDecisionRecord a yes/no fit verdict per company.

Records a yes/no fit verdict per company (keyed by website URL). yes keeps the row and moves it to ready-for-review; no removes it from the pipeline. One decision per primary key, each attributed and evidenced. An AI verdict (acting_as: 'ai:…') must disclose method — model, a non-empty reasoning summary, and an explicit list of any tools it ran.

Inputs
FieldTypeNotes
inputs.primary_keysstring[]requiredWebsite URLs to decide on — one decision each. (Inside a capsule, the host derives this from your decisions.)
inputs.industrystring | string[]optScopes which bucket(s) to search. Omitted → all.
decisions[].candidate_ref{entity_id}requiredentity_id = the website URL (binds the decision to the row).
decisions[].verdictenumrequiredyesno
decisions[].acting_asstringrequiredA registered actor id (not auto-mapped here).
decisions[].rationale · methodstring · objectoptBoth required when the actor is a named AI model.
Example request
await rubicon.call('op_screen_leads', {
  inputs: { primary_keys: ['https://acme-cad.com'], industry: 'Public Sector Software' },
  decisions: [{
    candidate_ref: { entity_id: 'https://acme-cad.com' },
    verdict: 'yes',
    acting_as: 'human:sufi@qualitystandard.com',
    rationale: '42 FTE, vertical CAD/RMS for municipal PD, founder-owned — fits the thesis.'
  }]
})
Example response
{
  "status": "applied",
  "results": [
    { "primary_key": "https://acme-cad.com", "verdict": "yes",
      "outcome": "kept · stage→ready-for-review", "decision_id": "d-7781…" }
  ],
  "audit_chain_root": "e5f6…"
}
op_partner_reviewPartner onlyThe partner's go / no-go on screened candidates.

The partner's go / no-go on screened candidates, keyed by website URL. Same decision shape as screening, but locked to a human partner — an app or model cannot stamp it. yes advances the row toward outreach; no drops it.

Inputs
FieldTypeNotes
inputs.primary_keysstring[]requiredWebsite URLs under review.
decisions[]object[]requiredOne per key: candidate_ref, verdict (yes/no), acting_as (a partner).
Example request
await rubicon.call('op_partner_review', {
  inputs: { primary_keys: ['https://acme-cad.com'] },
  decisions: [{ candidate_ref: { entity_id: 'https://acme-cad.com' },
    verdict: 'yes', acting_as: 'human:beau@qualitystandard.com' }]
})
op_draft_outreachWriteCompose and save the outbound email per company.

Composes the outbound email for one or more approved companies and saves it to the company's outreach record. Drafts start from what works — your top-performing Salesloft cadence emails for that motion are surfaced by prep_for_draft_outreach so the copy isn't from a blank page.

Inputs
FieldTypeNotes
inputs.outreach_targets[]object[]requiredOne per company: website (PK), email (the composed body), optional subject.
inputs.industrystring | string[]optNarrows which bucket(s) to write into.
Example request
await rubicon.call('op_draft_outreach', {
  inputs: { outreach_targets: [{
    website: 'https://acme-cad.com',
    subject: 'Quality Standard × Acme CAD',
    email: 'Hi Dana — we back vertical software founders in public safety…'
  }] }
})
op_approve_outreachPartner onlyApprove or reject drafted outreach.

A partner approves or rejects drafted outreach, keyed by website URL. Approve flips the row to outreach-approved and arms the send; reject sends it back. Authorship and approval sit with different people on purpose — nothing self-approves.

Inputs
FieldTypeNotes
inputs.accountsstring[]requiredWebsite URLs to approve/reject.
decisions[]object[]requiredOne per account: candidate_ref.website, verdict, acting_as (a partner).
Example request
await rubicon.call('op_approve_outreach', {
  inputs: { accounts: ['https://acme-cad.com'] },
  decisions: [{ candidate_ref: { website: 'https://acme-cad.com' },
    verdict: 'yes', acting_as: 'human:beau@qualitystandard.com' }]
})
op_move_company_between_industriesWriteRe-file a company between industry buckets.

Re-files one company between industry buckets — typically a lead that landed in Misc during sourcing or got bucketed wrong. Carries its outreach records along and logs the reason on the row.

Inputs
FieldTypeNotes
inputs.company_namestringrequiredAccount Name, matched verbatim.
inputs.source_industry · target_industrystringrequiredFrom-bucket and to-bucket (created if absent).
inputs.override_reasonstringrequiredWhy the move is warranted — persists in audit + on the row.
inputs.move_filesbooleanoptDefault true — bring outreach files too.
Example request
await rubicon.call('op_move_company_between_industries', {
  inputs: { company_name: 'Acme CAD',
    source_industry: 'Misc', target_industry: 'Public Sector Software',
    override_reason: 'Vertical CAD/RMS for municipal PD — belongs in Public Safety, not Misc.' }
})
apply_signal_preview → apply_signal_commitWriteA governed field write in two phases.

A governed field write in two phases. Preview evaluates expectations, shows the exact cascade, and returns a signed confirmation_token (5-min TTL). Commit requires that token — and an attested_override_reason if any soft expectation was unmet. The write lands in your system of record with a provenance breadcrumb.

Inputs (both phases)
FieldTypeNotes
signal_keyenumrequiredThe registered write. QS verbs:advance_stageflag_go_getset_prioritypass_leadlose_leadpromote_to_opportunitylaunch_cadencepause_cadenceclose_cadenceverified_email
object_typeenumrequiredaccountopportunitycontactleadcadence
object_id · valuestring · anyrequiredTarget Id + the new value (type matches the mapping).
acting_asstringrequiredRegistered actor.
confirmation_tokenstringcommitFrom the matching preview. (HTTP: X-Rubicon-Confirmation-Token header.)
attested_override_reasonstringoptRequired (≥10 chars) if a soft expectation was unmet at preview.
Example — preview, then commit
// 1) preview — evaluate + get the token
const p = await rubicon.call('apply_signal_preview', {
  signal_key: 'advance_stage', object_type: 'opportunity',
  object_id: '006Ab00000XyZ', value: 'Outreach',
  acting_as: 'human:beau@qualitystandard.com'
});
// 2) commit — only if you accept p.governance + p.irreversibility
await rubicon.call('apply_signal_commit', {
  signal_key: 'advance_stage', object_type: 'opportunity',
  object_id: '006Ab00000XyZ', value: 'Outreach',
  acting_as: 'human:beau@qualitystandard.com',
  confirmation_token: p.confirmation_token
});
Example preview response (abridged)
{
  "verb_phrase": "advance the deal stage",
  "object": { "type": "opportunity", "display_name": "Acme CAD" },
  "governance": { "dimension_4_preconditions": {
    "evaluated": [{ "name": "partner_review_passed", "result": "pass", "severity": "hard" }],
    "all_pass": true } },
  "irreversibility": { "level": "fully_reversible" },
  "confirmation_token": "wpf_9f3c….a4b1…",
  "token_expires_at": "2026-06-15T18:25:00Z",
  "will_proceed": true
}
stage_decisionDecisionRecord a subjective judgment with attribution + evidence.

The subjectivity boundary. Records a subjective judgment with mandatory attribution + evidence — Rubicon never originates a judgment. acting_as must be registered, and evidence is validated against the decision type's schema.

Inputs
FieldTypeNotes
decision_typestringrequiredA registered type, e.g. screening_decision. (See list_decision_types.)
acting_asstringrequiredHuman or named-AI actor id.
evidenceobjectrequiredShape validated against the decision type.
entity_ref{entity_type, entity_id}optWhat the decision is about; carries forward to downstream signals.
supersedes_decision_idstringoptRevises a prior decision (e.g. partner overrides an AI jury).
Example request
await rubicon.call('stage_decision', {
  decision_type: 'screening_decision',
  acting_as: 'ai:gpt-5.2',
  entity_ref: { entity_type: 'lead', entity_id: 'https://acme-cad.com' },
  evidence: { verdict: 'forward', score: 82,
    justification: 'Vertical CAD/RMS, founder-owned, 42 FTE.' }
})
inferApp runtimeThe firm's governed LLM call — keyless, typed, attributable.

The firm's governed LLM call. An app uses this instead of standing up its own model backend — the platform key lives server-side, so the app never holds a credential. Pass response_schema for strict typed JSON, or omit it for free text. Not an MCP tool — in a chat session the agent is the inference; infer is what a deployed app calls (from a capsule server: rubicon.read('infer', …), and list it in permissions.rubicon_ops).

Inputs
FieldTypeNotes
messages{role,content}[]requiredroleuser/assistant/system. (Or pass system separately.)
response_schemaJSON SchemaoptForces strict typed JSON back in output; omit for free text in output_text.
modelstringoptA name, an alias, or a selector (see catalog). Omit for the default.
include_grounded_searchbooleanoptDefault false. true grounds the answer in live web results + returns citations.
max_tokensintegeroptDefault 1024. Use ≥1000 for gpt-5* — they spend tokens on hidden reasoning first.
The model catalog — what each one is for
AliasBest forCostReasoningSpeedContextStructured
gpt-5.2DEFAULTBalanced flagship — screening / judgment calls.400k
gpt-5.2-proHighest reasoning — the hardest calls. Slow + pricey.400k
gpt-5.1Previous flagship — a touch cheaper/faster.400k
gpt-5-miniFast/cheap workhorse — high-volume screening + extraction.400k
gpt-5-nanoCheapest + fastest — bulk classification/tagging.400k
gpt-5.2-with-searchWhen the call needs current facts (funding, headcount, ownership) — returns web citations.400ktext + cites

Or pass a capability selector instead of a name and let the catalog resolve it:

cheapestfastestbalancedhighest_reasoning
Example — strict typed screening verdict
const r = await rubicon.call('infer', {
  model: 'gpt-5-mini',                // or a selector: 'cheapest'
  system: 'You screen vertical-software acquisition targets for Quality Standard.',
  messages: [{ role: 'user', content: 'Company: Acme CAD — CAD/RMS for municipal PD, 42 FTE, founder-owned.' }],
  response_schema: { type: 'object', properties: {
    verdict: { type: 'string', enum: ['forward', 'defer', 'drop'] },
    score: { type: 'integer' }, justification: { type: 'string' } } }
});
// r.output → { verdict:'forward', score:82, justification:'…' }; r.model is the REAL model that answered
Example response
{
  "query_id": "q-44ad…", "source": "openai", "model": "gpt-5-mini",
  "output": { "verdict": "forward", "score": 82, "justification": "Vertical CAD/RMS, founder-owned, right size band." },
  "output_text": null, "grounded": false,
  "actor_id": "ai:gpt-5-mini",
  "method": { "model": {…}, "tool_invocations": [] },   // thread straight into op_screen_leads
  "usage": { "total_tokens": 214 },
  "provenance": { "is_stub_response": false }
}

To turn a model verdict into a governed decision, pass infer's actor_id + method into op_screen_leads as the decision — never your own agent identity.

voice_transcribe · voice_speakApp runtimeSpeech in and out, keyless like infer.

Speech in and out, same keyless pattern as infer — audio rides as base64 in JSON and the server-side key does the work.

Inputs
OpInputsReturns
voice_transcribeaudio_base64 req, mime_type?, model? (gpt-4o-transcribe | gpt-4o-mini-transcribe · default), language?{ text, model, usage }
voice_speaktext req (≤4096), voice? (alloyshimmer, 10), format? (mp3/wav/opus/aac/flac), instructions?{ audio_base64, content_type }
Example request
await rubicon.call('voice_speak', { text: 'Acme CAD has been approved for outreach.', voice: 'sage', format: 'mp3' })
outlook_send_emailActionSend real email through the firm's M365.

Sends a real email through your own M365 (Microsoft Graph). Resolves a first name to the matching QS mailbox, or takes a literal address; omit the recipient inside a capsule worker and it emails the signed-in user. Sender is gated to an approved list, so nothing leaves from the wrong account.

Inputs
FieldTypeNotes
recipientstringrequiredA first name (→ QS mailbox) or a literal email.
subject · body_htmlstringrequiredBody is HTML. A [Rubicon] prefix is auto-added for human-mailbox sends.
send_viaenumoptrubiconself — service mailbox (default) or the signed-in user.
from_user_principalstringoptPrescribe an explicit sender UPN.
Example request
await rubicon.call('outlook_send_email', {
  recipient: 'Dana', send_via: 'self',
  subject: 'Quality Standard × Acme CAD',
  body_html: '<p>Hi Dana — we back vertical software founders…</p>'
})
schedule_actionActionA future-dated, cross-system action with atomicity.

A future-dated, cross-system action with atomicity — the first failed step short-circuits the rest, and compensating actions roll back partial success. Powers touchpoints, review-window blocks, cadence pauses with calendar holds.

Inputs
FieldTypeNotes
action_typestringrequirede.g. touchpoint, review_window_block, cadence_pause_with_hold.
target_systemsarrayrequiredPer-system steps, in order (order matters for atomicity).
acting_asstringrequiredRegistered actor.
scheduled_for · recurrencestringoptISO time (omit = now); RFC-5545 RRULE for recurring.
Example request
await rubicon.call('schedule_action', {
  action_type: 'touchpoint', acting_as: 'human:sufi@qualitystandard.com',
  scheduled_for: '2026-06-22T15:00:00Z',
  target_systems: [{ system: 'salesloft', step: 'add_to_cadence', kwargs: { person: 'dana@acme-cad.com' } }]
})

App lifecycle ship · iterate · restore

The capsule lifecycle: deploy from source, patch in place, and roll back instantly. Plus app state, jobs, and automations.

deploy_app_sourceBuildShip a capsule from source — build, host, version.

Ships a capsule: you send source_files (the manifest, src/App.tsx, server/index.ts), the platform type-checks, builds, and hosts it at /u/<app_id>/, pinned to the current API version. A failed build returns errors (file + line) and never touches the live version. Re-deploying with an app_id replaces the whole snapshot — for small edits use update_app_source instead. Start with dry_run: true — the sample request IS a minimal working capsule.

Inputs
FieldTypeNotes
source_files{path: content}requiredMust include rubicon.app.json (with permissions.rubicon_ops listing every op the server calls), src/App.tsx, server/index.ts. Boilerplate is scaffolded for you.
namestringrequiredDisplay name (first deploy).
description · change_summarystringoptLanding-card blurb + the version-history line users read.
app_idstringoptRedeploy in place (full-snapshot replace).
Example request
// First: get the complete working template + the hard rules, free
await rubicon.call('deploy_app_source', { dry_run: true })
// Then ship for real
await rubicon.call('deploy_app_source', {
  name: 'Screening Queue', change_summary: 'initial ship',
  source_files: { 'rubicon.app.json': '…', 'src/App.tsx': '…', 'server/index.ts': '…' }
})
Example response
{
  "status": "deployed", "app_id": "app_1a2b3c4d5e6f",
  "url": "/u/app_1a2b3c4d5e6f/", "version": 1,
  "api_version": "2026-07-01",
  "build": { "policy": "ok", "typecheck": "ok", "frontend_build": "ok", "server_bundle": "ok" },
  "context_summary": { "api_routes": 4, "rubicon_ops_used": 4, "workers": ["screenAll"] }
}
update_app_source · get_app_context · get_app_sourceBuildIterate: fetch the map, patch just what changes.

The iterate loop. get_app_context returns the app's map (routes, ops used, files, versions, its API-version pin); get_app_source fetches just the file(s) you'll change; update_app_source patches → rebuilds → promotes atomically — a failed build leaves the live version serving.

Inputs (update_app_source)
FieldTypeNotes
app_idstringrequiredWhich app.
edits[]object[]required{path, content} (whole file), {path, find, replace} (exact string), or {path, delete: true}.
base_versionintegerrequiredConcurrency guard — stale → re-fetch context.
change_summarystringrequiredThe version-history line users read. Always pass it.
Example request
await rubicon.call('update_app_source', {
  app_id: 'app_1a2b3c4d5e6f', base_version: 1,
  change_summary: 'add a rationale input to the screen action',
  edits: [{ path: 'src/App.tsx',
    find: '<h1>Screening Queue</h1>',
    replace: '<h1>Screening Queue</h1><p>Mark each company yes/no with a reason.</p>' }]
})
list_app_versions · rollback_appBuildVersion history + one-call restore.

Every deploy and edit snapshots a version with who made it, when, and its change_summary. list_app_versions reads that history; rollback_app restores any version instantly — no rebuild, the old bundle just starts serving again.

Example request
await rubicon.call('list_app_versions', { app_id: 'app_1a2b3c4d5e6f' })
await rubicon.call('rollback_app', { app_id: 'app_1a2b3c4d5e6f', version: 1 })
list_apps · get_app · set_app_status · delete_app · grant_app_access · revoke_app_accessBuildPromote, list, and control who can open an app.

Fleet management for hosted apps: list them, inspect one, promote between development and production (what the landing page badges), delete, and grant/revoke per-principal access to restricted apps.

Inputs
OpInputs
list_appsstatus? (development/production)
get_appapp_id req, include_content?
set_app_statusapp_id req, status req
delete_appapp_id req
grant_app_access · revoke_app_accessapp_id req + the principal to allow/deny.
app_state_get · app_state_set · app_state_list · app_state_deleteStorageThe app-owned key-value store.

Durable key-value state owned by the app (in a capsule: rubicon.state). Two gotchas: state.get(key) returns the raw stored value (or null) — not an envelope — and keys are app-scoped, not per-user: prefix with rubicon.whoami() for private-to-the-signed-in-user data. Declare namespaces in rubicon.app.json under permissions.state_namespaces.

Example (capsule server)
await c.env.rubicon.state.set('review-notes:acme-cad', { note: 'call back in Q3' });
const v = await c.env.rubicon.state.get('review-notes:acme-cad');  // the raw value, or null
trigger_job · get_job · list_jobs · cancel_job · deploy_background_jobJobsDurable background runs — status, history, cancel.

The job surface behind capsule workers: inspect a run with get_job (status, progress, result), list history, cancel. Inside a capsule you rarely call these directly — declare export const workers and use rubicon.startJob / rubicon.getJob; the platform owns durability and the run survives the tab closing.

deploy_automation · list_automations · get_automation · set_automation_status · delete_automationScheduleApps without a UI — recipes on a schedule.

Scheduled automations — a recipe of governed operations that runs on an interval or wall-clock cron, with the same governance and audit as an app. Each gets a status page at /a/<automation_id>/. Dry-run deploy_automation for the recipe shape.

report_build_manifestBuildRecord what an app did, for nightly drift replay.

Records what an app did as a machine-readable manifest, replayed nightly against your real stack (with synthetic data) to catch API/catalog drift before it breaks an app. Called as the last step after composing an app.

Inputs
FieldTypeNotes
manifestobjectrequiredFull payload per the build-manifest schema (manifest_id, schema_version, the captured op sequence…).

Exports documents partners pass around

Render partner-readable artifacts into M365 — Excel, Word, and generic reports.

export_pipeline_excel · export_outreach_word · export_report_docxDocumentExcel pipeline, Word outreach history, generic reports.

Render partner-readable documents into M365. Excel gives one worksheet per industry in the canonical pipeline column order; Word gives one company's full outreach history with partner margin comments; export_report_docx is the generic artifact sink — an app or automation drops any report as a Word doc. Every export is audited with who exported what and where it landed.

Inputs
OpInputs
export_pipeline_excelindustries? (string[]), filters? (by header, e.g. {"Partner Review":"yes"}), sourcing_stages? (string[])
export_outreach_wordwebsite? — or industry? + company_name?
export_report_docxtitle + the report content — dry-run for the exact shape.
Example request
await rubicon.call('export_pipeline_excel', {
  industries: ['Public Sector Software'], filters: { 'Partner Review': 'yes' }
})
// → { url: 'https://…sharepoint.com/…/Rubicon/_exports/pipeline/…xlsx' }

API versioning

The platform carries a dated API version (e.g. 2026-07-01). Every app is pinned server-side at deploy time to the version that was current when it shipped — the pin lives with the app, not in your code.

Older pins keep working. When the platform moves forward, requests and responses for pinned apps are translated at the boundary, so a shipped app never breaks because the API evolved underneath it. A green redeploy re-pins the app to the current version.

Check the current version and your app's pin any time via list_capabilities or get_app_context.

Governance

Every call is audited with who, what, and when — one accountability row per governed action, queryable via audit_trail. Writes are two-phase (preview → confirm) or fail loud; nothing lands silently. Subjective decisions require a registered actor — a human or a named AI model — attesting with evidence that is validated against the decision type's schema. Some apps and operations may be restricted by your admins: a 403 with code: "access_restricted" means the platform is working as intended — ask an admin for access.