How it works
Zupflow sits between your product and the services your customers use. Your backend talks to it with an API key; your customers only ever see your product and, once per account, the hosted Connect page.
sequenceDiagram autonumber actor P as Person participant UI as Your product participant B as Your backend participant Z as Zupflow participant S as Their service (MCP) P->>UI: Connect Linear UI->>B: request B->>Z: create Connect session Z-->>UI: hosted Connect URL P->>Z: sign in on the Connect page Z->>S: OAuth consent and token exchange Note over Z: credentials sealed into an installation P->>UI: Plan my week UI->>B: request B->>Z: start run loop each turn Z->>Z: call the model with the granted tools Z->>S: tools/call (allowed calls) end Z-->>B: webhook run.waiting_for_approval B->>UI: ask the person P->>UI: Allow B->>Z: resolve approval Z->>S: tools/call Z-->>B: webhook run.completedThe flow
Section titled “The flow”- 1WorkspaceOne per customer
- 2ConnectThey link an account
- 3GrantYou choose the tools
- 4RunThe agent works
- 5ApproveA person decides
- 6DoneWebhook to you
- Your backend creates a workspace for each of your customers, with your own ID for them as
external_id. - Your customer connects an account. Your backend creates a Connect session and sends the person to its URL. Zupflow runs the sign-in, seals the credentials and creates an installation. Zupflow then lists the app’s tools for that installation.
- You grant an agent tools. A grant names tools of one installation, and the organization’s tool policy bounds what any grant can allow.
- Your backend starts a run with the person’s input. Zupflow calls the model with the tools the agent may use, decides each call it asks for, sends the allowed ones to the installation’s MCP server with the credential injected last, and records every step as an event.
- When a call needs approval, the run waits and Zupflow sends
run.waiting_for_approval. Your product asks the person; your backend sends the decision. - When the run finishes, Zupflow sends
run.completed(orrun.failed,run.cancelled), and you read its output and events.
Built to finish what it starts
Section titled “Built to finish what it starts”flowchart LR P["Plan the next step<br/>from the run as saved"] --> S["Save the step<br/>with its events<br/>and webhooks"] S --> A["Take the one action<br/>it allows: ask the model<br/>or send one call"] A -->|outcome| PA run moves one step at a time, and every step is saved, together with its events and the webhooks it causes, before the action it allows is taken. If anything is interrupted, the run picks up from its last saved step. A tool call is sent at most once: one whose outcome was lost is recorded as unknown and reported to the model, never sent again.