---
title: "Consume a magic link and start a session"
method: POST
path: "/api/v1/auth/verify"
---

# Consume a magic link and start a session

`POST /api/v1/auth/verify`

## Request

- Base URL: `https://applytovolunteer.com`
- URL: `https://applytovolunteer.com/api/v1/auth/verify`
- Auth: none required

A same-origin POST with a usable token consumes it and returns 302 when the client IP is present. A template link redirects to /login?template={slug}. A plain link redirects to /app, a safe next path, /app/forms/{id} after a preview claim, /app?preview=expired, or /tools/p/{token}. A POST that fails sameOriginWrite returns 403 and does not consume the token. That includes a cross-site Sec-Fetch-Site. A present session cookie does not change that 403. When the cookie-mutation origin check refuses the POST, prefersHtml still selects the HTML page and any other Accept still selects the JSON error. When prefersHtml is true for Accept, the body is text/html titled Open this link in a current browser, with that sentence and a link to /login?template={slug} when the token meta has a known template slug. Otherwise that link is /login. Any other Accept, including a missing header, returns application/json { error: "forbidden" }. PUT, PATCH, and DELETE that fail the cookie-mutation origin check return that JSON error even when Accept prefers HTML. A missing token returns 400 { error: "token required" }. An unknown, used, expired, or wrong-purpose token returns 400 { error: "invalid or expired" }. A consumed link with no client IP returns 400 { error: "missing_client_ip" }. No API key. The handler does not read a request body.

## Query parameters

- `token` string, required: Magic-link token from the email.

## Other responses

- `302`: Token consumed and a session cookie is set. A template link redirects to /login?template={slug}. A plain link redirects to /app, a safe next path, /app/forms/{id} after a preview claim, /app?preview=expired, or /tools/p/{token}.
- `400`: { error: "token required" } when the token query is missing. { error: "invalid or expired" } when the token is unknown, used, expired, or the wrong purpose. { error: "missing_client_ip" } when the link was consumed and the client IP cannot be resolved.
- `403`: sameOriginWrite refused the POST, including a cross-site Sec-Fetch-Site. A present session cookie does not change the body. The token is not consumed. text/html when prefersHtml is true for Accept. The page says Open this link in a current browser and links to /login?template={slug} when the token meta has a known template slug, or to /login when it does not. application/json { error: "forbidden" } for every other Accept.

---

[API](https://skmtc.dev/applytovolunteer/apis/apply2volunteer-api.md) · [All operations](https://skmtc.dev/applytovolunteer/apis/apply2volunteer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/applytovolunteer/apis/apply2volunteer-api/revisions/23df1ec8c88d?raw)
