---
title: "Change a stored app login's metadata."
method: PATCH
path: "/story-projects/{projectId}/app-connections/{connectionId}"
tags: ["Sources"]
---

# Change a stored app login's metadata.

`PATCH /story-projects/{projectId}/app-connections/{connectionId}`

## Request

- Base URL: `https://docs.sleeperhit.studio/api/v1`
- URL: `https://docs.sleeperhit.studio/api/v1/story-projects/{projectId}/app-connections/{connectionId}`
- Auth: HTTP bearer

Any create field; null clears an optional one. A change that ADDS an origin (a new `origin` or extra origin) deletes every saved secret, so a stored login never follows the app to another site; a new `loginMethod` or `headerKind` deletes the secrets the old one used. Each removal is audited. A secret field is refused by name. Free. A story project is refused with 409 `project_not_product` (nothing is stored or started): app connections are for product projects. Listing, reading and deleting a story project's existing connections still work.

## Request body

- AppConnectionUpdate: Any create field; null clears an optional one. No field takes a secret.
  - `origin` string
  - `label` string, nullable
  - `extraOrigins` string[]
  - `loginUrl` string, nullable
  - `loginMethod` 'form' | 'storageState' | 'provider' | 'none'
  - `providerEndpoint` string, nullable: A new endpoint deletes the saved grant; leaving `provider` drops it.
  - `loggedInWhen` object[], nullable
  - `headerKind` 'none' | 'cloudflareAccess' | 'vercelBypass'
  - `maskSelectors` string[]
  - `retentionDays` integer, nullable

## Response `200`

The changed connection.

- AppConnectionEnvelope
  - `connection` AppConnection, required
    - `id` string, required
    - `projectId` string, required
    - `label` string, nullable, required
    - `origin` string, required: The app's https origin: the only site a secret is typed into or a header sent to (with `extraOrigins`).
    - `extraOrigins` string[], required
    - `loginUrl` string, nullable, required
    - `loginMethod` 'form' | 'storageState' | 'provider' | 'none', required
    - `providerEndpoint` string, nullable, required: `provider` only: the app's sleeper.capture/1 API. The saved grant is sent there, in the Authorization header, and nowhere else.
    - `loggedInWhen` object[], nullable, required: Capture preconditions that hold once signed in.
    - `headerKind` 'none' | 'cloudflareAccess' | 'vercelBypass', required
    - `maskSelectors` string[], required
    - `usernameHint` string, nullable, required: The username masked when it was saved ("de•••@example.com").
    - `status` 'untested' | 'verified' | 'failed', required
    - `lastVerifiedAt` string, date-time, nullable, required
    - `lastUsedAt` string, date-time, nullable, required
    - `lastTestRequestedAt` string, date-time, nullable, required
    - `lastTest` AppConnectionTest, required: One test sign-in check, including checks that could not reach the app. `failed` means the check ended without a successful sign-in; it does not by itself mean the app rejected the login. `not_ready` means setup is incomplete; `unavailable`, `runner`, `runner_busy` and `recipe_unavailable` mean the check is unavailable. `credentials_refused` reports that the app refused the username and password. `needs_field` and `sso_only` stop before anything is typed: the sign-in asks for something the saved login does not have; `recipe_failed` means a sign-in step stopped even after one repair. Never a secret.
      - `id` string, required
      - `status` 'running' | 'passed' | 'failed', required: `running`, `passed` or `failed`. A test still running long after it was asked for reads as failed (`runner`).
      - `requestedAt` string, date-time, required
      - `finishedAt` string, date-time, nullable, required
      - `reason` 'not_https' | 'off_origin' | 'captcha' | 'form_not_found' | 'credentials_refused' | 'mfa_required' | 'totp_refused' | 'not_signed_in' | 'redeem_refused' | 'password_in_session' | 'timeout' | 'browser' | 'provider_refused' | 'unreachable' | 'not_ready' | 'unavailable' | 'runner' | 'runner_busy' | 'needs_field' | 'sso_only' | 'recipe_unavailable' | 'recipe_failed', nullable, required: Why it failed: given for an app whose origin is verified as yours, and for reasons on Sleeper's side (`not_ready`, `unavailable`, `runner`, `runner_busy`); null otherwise (a test is not a way to check logins for apps that are not yours). A running test can wait up to five minutes for a requested one-time code, entered only in that test on Connected apps. Story API, CLI, MCP and Slug never accept that code. `mfa_required` means that attempt ended without completing its second sign-in step. For an interactive retry, enter a requested code only in the running sign-in's temporary field. For unattended sign-in, save an authenticator setup key if the app accepts its codes, or use a saved session or capture grant. Sleeper cannot read email or texts, follow sign-in links, approve push requests, or use a passkey.
      - `message` string, nullable, required: What happened, in plain words.
      - `landedUrl` string, nullable, required: Where the browser was when it finished (no query or fragment); only for an app whose origin is verified as yours.
      - `hasScreenshot` boolean, required: Whether a picture of what the browser saw was kept. Only the owner's own reads link to it.
    - `retentionDays` integer, nullable, required
    - `secrets` object[], required: Which secrets are saved, and when. Never a value.
      - `kind` 'username' | 'password' | 'totpSeed' | 'workspace' | 'cfAccessClientId' | 'cfAccessClientSecret' | 'vercelBypass' | 'storageState' | 'providerGrant', required: One sealed secret of a stored app login. Reads name the kinds saved, never a value.
      - `savedAt` string, date-time, required
    - `missingSecrets` AppConnectionSecretKind[], required: The secrets its sign-in method and header still need, and a `workspace` when its sign-in steps ask for one.
    - `signInRecipe` AppConnectionSignInRecipe, required: THE SIGN-IN STEPS of a form sign-in (BL-2623): found by reading the signed-out app (no login is ever shown to what reads it), checked against the saved secrets before anything is typed, followed by every test, recording and walk, repaired once when a step no longer matches, and kept versioned. A secret is typed only into a field inside the form a step names. The sign-in page a person set wins (`loginUrlPinned`).
      - `version` integer, required
      - `source` 'scout' | 'repair', required
      - `foundAt` string, date-time, required
      - `lastPassedAt` string, date-time, nullable, required
      - `loginUrl` string, required: The sign-in page a run opens.
      - `loginUrlPinned` boolean, required: The person set the sign-in page.
      - `steps` object[], required
        - `fields` object[], required
          - `role` 'email' | 'username' | 'password' | 'workspace' | 'totp' | 'other', required
          - `label` string, required
      - `sso` object, required
        - `providers` string[], required
        - `only` boolean, required
      - `notes` string[], required
      - `summary` string, required: One sentence: "Signs in at /login · Email, then Password".
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required

## Other responses

- `400`: Request payload failed validation. The error message names the offending field.
- `401`: Missing, malformed, revoked, or expired API key.
- `403`: API key is valid but lacks the scope, is blocked by IP allowlist, or the account is disabled.
- `404`: No app connection with that id in this project (`app_connection_not_found`), or the project is not yours (`project_not_found`).
- `409`: `project_not_product` — the project is a story project: app connections are for product projects. PATCH its `kind` to `product` first; nothing was stored or started.
- `429`: Rate limit for this API key + IP + route family was exceeded.
- `500`: The API failed unexpectedly. Safe to retry with the same Idempotency-Key.

---

[API](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api.md) · [All operations](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api/llms.txt) · [OpenAPI document](https://skmtc.dev/sleeperhit/apis/sleeper-hit-studio-story-api/revisions/64c1102ccb2f?raw)
