---
title: "Read the newest test sign-in of a stored app login."
method: GET
path: "/story-projects/{projectId}/app-connections/{connectionId}/test-login"
tags: ["Sources"]
---

# Read the newest test sign-in of a stored app login.

`GET /story-projects/{projectId}/app-connections/{connectionId}/test-login`

## Request

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

Running, passed or failed, in plain words (`message`), and `tested: true` once it finished. Why a test failed and where the browser landed are given only for an app whose origin is verified as yours (and for reasons on Sleeper's side: a missing secret, no runner, a busy one); otherwise it is passed or failed and the picture. For the owner's own key, `screenshotUrl` is a link of at most ten minutes to the picture of what the browser saw, kept in a private bucket and never on the public media host; an OAuth client (an agent) gets the outcome without it. Reads only.

## Response `200`

The newest test sign-in.

- AppConnectionTestLogin
  - `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
  - `ready` boolean, required: Every secret its sign-in method and header need is saved.
  - `missingSecrets` AppConnectionSecretKind[], required
  - `tested` boolean, required: Whether a browser actually tried to sign in: the newest test has finished (passed or failed).
  - `test` 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.
  - `screenshotUrl` string, uri, nullable, required: GET only, for the owner's own key: a private link to the picture of what the browser saw, good for 10 minutes. Null for an OAuth client (an agent), and on the POST.
  - `message` string, required: What happened, in plain words.

## Other responses

- `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`).
- `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/4534f5d25540?raw)
