Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.deck.co/llms.txt

Use this file to discover all available pages before exploring further.

Build your own credential collection flow with full control over the look, feel, and UX, and Deck stores the credentials securely in the Credential Vault.
For simple credential collection flows that don’t require customization, the pre-built Auth Component is available as a drop-in React component.

How it works

Credentials go directly from the user to your server over HTTPS. Your server stores them with Deck. The credential is created immediately with unverified status and becomes verified the first time a task run authenticates successfully.

Credential statuses

When you first store a credential, it starts as unverified. The credential is verified automatically when it is used successfully in a task run. During that task run, the source may require additional input from the user — like an MFA code or a security question — which puts the task run into an interaction_required state. Once any required interactions are resolved and the task run completes successfully, the credential moves to verified. If the source rejects the credentials, the status becomes invalid.
StatusWhat to show
unverifiedCredentials stored. Ready to use on task runs.
verifiedCredentials confirmed working.
invalidSource rejected the credentials. Prompt the user to update.
deletedCredential was permanently removed.

Step-by-step implementation

1

Build a credential form

Start with a form that collects the credentials required by the source. Most sources use username_password.
function StoreCredentials({ sourceId }: { sourceId: string }) {
  const [status, setStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle')

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    setStatus('saving')

    const form = new FormData(e.currentTarget)
    const res = await fetch('/api/credentials', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        source_id: sourceId,
        email: form.get('email'),
        password: form.get('password'),
      }),
    })

    if (res.ok) {
      setStatus('saved')
    } else {
      setStatus('error')
    }
  }

  if (status === 'saving') return <p>Saving credentials...</p>
  if (status === 'saved') return <CredentialSaved />
  if (status === 'error') return <p>Something went wrong. Please try again.</p>

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />

      <label htmlFor="password">Password</label>
      <input id="password" name="password" type="password" required />

      <button type="submit">Save Credentials</button>
    </form>
  )
}
2

Store the credential server-side

Your server receives the credentials and stores them with Deck. This can be an Express handler, a Next.js API route, or any server-side endpoint.
app.post('/api/credentials', async (req, res) => {
  const { source_id, email, password } = req.body

  const credential = await fetch('https://api.deck.co/v2/credentials', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.DECK_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      source_id,
      auth_method: 'username_password',
      auth_credentials: { username: email, password },
      external_id: req.user.id, // your internal user ID, used to map the credential back to the user in your system
    }),
  })

  const data = await credential.json()
  res.json(data)
})
The response comes back with status: "unverified". The credentials are encrypted and stored in the Credential Vault.
3

Listen for credential events

Deck sends events to your event destination when credential statuses change.
app.post('/webhooks/deck', async (req, res) => {
  const event = req.body

  switch (event.type) {
    case 'credential.verified':
      await notifyClient(event.data.external_id, {
        status: 'verified',
        credential_id: event.data.id,
      })
      break

    case 'credential.invalid':
      await notifyClient(event.data.external_id, {
        status: 'invalid',
        message: 'Credentials were rejected. Please update them.',
      })
      break

    case 'task_run.interaction_required':
      // The source is asking for user input (e.g. MFA code, security question)
      await notifyClient(event.data.credential_id, {
        status: 'interaction_required',
        task_run_id: event.data.id,
        interaction: event.data.interaction, // contains type, message, and fields
      })
      break
  }

  res.sendStatus(200)
})

Handling interactions

Some sources require additional input during a task run, like an MFA code or a security question. When this happens, the task run pauses and Deck fires a task_run.interaction_required event. The event includes an interaction object that describes what the source needs:
{
  "type": "mfa",
  "message": "Enter the verification code sent to your phone",
  "fields": [
    {
      "name": "code",
      "type": "string",
      "label": "Verification code"
    }
  ]
}
Use the fields array to render a form that collects the required input. Once you have the user’s response, submit it to resume the task run:
app.post('/api/task-runs/:taskRunId/interaction', async (req, res) => {
  const response = await fetch(
    `https://api.deck.co/v2/task-runs/${req.params.taskRunId}/interaction`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.DECK_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        input: { code: req.body.code },
      }),
    }
  )

  const data = await response.json()
  res.json(data)
})
The task run resumes where it left off and continues to completion. A task run may require multiple interactions — for example, an MFA code followed by a security question. See the Interactions guide for more detail.
4

Show success

Once the credential is stored, show a success state and move the user forward in your product.
function CredentialSaved() {
  return (
    <div>
      <h3>Credentials saved</h3>
      <p>Your account has been linked. Credentials will be verified on the first task run.</p>
    </div>
  )
}

Handling errors

Build your UI to handle common failure cases gracefully:
ScenarioWhat happenedRecommended UX
Invalid credentialsSource rejected the username/password during a task runShow an error. Let the user update the credential with corrected details.
Source unavailableThe target website is down or blockingShow a temporary error. Suggest trying again later.
Credential deletedCredential was permanently removedPrompt the user to add new credentials.

Updating credentials

When a credential becomes invalid, prompt the user to re-enter their details and update the credential with a PATCH request.
app.patch('/api/credentials/:credentialId', async (req, res) => {
  const { email, password } = req.body

  const credential = await fetch(
    `https://api.deck.co/v2/credentials/${req.params.credentialId}`,
    {
      method: 'PATCH',
      headers: {
        'Authorization': `Bearer ${process.env.DECK_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        auth_credentials: { username: email, password },
      }),
    }
  )

  const data = await credential.json()
  res.json(data)
})
The credential status resets to unverified and will be re-verified on the next task run.