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

# Hosted Credential Collection

> Let end users connect a third-party account through a Rubie-hosted form, without secrets touching your servers.

Blueprints act on a third-party system on behalf of your end user, which means
they need that user's credentials. A **credential session** is a short-lived,
Rubie-hosted form that collects those credentials, encrypts them, and vaults
them — and hands you back nothing but an opaque id.

The secrets never transit your infrastructure, so this flow keeps credential
handling out of your compliance scope.

## The shape of the flow

```mermaid theme={null}
sequenceDiagram
  participant You as Your backend
  participant Rubie
  participant User as End user
  You->>Rubie: POST /credential-sessions { strategy_id, return_url }
  Rubie-->>You: sess_... + hosted_url (status: pending)
  You->>User: Open hosted_url
  User->>Rubie: Submits credentials
  Rubie->>User: Redirect to return_url?session_id=sess_...
  You->>Rubie: GET /credential-sessions/sess_...
  Rubie-->>You: status: completed, credential_id: cred_...
  You->>You: Store cred_... against the user
```

## Choosing a strategy

Every session is created against an [authentication
strategy](/concepts/auth-strategies) — a template that defines which fields the
target system needs (username/password, bearer token, custom headers, TOTP, and
so on). The strategy determines what the hosted form renders, so you don't have
to build or maintain a credential UI per integration.

Strategy ids are provisioned by Rubie for your account. You'll typically hold one
per system you integrate with.

## Creating a session

```bash theme={null}
curl -s "$RUBIE_API_URL/api/v1/credential-sessions" \
  -X POST \
  -H "Authorization: Bearer $RUBIE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: connect-user-123" \
  -d '{
    "strategy_id": "strat_...",
    "name": "Acme Corp production account",
    "return_url": "https://app.example.com/integrations/rubie/connected"
  }'
```

```json theme={null}
{
  "id": "sess_...",
  "status": "pending",
  "hosted_url": "https://app.rubiehq.com/credential-collect/...",
  "return_url": "https://app.example.com/integrations/rubie/connected",
  "strategy_id": "strat_...",
  "credential_id": null,
  "expires_at": "2026-08-06T00:00:00.000Z",
  "completed_at": null,
  "created_at": "2026-07-30T00:00:00.000Z"
}
```

`name` is optional and is only a label for the vaulted credential in the Rubie
dashboard. Something that identifies the end user or their account is a good
choice — it's what your team will see when debugging a run.

`return_url` is optional. Its exact origin — scheme, host, and port — must be
allowlisted for your account by Rubie. Paths and query strings can vary beneath
that origin. For example, allowlisting `https://app.example.com` permits both
`https://app.example.com/settings` and
`https://app.example.com/onboarding?step=connect`, but not an `http://` URL, a
subdomain, or another port.

Sessions expire after **7 days** by default. `hosted_url` is populated only while
`status` is `pending`; once a session is `completed` or `expired` it returns
`null`, so the link cannot be reused.

## Presenting the form

Open `hosted_url` in a new tab or an iframe. Either works — pick based on how
much you want the flow to feel embedded.

If users should choose from several Blueprints inside your product, use the
[Embedded Credential Capture widget](/guides/embedded-credential-capture)
instead. It provides a Blueprint picker, an origin-bound iframe, browser
lifecycle events, and the selected Blueprint key on completion.

The user may not finish in one sitting, and some strategies redirect out to the
target system's own login. Neither breaks anything: the session stays `pending`
until it's either submitted or expires, so you can re-open the same `hosted_url`
as long as it's still valid.

## Returning to your app

After the user submits successfully, Rubie redirects them to your `return_url`
with the session id appended:

```text theme={null}
https://app.example.com/integrations/rubie/connected?session_id=sess_...
```

Read `session_id` from the redirect and confirm the session through your backend
with the authenticated API:

```bash theme={null}
curl -s "$RUBIE_API_URL/api/v1/credential-sessions/sess_..." \
  -H "Authorization: Bearer $RUBIE_API_KEY"
```

When `status` is `completed`, `credential_id` is populated. Store it against the
user in your own database — **Rubie does not own the mapping between your users
and their credentials.** That id is stable for the life of the credential.

If the returned status is still non-terminal, poll every 2 seconds until it
settles. This can happen as Rubie adds verification between form submission and
completion. In the normal user-present flow, polling starts only after the
redirect and lasts seconds — clients should not poll continuously for the
session's full seven-day lifetime.

If you omit `return_url`, the hosted page shows a success message and tells the
user they can close the tab. In that fallback flow, start polling when you open
`hosted_url` and stop when the user closes the flow, the session reaches a
terminal state, or your own short timeout expires. The session can still be
checked later on an explicit user action.

| `status`    | Terminal? | What to do                                             |
| ----------- | --------- | ------------------------------------------------------ |
| `pending`   | No        | Keep polling; `hosted_url` is still open               |
| `completed` | Yes       | Persist `credential_id` against your user              |
| `expired`   | Yes       | Create a fresh session and send the user through again |

<Warning>
  Treat `status` as an open string set, not a fixed enum. Branch on the three
  values above and **keep polling on anything you don't recognise**. Additional
  non-terminal statuses may appear between collection and `completed` — for
  example while Rubie verifies the credential against the target system.
  `completed` will always mean terminal and usable, so `if (status ===
      "completed") {save(credential_id)}` is safe to write today and will stay
  correct.
</Warning>

<Note>
  Today, `completed` means the credentials were collected and vaulted — not that
  they were tested. Validity is checked on the first Blueprint run, so a typo'd
  password surfaces as a failed run rather than a failed connection. In-session
  verification is planned and will arrive as an extra non-terminal status, not
  as a change to the meaning of `completed`.
</Note>

## Reconnecting and revoking

Credentials go stale: users rotate passwords, revoke tokens, or churn. Two things
to build for.

**Reconnect.** There's no "update credential" step in this flow. To replace a
stale credential, create a new session, and swap the stored id once it completes.

**Revoke.** When a user disconnects, delete the credential:

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

This is a hard delete — the vaulted secrets are destroyed and the id stops
resolving. Drop your stored mapping at the same time.

## Idempotency

Both `POST /credential-sessions` and `DELETE /credentials/{id}` accept an
`Idempotency-Key`. Key it on the user or connection rather than the attempt, so a
double-submitted "Connect" button replays the original session instead of
creating a second one. See [Idempotency](/guides/idempotency).

## Next

* [Embedded Credential Capture](/guides/embedded-credential-capture) — add a Blueprint picker and credential flow to your product
* [Triggering Blueprint runs](/guides/blueprint-runs) — put the credential to work
* [Auth strategies](/concepts/auth-strategies) — the field model behind the hosted form
* [Identifiers](/guides/identifiers) — what `sess_`, `strat_`, and `cred_` ids mean
