> ## Documentation Index
> Fetch the complete documentation index at: https://rubie.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Connect an end user's account, then run a Rubie Blueprint against it.

Every Rubie integration is two loops with different lifetimes:

* **Connect** — once per user, per system. Your user hands over their credentials
  through a form Rubie hosts, and you get back an id to store against them.
* **Run** — as often as you need. You trigger a Blueprint with that id and poll
  until it finishes, then fetch its outputs.

## What you'll need

| Value         | Where it comes from                                      | How many you'll have              |
| ------------- | -------------------------------------------------------- | --------------------------------- |
| API key       | [Your Rubie dashboard](https://app.rubiehq.com/api-keys) | One or more per account           |
| `strategy_id` | Provided by Rubie                                        | One per system your users connect |
| Blueprint key | Provided by Rubie                                        | One per workflow                  |

The API key is a secret, so keep it in your server-side environment or secret
manager:

```bash theme={null}
export RUBIE_API_KEY="sk_..."
export RUBIE_API_URL="https://app.rubiehq.com"
```

Strategy ids and Blueprint keys are non-secret configuration. For a single
integration, constants are the simplest option. If your application later
supports several source systems or workflows, move them into configuration or
separate lookup tables. They are not fixed pairs: one strategy can be reused by
several Blueprints, and a Blueprint can require more than one strategy.

```javascript theme={null}
const strategyId = "strat_...";
const blueprintKey = "your-blueprint-key";

const apiKey = process.env.RUBIE_API_KEY;
const baseUrl = process.env.RUBIE_API_URL ?? "https://app.rubiehq.com";

async function rubieJson(url, init = {}) {
  const response = await fetch(url, init);
  const body = await response.json().catch(() => null);

  if (!response.ok) {
    throw new Error(
      body?.error?.message ?? `Rubie request failed (${response.status})`,
    );
  }

  return body;
}
```

<Info>
  API keys are account-scoped and every endpoint here is server-to-server, so
  keys must never reach a browser. See [Authentication](/guides/authentication).
</Info>

## Part 1 — Connect an account

Do this once, when a user first connects a system. The credentials go straight
from your user to Rubie's vault — they never touch your servers.

<Steps>
  <Step title="Create a credential session">
    `strategy_id` tells Rubie which system the user is connecting, so it renders
    the right fields on the form. `return_url` sends them back to your app when
    they're done. Ask Rubie to allowlist its exact origin on the account that
    owns the API key making this request, then open the returned `hosted_url` in
    the same tab or a new one.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --fail-with-body -sS "$RUBIE_API_URL/api/v1/credential-sessions" \
        -X POST \
        -H "Authorization: Bearer $RUBIE_API_KEY" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: connect-integration-123" \
        -d '{
          "strategy_id": "strat_...",
          "name": "Production source account",
          "return_url": "https://app.example.com/integrations/rubie/connected"
        }'
      # → { "id": "sess_...", "status": "pending", "hosted_url": "...", "return_url": "...", "credential_id": null }
      ```

      ```javascript Node.js theme={null}
      const session = await rubieJson(`${baseUrl}/api/v1/credential-sessions`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${apiKey}`,
          "Content-Type": "application/json",
          "Idempotency-Key": "connect-integration-123",
        },
        body: JSON.stringify({
          strategy_id: strategyId,
          name: "Production source account",
          return_url: "https://app.example.com/integrations/rubie/connected",
        }),
      });

      // Open session.hosted_url for the user
      ```
    </CodeGroup>

    `name` is just a label for your team in the Rubie dashboard. Something that
    identifies the connection is a good choice.
  </Step>

  <Step title="Confirm the session, then store the credential id">
    After collection, Rubie redirects to your `return_url` with the session id:

    `https://app.example.com/integrations/rubie/connected?session_id=sess_...`

    Read `session_id` from the query string and confirm the session from your
    backend before saving `credential_id`:

    <CodeGroup>
      ```bash cURL theme={null}
      curl --fail-with-body -sS "$RUBIE_API_URL/api/v1/credential-sessions/sess_..." \
        -H "Authorization: Bearer $RUBIE_API_KEY"
      # → { "status": "completed", "credential_id": "cred_...", ... }
      ```

      ```javascript Node.js theme={null}
      const sessionId = requestUrl.searchParams.get("session_id");

      const current = await rubieJson(
        `${baseUrl}/api/v1/credential-sessions/${sessionId}`,
        { headers: { Authorization: `Bearer ${apiKey}` } },
      );

      if (current.status === "completed") {
        await db.saveIntegrationCredential(current.credential_id); // "cred_..."
      }
      ```
    </CodeGroup>

    When `status` is `completed`, `credential_id` is populated. Save it in your
    application's integration settings. Rubie already stores the underlying
    credential under the account associated with your API key; your application
    stores only this opaque id so it can select the connection on future runs.

    If your application supports multiple connections, associate each
    `credential_id` with the corresponding workspace or integration. Branch on
    `completed` and `expired`; if the status is anything else, poll briefly until
    it settles. That id is now good for as many runs as you like.
  </Step>
</Steps>

## Part 2 — Run a Blueprint

Now that you have a `cred_...`, trigger a run whenever you need work done in that
system — on a schedule, on a webhook, or from a button in your UI.

<Steps>
  <Step title="Trigger the run">
    Pass the stored credential id. You'll get `202` back: the run is queued, not
    finished.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --fail-with-body -sS "$RUBIE_API_URL/api/v1/blueprints/your-blueprint-key/trigger" \
        -X POST \
        -H "Authorization: Bearer $RUBIE_API_KEY" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: integration-run-2026-07-31" \
        -d '{
          "credential_id": "cred_...",
          "run_name": "On-demand integration run"
        }'
      # → 202 { "id": "run_...", "run_key": "...", "status": "queued" }
      ```

      ```javascript Node.js theme={null}
      const credentialId = await db.getIntegrationCredential();

      const run = await rubieJson(
        `${baseUrl}/api/v1/blueprints/${blueprintKey}/trigger`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${apiKey}`,
            "Content-Type": "application/json",
            "Idempotency-Key": "integration-run-2026-07-31",
          },
          body: JSON.stringify({
            credential_id: credentialId,
            run_name: "On-demand integration run",
          }),
        },
      );
      // run.id → "run_..."
      ```
    </CodeGroup>
  </Step>

  <Step title="Poll until it finishes">
    Poll every 2–5 seconds until `status` is `completed`, `failed`, or
    `cancelled`. Don't block a user-facing request on it.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --fail-with-body -sS "$RUBIE_API_URL/api/v1/blueprint-runs/run_.../status" \
        -H "Authorization: Bearer $RUBIE_API_KEY"
      # → { "status": "completed", "progress_step": "completed", "error": null, ... }
      ```

      ```javascript Node.js theme={null}
      const runStatus = await rubieJson(
        `${baseUrl}/api/v1/blueprint-runs/${run.id}/status`,
        { headers: { Authorization: `Bearer ${apiKey}` } },
      );

      // runStatus.status, runStatus.progress_step, runStatus.error?.code
      ```
    </CodeGroup>

    On `failed`, `error.code` is `execution_failed` — offer a retry. A network
    retry of the original trigger request should reuse its `Idempotency-Key`, but
    a user-initiated retry after a terminal failure is a new logical operation
    and must use a new key. Otherwise Rubie replays the original run response
    instead of creating another run.

    This endpoint returns lifecycle facts only, never run inputs, outputs, or
    file URLs, so it's safe to poll from systems handling regulated data.
  </Step>

  <Step title="Fetch the results">
    After the status is `completed`, fetch the Blueprint outputs. Use direct
    delivery when you want records in the response body:

    <CodeGroup>
      ```bash cURL theme={null}
      curl --fail-with-body -sS "$RUBIE_API_URL/api/v1/blueprint-runs/run_.../results?deliveryMethod=direct" \
        -H "Authorization: Bearer $RUBIE_API_KEY"
      # → { "dataDeliveryMethod": "direct", "results": { ... }, "executionMetadata": { ... } }
      ```

      ```javascript Node.js theme={null}
      const output = await rubieJson(
        `${baseUrl}/api/v1/blueprint-runs/${run.id}/results?deliveryMethod=direct`,
        { headers: { Authorization: `Bearer ${apiKey}` } },
      );

      if (output.dataDeliveryMethod === "direct") {
        for (const [outputKey, result] of Object.entries(output.results)) {
          console.log(outputKey, result.totalRecords, result.records);
        }
      } else {
        for (const [outputKey, downloadUrl] of Object.entries(output.results)) {
          const response = await fetch(downloadUrl);
          if (!response.ok) throw new Error(`Result download failed (${response.status})`);
          const result = await response.json();
          console.log(outputKey, result.totalRecords, result.records);
        }
      }
      ```
    </CodeGroup>

    Direct responses contain each output's records. For larger results, request
    `deliveryMethod=presigned_url` (the default) and download the temporary URLs
    in `results`. Rubie automatically switches a direct request to presigned URL
    delivery when the estimated payload is 5 MB or larger, so always branch on
    the returned `dataDeliveryMethod`.
  </Step>
</Steps>

## Disconnecting

When a user disconnects a system, revoke the credential. This is a hard delete:
the vaulted secrets are destroyed and the `cred_...` stops resolving, so drop
your stored mapping at the same time.

```bash theme={null}
curl --fail-with-body -sS "$RUBIE_API_URL/api/v1/credentials/cred_..." \
  -X DELETE \
  -H "Authorization: Bearer $RUBIE_API_KEY" \
  -H "Idempotency-Key: disconnect-user-123"
# → 204
```

## Next

* [Hosted credential collection](/guides/hosted-credential-collection) — session lifecycle, embedding, and reconnect flows
* [Embedded Credential Capture](/guides/embedded-credential-capture) — let users choose a Blueprint and connect inside an iframe
* [Triggering Blueprint runs](/guides/blueprint-runs) — input routing, file uploads, polling, and results in depth
* [Idempotency](/guides/idempotency) — why every example above carries an `Idempotency-Key`
* [Auth strategies](/concepts/auth-strategies) — what sits behind a `strategy_id`, if you're curious
