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/mcpFor 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.
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_keyis 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.
-
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 } -
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 -
The browser talks only to its own routes
Front-end code calls
./api/*and nothing else — your Hono routes inserver/index.tsare auto-mounted under/api.fetch("./api/leads")app.get("/leads", …) -
Every server effect goes through
c.env.rubiconReads return refusals as data — check
r.ok === falseand surface it, never render an empty page over one. Writes throw — always try/catch.await c.env.rubicon.read("…")await c.env.rubicon.write("…") -
Background work is a worker, not a browser loop
Declare
export const workersand drive them withstartJob/getJob— durable, server-side, survives the tab closing.export const workers = { … }rubicon.startJob("name", args)rubicon.getJob(job_id) -
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.
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.
| Op | Inputs | Returns |
|---|---|---|
list_operations | domain req (e.g. sourcing), include_summaries?, include_eligibility? | Operations + what each can write/decide. |
list_signals | domain?, object_type? | signal_key → target field mappings. |
list_decision_types | domain?, 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_handoff | operation req, include_past_examples? | One op's full overlay: semantics, evidence shape, allowed actors, attributed past examples. |
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.
| Field | Type | Notes | |
|---|---|---|---|
sourcing_stage | string | required | e.g. needs-triage, ready-for-review, outreach-drafted. |
industry | string | string[] | opt | Restrict to bucket(s). Omitted → all. |
limit | integer | opt | Cap the page. Rows come newest-first. |
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).
| Field | Type | Notes | |
|---|---|---|---|
pipeline_domain | string | required | e.g. sourcing — picks the stage taxonomy. |
filter | object | opt | e.g. {stage:'partner_review'}, {industry:'…'}. |
as_of_iso | string | opt | Time-travel point — "the pipeline as of March 1". |
include_what_fires_next | boolean | opt | Per entity, preview the next signals. |
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.
| Field | Type | Notes | |
|---|---|---|---|
entity_type | enum | required | accountopportunitycontactleadcadenceemail_messagecalendar_eventcalendly_action |
entity_id | string | required | Source-system Id. |
fields | string[] | opt | Subset; defaults to a curated set per type. |
include_per_field_provenance · include_governance_hints · include_links | boolean | opt | Attach source/last-modified, write-eligibility, related entities. |
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.
| Field | Type | Notes | |
|---|---|---|---|
source | enum | required | gratahuntersalesforcesalesloftwebsitepipelinesharepointoutlook |
query_type | string | required | Per-source (e.g. company_search, email_finder). |
criteria | object | required | Open shape — validated by the adapter. |
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?"
| Field | Type | Notes |
|---|---|---|
filter | object | AND-combined: entity_id, acting_as, via_app, decision_type, signal_key, occurred_after/before, and outcome ∈ permitted_writestage_decisionpermitted_readrefused_readerror |
aggregations | array | Group-by + count, e.g. [{group_by:'acting_as', count:'decision_type'}]. |
limit · cursor | integer · string | Pagination. |
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:
| Op | Returns rows where… |
|---|---|
prep_for_screen_leads | freshly sourced, not yet screened. |
prep_for_partner_review | Partner Review is still blank. |
prep_for_draft_outreach | Partner Review = yes — approved, ready to draft. Brings the proven cadence copy. |
prep_for_approve_outreach | Partner Review = yes AND Email Draft = draft — drafted, awaiting approval. |
| Field | Type | Notes |
|---|---|---|
inputs.industry / industries | string | string[] | One Tier-2 bucket or a list. Omitted → scan all. |
inputs.owner / owners | string | string[] | Owning partner(s) — actor id or display name. |
inputs.websites / website_urls | string | string[] | Restrict to specific rows (the website PK). |
inputs.enrich | boolean | Default true — backfill website/LinkedIn enrichment on returned rows. |
inputs.include_dumps | boolean | Response shaping only (default false) — attach raw scrape blobs. |
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.
| Field | Type | Notes | |
|---|---|---|---|
inputs.search_terms | string | string[] | required | Grata keyword(s), max 10. One parallel search each. |
inputs.industry | string | object | opt | Where to file results. A QS Tier-2 bucket, or {tier2, tier3}. Omitted → resolved from the terms, else Misc. |
inputs.min_employees / max_employees | integer | opt | Headcount band. Defaults 10–150 when omitted. |
inputs.year_founded | integer | opt | Founded on/after this year. |
inputs.location | {city,state,country} | opt | Geo filter. Defaults to US. |
inputs.limit | integer | opt | Max results per term (default 50). |
inputs.dedupe_against_salesforce | boolean | opt | Default true — drops anything already in SF. |
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.
| Field | Type | Notes | |
|---|---|---|---|
inputs.primary_keys | string[] | required | Website URLs to decide on — one decision each. (Inside a capsule, the host derives this from your decisions.) |
inputs.industry | string | string[] | opt | Scopes which bucket(s) to search. Omitted → all. |
decisions[].candidate_ref | {entity_id} | required | entity_id = the website URL (binds the decision to the row). |
decisions[].verdict | enum | required | yesno |
decisions[].acting_as | string | required | A registered actor id (not auto-mapped here). |
decisions[].rationale · method | string · object | opt | Both required when the actor is a named AI model. |
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.
| Field | Type | Notes | |
|---|---|---|---|
inputs.primary_keys | string[] | required | Website URLs under review. |
decisions[] | object[] | required | One per key: candidate_ref, verdict (yes/no), acting_as (a partner). |
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.
| Field | Type | Notes | |
|---|---|---|---|
inputs.outreach_targets[] | object[] | required | One per company: website (PK), email (the composed body), optional subject. |
inputs.industry | string | string[] | opt | Narrows which bucket(s) to write into. |
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.
| Field | Type | Notes | |
|---|---|---|---|
inputs.accounts | string[] | required | Website URLs to approve/reject. |
decisions[] | object[] | required | One per account: candidate_ref.website, verdict, acting_as (a partner). |
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.
| Field | Type | Notes | |
|---|---|---|---|
inputs.company_name | string | required | Account Name, matched verbatim. |
inputs.source_industry · target_industry | string | required | From-bucket and to-bucket (created if absent). |
inputs.override_reason | string | required | Why the move is warranted — persists in audit + on the row. |
inputs.move_files | boolean | opt | Default true — bring outreach files too. |
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.
| Field | Type | Notes | |
|---|---|---|---|
signal_key | enum | required | The registered write. QS verbs:advance_stageflag_go_getset_prioritypass_leadlose_leadpromote_to_opportunitylaunch_cadencepause_cadenceclose_cadenceverified_email |
object_type | enum | required | accountopportunitycontactleadcadence |
object_id · value | string · any | required | Target Id + the new value (type matches the mapping). |
acting_as | string | required | Registered actor. |
confirmation_token | string | commit | From the matching preview. (HTTP: X-Rubicon-Confirmation-Token header.) |
attested_override_reason | string | opt | Required (≥10 chars) if a soft expectation was unmet at preview. |
// 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.
| Field | Type | Notes | |
|---|---|---|---|
decision_type | string | required | A registered type, e.g. screening_decision. (See list_decision_types.) |
acting_as | string | required | Human or named-AI actor id. |
evidence | object | required | Shape validated against the decision type. |
entity_ref | {entity_type, entity_id} | opt | What the decision is about; carries forward to downstream signals. |
supersedes_decision_id | string | opt | Revises a prior decision (e.g. partner overrides an AI jury). |
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).
| Field | Type | Notes | |
|---|---|---|---|
messages | {role,content}[] | required | role ∈ user/assistant/system. (Or pass system separately.) |
response_schema | JSON Schema | opt | Forces strict typed JSON back in output; omit for free text in output_text. |
model | string | opt | A name, an alias, or a selector (see catalog). Omit for the default. |
include_grounded_search | boolean | opt | Default false. true grounds the answer in live web results + returns citations. |
max_tokens | integer | opt | Default 1024. Use ≥1000 for gpt-5* — they spend tokens on hidden reasoning first. |
| Alias | Best for | Cost | Reasoning | Speed | Context | Structured |
|---|---|---|---|---|---|---|
gpt-5.2DEFAULT | Balanced flagship — screening / judgment calls. | 400k | ✓ | |||
gpt-5.2-pro | Highest reasoning — the hardest calls. Slow + pricey. | 400k | ✓ | |||
gpt-5.1 | Previous flagship — a touch cheaper/faster. | 400k | ✓ | |||
gpt-5-mini | Fast/cheap workhorse — high-volume screening + extraction. | 400k | ✓ | |||
gpt-5-nano | Cheapest + fastest — bulk classification/tagging. | 400k | ✓ | |||
gpt-5.2-with-search | When the call needs current facts (funding, headcount, ownership) — returns web citations. | 400k | text + cites |
Or pass a capability selector instead of a name and let the catalog resolve it:
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.
| Op | Inputs | Returns |
|---|---|---|
voice_transcribe | audio_base64 req, mime_type?, model? (gpt-4o-transcribe | gpt-4o-mini-transcribe · default), language? | { text, model, usage } |
voice_speak | text req (≤4096), voice? (alloy…shimmer, 10), format? (mp3/wav/opus/aac/flac), instructions? | { audio_base64, content_type } |
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.
| Field | Type | Notes | |
|---|---|---|---|
recipient | string | required | A first name (→ QS mailbox) or a literal email. |
subject · body_html | string | required | Body is HTML. A [Rubicon] prefix is auto-added for human-mailbox sends. |
send_via | enum | opt | rubiconself — service mailbox (default) or the signed-in user. |
from_user_principal | string | opt | Prescribe an explicit sender UPN. |
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.
| Field | Type | Notes | |
|---|---|---|---|
action_type | string | required | e.g. touchpoint, review_window_block, cadence_pause_with_hold. |
target_systems | array | required | Per-system steps, in order (order matters for atomicity). |
acting_as | string | required | Registered actor. |
scheduled_for · recurrence | string | opt | ISO time (omit = now); RFC-5545 RRULE for recurring. |
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.
| Field | Type | Notes | |
|---|---|---|---|
source_files | {path: content} | required | Must 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. |
name | string | required | Display name (first deploy). |
description · change_summary | string | opt | Landing-card blurb + the version-history line users read. |
app_id | string | opt | Redeploy in place (full-snapshot replace). |
// 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.
| Field | Type | Notes | |
|---|---|---|---|
app_id | string | required | Which app. |
edits[] | object[] | required | {path, content} (whole file), {path, find, replace} (exact string), or {path, delete: true}. |
base_version | integer | required | Concurrency guard — stale → re-fetch context. |
change_summary | string | required | The version-history line users read. Always pass it. |
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.
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.
| Op | Inputs |
|---|---|
list_apps | status? (development/production) |
get_app | app_id req, include_content? |
set_app_status | app_id req, status req |
delete_app | app_id req |
grant_app_access · revoke_app_access | app_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.
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.
| Field | Type | Notes | |
|---|---|---|---|
manifest | object | required | Full 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.
| Op | Inputs |
|---|---|
export_pipeline_excel | industries? (string[]), filters? (by header, e.g. {"Partner Review":"yes"}), sourcing_stages? (string[]) |
export_outreach_word | website? — or industry? + company_name? |
export_report_docx | title + the report content — dry-run for the exact shape. |
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.