---
title: "Verify Email"
method: POST
path: "/api/v1/auth/verify-email"
tags: ["auth"]
---

# Verify Email

`POST /api/v1/auth/verify-email`

Redeem a signup verification link and continue into admission.

Rate Limited: 5 requests per minute per IP address.

Does not require authentication — the link is the credential, matching the
account-setup redeem path. Each refusal gets its own status so the page can
tell the user what to do next; none of them names an account.

## Request body

- EmailVerificationRequest — Request model for redeeming a signup email-verification link.
  - `token` string, required

## Response `200`

Successful Response

- SignupResponse — Where a signup ended up. ``account_id`` is absent for the two outcomes that create no account — an address that already has one, and one still waiting on its verification link. ``intent`` is the intent the server actually recorded, never the one the client asked for: with the paid door switched off a paid request comes back ``free``, and a client that routes to checkout on its own request rather than on this answer would send the buyer to a checkout the workspace was never admitted for.
  - `status` 'admitted' | 'waitlisted' | 'preparing' | 'verification_required' | 'already_registered', required
  - `account_id` string, uuid, nullable
  - `intent` 'free' | 'paid' — What a public signup is asking for. ``PAID`` is what a buyer arriving from the pricing page carries. It is not a funding fact — nothing is charged until checkout, and the workspace opens on the same joining grant a free signup gets — so it decides only two things, and this is the one statement of them that the rest of the codebase points at rather than restates: 1. None of the free Discovery experiment's controls bind. The seat cap, the waitlist and the operator pause are that experiment's budget, and a buyer is not in it: a paid account holds no seat, so admitting one spends nothing the cap was sized to protect. Its abuse gates still bind — a verified address, the rate limits and the disposable-domain block — because those protect MAIA, not the experiment. The work-email rule is the exception: it decides who the free experiment is offered to, and refusing a buyer for paying from a personal address is a lost sale. 2. The workspace records ``WorkspaceOrigin.DIRECT_PAID`` rather than ``SELF_SERVE_DISCOVERY``. It is not a privilege worth stealing: everything it unlocks is reachable through the free door by anyone the paid door would admit, except during the hours the free cohort is full or paused.

## Other responses

- `422` — Validation Error

## Changes

- **2026-09-19** `924eeadc29ad` — 1 info
  - added the optional property `intent` to the response with the `200` status
- **2026-08-22** `136e4af4d514` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/maia-analytics/apis/maia-api/changes/api/v1/auth/verify-email/post.md)

---

[API](https://skmtc.dev/maia-analytics/apis/maia-api.md) · [All operations](https://skmtc.dev/maia-analytics/apis/maia-api/llms.txt) · [OpenAPI document](https://skmtc.dev/maia-analytics/apis/maia-api/revisions/8dc47faf0418?raw)
