XY Logo
Developer hub

Credentials and MFA

Give workflows a reference, never a password.

An XY API key authenticates your service to XY. A runtime credential authenticates an XY browser workflow or API integration to a third-party system. They have separate scopes, lifecycles, and exposure rules.

Two credential types

Control-plane auth and portal auth stay separate.

Confusing the partner key with the portal login is both a security risk and a broken workflow design.

XY API key

Your service → XY

  • Identifies one organization and environment.
  • Scopes REST and MCP operations.
  • A Personal key authorizes as its signed-in user, rechecks membership on every request, and applies that user's relevant workflow permissions.
  • A Service key authorizes as the organization's managed service identity for unattended systems.
  • Created in Organization settings and shown once.
  • Never supplied as workflow input or browser memory.

Runtime credential

XY runtime → third-party system

  • Stored in an encrypted, XY-hosted organization vault.
  • Supports username/password, API token, and optional authentication factors.
  • Referenced by a stable organization-owned credential ID.
  • Resolved only while validating or executing the bound activity.
  • Secret values are never returned by credential read routes.

Create

Store the secret once, then use only its reference.

Add a runtime credential in Password Vault, or provision an API-managed entry from a trusted backend. Both paths produce references that authorized workflows can use. Neither path should put secrets into an AI conversation.
Credential ownership and supported use
Created throughOwnerSupported use
Password Vault UIThe signed-in userSigned-in XY products plus public Planner browser steps and Browser Studio calls that can see the entry.
POST /credentialsThe organization's managed service identityPublic Planner, Integration Factory, Browser Studio, REST, and MCP workflows that pass the required authorization checks.
Create a secure credential reference
# Build this body from a protected secret source; do not paste it into shell history.
: "${XY_CREDENTIALS_SERVICE_KEY:?Use an organization service key with credentials:write}"
curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_CREDENTIALS_SERVICE_KEY}" \
  --header "Content-Type: application/json" \
  "${XY_API_BASE}/credentials" \
  --data @credential.json


# credential.json
{
  "domain": "portal.example.com",
  "site_name": "Example operations portal",
  "username": "workflow-user",
  "password": "read-from-your-secret-manager",
  "login_url": "https://portal.example.com/login",
  "sms_phone_type": "voip",
  "totp": "optional-base32-seed",
  "pin": "optional-static-pin",
  "notes": "optional private vault notes"
}

credentials:read can be granted to personal or system keys for safe metadata discovery. Among manually issued API keys, credentials:write remains system-key-only. Separately authorized MCP OAuth clients may receive it through the live OAuth grant policy. Writes manage API-created entries. Keep write access out of ordinary Planner agent keys and analytics keys.

Integration Factory tests, validation, and connections still require API-managed credentials created through POST /credentials, not human-created Vault entries.

Use an API-managed credential in the Integration Factory

Bind

The ID crosses the workflow boundary; the secret does not.

Personal and SYSTEM API keys with planner:write can bind an exact credential reference visible to that identity. Use credentials:read to discover IDs; neither credential scope is required when an eligible exact ID is already known. Secret-shaped fields are recursively rejected.
Discover visible Password Vault references
# credentials:read is available to personal and organization service keys.
curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/credentials"


# Match site_name, domain, and username. If no entry or multiple entries match,
# ask the user which Password Vault entry to use. Never guess or request the secret.
Start the Planner agent with a credential reference
# A personal or organization service key with planner:write can bind a visible entry.
: "${XY_PLANNER_API_KEY:?Use a key with planner:write}"
curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_PLANNER_API_KEY}" \
  --header "Idempotency-Key: portal-flow-v1" \
  --header "Content-Type: application/json" \
  "${XY_API_BASE}/planner/builds" \
  --data '{
    "request": "Sign in and collect the open records.",
    "context": {
      "portal_url": "https://portal.example.com/open-records",
      "credential_id": "cred_REPLACE"
    }
  }'

Validate

The reference must be active, unrevoked, in the same organization, and visible to the calling identity. Binding does not grant credential listing or management and never exposes secret values.

Bind

The canonical vault domain is derived by XY. A conflicting caller-supplied domain is rejected.

Resolve

The runtime loads the credential only for the bound browser authoring or execution session.

Runtime memory

Each authentication factor has a specific job.

The browser SDK receives a small, conventional set of ephemeral memory values. Generated activity code reads them at runtime; it must never embed their values in source.
Vault fieldEphemeral browser memoryBehavior
Usernamecredentials_usernameUsed for the portal identity field.
Passwordcredentials_passwordWrite-only secret resolved for the session.
TOTP seedcredentials_otp_codeOnly the current generated code enters memory; long sessions refresh it. The seed never does.
Static PINcredentials_pinAvailable only when the portal has a separate static factor.
Selected SMS verification numbercredentials_phone_numberThe runtime resolves either the organization's dedicated VoIP number or XY's shared real SIM number from the credential's sms_phone_type choice.
Provision or retrieve the default SMS inbox
# Requires credentials:write. Identity comes from the bearer key.
curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  --header "Content-Type: application/json" \
  "${XY_API_BASE}/sms-inbox" \
  --data '{
    "country_code": "US",
    "area_code": "415"
  }'


# First successful call: 201 Created with "created": true.
# Every later call: 200 OK with the same number and "created": false.
# Use {} when you do not need to request a preferred area code.
Read messages from the dedicated VoIP inbox
# Requires sms:read. Capture this immediately before requesting the portal code.
AFTER_MS=$(date +%s000)


curl --fail-with-body \
  --header "Authorization: Bearer ${XY_API_KEY}" \
  "${XY_API_BASE}/sms-inbox/messages?after_ms=${AFTER_MS}&limit=20"


# Repeat about every four seconds until the portal message appears.
# MCP equivalent: sms_inbox_messages(after_ms, limit)

MFA and human gates

Automate known factors, hand off consent and unknown challenges.

Some authentication can be deterministic; some must remain an explicit human action. Design both paths instead of treating every challenge as a selector problem.

Good automation candidates

  • Username and password form entry.
  • TOTP generated from the stored seed.
  • A known static PIN field.
  • SMS codes received by the organization's configured automation inbox.
  • Session restoration and deterministic post-login checks.

Good human gates

  • New-device consent and terms acceptance.
  • CAPTCHA or page-side verification.
  • Push approval or an OTP channel not integrated with XY.
  • Any step that requires an accountable person's judgment.

Rotate and revoke

Change the secret without changing the workflow.

The public credential ID is stable across rotation. Workflows do not need to be rebuilt just because the portal password changes.
Replace or revoke
: "${XY_CREDENTIALS_SERVICE_KEY:?Use an organization service key with credentials:write}"
curl --fail-with-body --request POST \
  --header "Authorization: Bearer ${XY_CREDENTIALS_SERVICE_KEY}" \
  --header "Content-Type: application/json" \
  "${XY_API_BASE}/credentials/cred_REPLACE/rotations" \
  --data @replacement.json


# The public ID stays the same, so promoted workflows use the replacement next run.
curl --fail-with-body --request DELETE \
  --header "Authorization: Bearer ${XY_CREDENTIALS_SERVICE_KEY}" \
  "${XY_API_BASE}/credentials/cred_REPLACE"

Security FAQ

What happens to a secret after you give it to XY?

These boundaries apply to portal passwords, API tokens, TOTP seeds, static PINs, and private credential notes.