POST Login

Single-call credentials-mode login for catalogs that require a username/password

POSThttps://api.openwire.sh/v1/wire/login

Sign in to a catalog that requires credentials, and get back a credential_id you can immediately use with POST /v1/wire/task.

The endpoint:

  1. Resolves the catalog by slug
  2. Finds-or-creates an identity for you (auto-named from params if you don't supply identity_name)
  3. Runs the catalog's login wheel against the supplied params
  4. Persists a cookies-only credential (the password is never stored)
  5. Returns everything needed to call authenticated actions

The password is discarded after a successful sign-in. Only the issued cookies are stored, encrypted. When the cookies expire (see expires_at), call /login again to mint fresh ones.

For browser-based catalogs (auth_types includes browser_state but not credentials), use the interactive connect flow instead.


Request Body

{
  "catalog_slug": "neb",
  "identity_name": "work-account",
  "params": {
    "email": "alice@example.com",
    "password": "..."
  }
}
ParameterTypeDescription
catalog_slug requiredstringCatalog slug, e.g. neb. Find it in GET /v1/wire/catalog.
identity_namestringOptional label for the identity. If omitted, derived from params (priority: email > username > user > login > account_id > account, falling back to "default"). Same name re-uses the same identity row across calls.
params requiredobjectWheel-defined login fields. Shape varies per catalog — discover it by reading login_input_schema from GET /v1/wire/catalog/{slug}.

Discovering the params shape

Different catalogs require different fields. Always read the schema first:

curl {API_BASE}/catalog/neb \
  -H "X-API-Key: your_api_key"

The response includes login_input_schema — an array of {name, type, required, description} field descriptors. Send exactly those fields in params.


Response

201 Created
{
  "status": "verified",
  "identity_id": "7c3f1a2b-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
  "identity_name": "alice@example.com",
  "credential_id": "11111111-2222-3333-4444-555555555555",
  "catalog_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "catalog_slug": "neb",
  "expires_at": "2026-05-29T08:09:23Z"
}
FieldTypeDescription
statusstringAlways "verified" on success
identity_idstring (UUID)The identity this credential is attached to. Stable across re-runs with the same name.
identity_namestringThe name used (explicit or derived)
credential_idstring (UUID)Pass this as credential_id in subsequent POST /v1/wire/task calls.
catalog_idstring (UUID)The catalog's UUID — saves you another lookup
catalog_slugstringEchoed back
expires_atstringRFC3339 UTC. When the session expires. Capped at 30 days; defaults to 24h if the wheel doesn't supply one. Re-call /login to refresh.

Idempotent re-runs

Calling /login again with the same identity_name (or the same derived name) re-uses the existing identity row and refreshes its credential in place — no duplicate rows accumulate. Pass an explicit identity_name if you want multiple identities for the same catalog under one account (e.g. work + personal).


Error Responses

All errors return JSON of the form { "status": "error", "error": { "code": "...", "message": "...", "missing_fields"?: [...] } }. The wheel's raw error text is never echoed; only the typed code is surfaced.

CodeHTTPWhen
INVALID_BODY400Request body isn't valid JSON
INVALID_INPUT400catalog_slug or params missing
INVALID_PARAMS400One or more required: true fields from login_input_schema are missing, null, or blank. Response includes missing_fields: [...]. The wheel is not invoked — failures here cost nothing.
CATALOG_NOT_FOUND400No catalog matches catalog_slug
LOGIN_NOT_SUPPORTED400The catalog's auth_types doesn't include credentials, OR the catalog has no published login action
LOGIN_NOT_AVAILABLE503Credentials-mode login isn't configured on this engine (rare; infrastructure issue)
LOGIN_TRANSPORT_ERROR502Could not reach the wheel runner
BAD_PASSWORD200Sign-in rejected by the site — wrong credentials
MFA_REQUIRED200Account requires multi-factor auth (not yet supported)
CAPTCHA_REQUIRED200Site is showing a captcha challenge
ACCOUNT_LOCKED200Site reports the account is locked
LOGIN_TIMEOUT200Sign-in took too long
LOGIN_PAGE_CHANGED200Site's login flow may have changed
LOGIN_NO_COOKIES200Sign-in completed but no session cookie was issued
LOGIN_INFRASTRUCTURE_ERROR200Transient Browser Connect / upstream issue. Retry.
LOGIN_FAILED200Generic fallback — sign-in failed for an unclassified reason

HTTP 200 with status: "error" is used for "the request was well-formed and the wheel ran, but the sign-in itself didn't complete." Treat any status != "verified" as failure regardless of HTTP code.

Example INVALID_PARAMS response:

{
  "status": "error",
  "error": {
    "code": "INVALID_PARAMS",
    "message": "Missing required login fields: password",
    "missing_fields": ["password"]
  }
}

Code Examples

curl {API_BASE}/login \
  -X POST \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "catalog_slug": "neb",
    "params": {
      "email": "alice@example.com",
      "password": "your_password"
    }
  }'

Rate limit

10 requests per minute per user (each call triggers a real sign-in run against the target site).