> For the complete documentation index, see [llms.txt](https://api-docs.visadoc.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://api-docs.visadoc.co/magic-link.md).

# Magic Link

Get a passwordless sign-in token that logs a user straight into VisaDoc, optionally landing them on a specific assessment or application. This is what powers the [assessment-driven handoff](/use-cases/assessment-driven-handoff.md): you build the link, send it, the user clicks, they're in, with no VisaDoc password.

## Endpoint

```
GET /magic_link
```

Authenticated with your Bearer token and `X-Data-Source`, like every endpoint.

Only instance Admins can call it (every token from `POST /token` belongs to an Admin), and only for users in that token's data source.

## Parameters

| Parameter | Required | Description                                              |
| --------- | -------- | -------------------------------------------------------- |
| `email`   | yes      | Query parameter. The email of the user the token is for. |

## What you get back

The response body is the sign-in token, as a JSON string:

```json
"eyJhbGciOiJBMjU2S1ciLCJlbmMiOi…"
```

Parse the body as JSON to get the token value. If you read the raw body as text, the quotes become part of the token and the link won't work.

The endpoint returns only the token. You build the link yourself, as below.

## Building the link

The link always points to your instance's `/magic_login` page. That page hosts the authentication logic: a link to any other page won't sign the user in.

```
https://<instance_id>.visadoc.co/magic_login
```

Replace `<instance_id>` with your instance's subdomain, e.g. `acme` for `https://acme.visadoc.co`. In staging, use your staging base URL with the same `/magic_login` path.

Add the token as the `token` query parameter:

| Your link                | With the token                                              |
| ------------------------ | ----------------------------------------------------------- |
| No query params          | `…/magic_login` → `…/magic_login?token=<token>`             |
| Already has query params | `…/magic_login?tab=…` → `…/magic_login?tab=…&token=<token>` |

## How long a link lasts

* A link is valid for **60 minutes**.
* It's **single-use**: it stops working as soon as it has been used.

So request the token at the moment you send the user into VisaDoc, not in advance, and don't store it. If the user needs to come back later, request a new one.

## Where the user lands

Routing params on the link decide where the user ends up after signing in.

{% stepper %}
{% step %}

## Portal home

No routing params:

```
https://<instance_id>.visadoc.co/magic_login?token=<token>
```

After login → dashboard home.
{% endstep %}

{% step %}

## A specific assessment

Add `tab=assessment-form` and the assessment id:

```
https://<instance_id>.visadoc.co/magic_login?tab=assessment-form&assessment=<assessment_id>&token=<token>
```

After login → that assessment's results, where the user can pick an entry option and start the application.
{% endstep %}

{% step %}

## A specific application

Add `tab=application` and the application id:

```
https://<instance_id>.visadoc.co/magic_login?tab=application&application=<application_id>&token=<token>
```

After login → that application.
{% endstep %}
{% endstepper %}

> Mind the naming: for an assessment the tab is `assessment-form` and the id param is `assessment`; for an application the tab is `application` and the id param is `application`.

**Anything else.** The two patterns above are the common destinations, but `/magic_login` carries through any query params the dashboard understands. Add whatever is relevant to your flow, and add the token the same way.

## Example

**1 — Request the token.** `--data-urlencode` encodes the email, so addresses like `jane+travel@acme.com` arrive intact:

```bash
curl -G https://api.visadoc.co/api:v3/magic_link \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Data-Source: YOUR_DATA_SOURCE" \
  --data-urlencode "email=jane@acme.com"
```

Response:

```json
"eyJhbGciOiJBMjU2S1ciLCJlbmMiOi…"
```

**2 — Build the link**, e.g. to open application `123`:

```
https://<instance_id>.visadoc.co/magic_login?tab=application&application=123&token=<token>
```

In code, a URL builder adds the `?` or `&` for you:

```javascript
const res = await fetch(
  `https://api.visadoc.co/api:v3/magic_link?email=${encodeURIComponent(email)}`,
  { headers: { Authorization: `Bearer ${API_TOKEN}`, "X-Data-Source": DATA_SOURCE } }
);
const token = await res.json(); // the body is a JSON string

const link = new URL(`https://${INSTANCE_ID}.visadoc.co/magic_login`);
link.searchParams.set("tab", "application");
link.searchParams.set("application", applicationId);
link.searchParams.set("token", token);
// link.toString() is ready to send
```

## Good to know

* The token signs the user in. Treat it like a password: send it only to that user, and keep it out of logs and analytics.
