Passwordless email sign-in for the inference.sh workspace. The same email includes a clickable link and a short login code for users who read email on a different device than the one they sign in on.
The workspace app uses this flow on the login page. You can call the endpoints directly to build a custom sign-in experience.
No API key is required for send or verify endpoints. Successful verification sets a session cookie on the response (same cookie the workspace uses for browser sessions).
→ Authentication · Device authorization (CLI and IDE login)
Flow overview
1sequenceDiagram2 participant Client as Your app or browser3 participant API as api.inference.sh4 participant Email as User email56 Client->>API: POST /auth/magic-link/send {email}7 API->>Email: Magic link + 5-char login code8 alt Click link (same device)9 Email->>API: GET /auth/magic-link/verify?token=…10 API-->>Client: Redirect to workspace (session cookie set)11 else Enter code (different device)12 Client->>API: POST /auth/magic-link/verify-code {email, code}13 API-->>Client: AuthResponse + session cookie14 end15 opt Admin account16 Client->>API: POST /auth/otp/verify {code}17 API-->>Client: Session fully unlocked18 end- Call
POST /auth/magic-link/sendwith the user's email. - The user receives an email with a verification link and a 5-character login code (valid for 15 minutes). Both redeem the same challenge — using one consumes the other.
- Complete sign-in by clicking the link (
GET /auth/magic-link/verify) or submitting the code (POST /auth/magic-link/verify-code). - Admin accounts require a second factor: after magic-link or code sign-in, the API returns
otp_required: trueand emails a 6-digit OTP. CallPOST /auth/otp/verifybefore accessing OTP-gated routes. If the OTP expires, the next gated request automatically sends a fresh code.
Send magic link
POST /auth/magic-link/send
Authentication: None.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address to sign in with |
redirect_to | string | No | Path or URL appended to the verification link (for example /dashboard) |
1curl -X POST https://api.inference.sh/auth/magic-link/send \2 -H "Content-Type: application/json" \3 -d '{"email": "[email protected]"}'Response
The API always returns success to prevent email enumeration, even when the address is unknown or rate limited (except IP-level throttling):
1{2 "message": "If an account exists, a magic link has been sent"3}When the same email requests another link within the burst window (about one per minute), the response includes:
1{2 "message": "Please wait before requesting another link",3 "rate_limited": true,4 "retry_after": 605}IP-level rate limits return HTTP 429 with a Retry-After header (seconds). The header is exposed to browser clients via CORS.
Verify via link (browser)
GET /auth/magic-link/verify?token={token}
Authentication: None.
Used when the user clicks the link in the email. On success, the API sets the session cookie and redirects to the workspace (or to redirect_to when provided at send time). Admin users are redirected with otp_required=true in the query string.
For SPA or mobile clients that handle the token themselves, use the JSON endpoint below instead.
Verify via token (JSON)
POST /auth/magic-link/verify
Authentication: None.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Token from the magic link URL |
1curl -X POST https://api.inference.sh/auth/magic-link/verify \2 -H "Content-Type: application/json" \3 -c cookies.txt \4 -d '{"token": "TOKEN_FROM_EMAIL_LINK"}'Response
1{2 "user": { "id": "user_abc", "email": "[email protected]", "username": "you" },3 "session_id": "sess_xyz",4 "otp_required": false5}When otp_required is true, complete OTP verification before calling OTP-gated API routes. The session cookie is set on the response either way.
Verify via login code
POST /auth/magic-link/verify-code
Authentication: None.
Device-independent sign-in: the user reads the 5-character code from email and enters it on the sign-in device.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Same email used in send |
code | string | Yes | 5-character code from the email (case-insensitive) |
1curl -X POST https://api.inference.sh/auth/magic-link/verify-code \2 -H "Content-Type: application/json" \3 -c cookies.txt \4 -d '{"email": "[email protected]", "code": "AB3K9"}'Response
Same AuthResponse shape as verify via token. The session cookie is set on success.
Errors
| Code | HTTP | Description |
|---|---|---|
invalid_code | 400 | Code wrong, expired, or already used |
account_locked | 429 | Too many failed attempts for this email |
rate_limited | 429 | Too many verification attempts from this IP |
Failed code attempts count toward per-email lockout. Codes use an alphanumeric charset without ambiguous characters (0, 1, O, I, L).
Admin OTP (second factor)
Admin accounts require email OTP after magic-link or code sign-in. This is separate from the passwordless login code — it is session-bound 2FA.
| Endpoint | Method | Description |
|---|---|---|
/auth/otp/status | GET | Whether the current session needs OTP (required, verified) |
/auth/otp/verify | POST | Body: { "code": "123456" } — completes 2FA |
/auth/otp/resend | POST | Sends a new OTP to the admin's email |
All OTP routes require the session cookie from magic-link verification.
When an OTP-gated route returns 403 with code otp_required, the platform automatically emails a fresh OTP if the previous one is missing or expired (so the prompt is never a dead end).
1# Check whether OTP is still required2curl https://api.inference.sh/auth/otp/status -b cookies.txt34# Submit the 6-digit code from email5curl -X POST https://api.inference.sh/auth/otp/verify \6 -H "Content-Type: application/json" \7 -b cookies.txt \8 -d '{"code": "123456"}'Related
- Device authorization — CLI and IDE login (returns an API key instead of a session cookie)
- Authentication — API keys for programmatic access
- REST API overview — optional
/v1/path prefix