Source: https://help.spamfoo.com/api/login-link
Last updated: 2026-10-27

***

# Create a Sign-In Link

Make a one-time link that signs someone in to the SpamFoo Dashboard with the role you choose. A mail server or control panel can use it to send its users to the dashboard without sharing the proxy secret.

`POST /generate-login-link`

## When to Use It

- Add a "SpamFoo Dashboard" link to your own webmail or control panel, and make a fresh sign-in link each time a user clicks it.
- Give a domain administrator access to the domains they manage, and nothing else.

To make a link by hand on the server, use [`spamfoo-client login-link`](https://help.spamfoo.com/guides/commands#login-link) instead. For what each role sees, see [Manage Sign-In and Roles](https://help.spamfoo.com/guides/roles).

## Request

Send the current proxy secret in `X-SpamFoo-Proxy-Auth`, even from loopback. Send a JSON body with `Content-Type: application/json`.

```
{
    "role": "DomainAdmin",
    "domains": ["example.com", "example.org"],
    "email": "admin@example.com",
    "expiresInMinutes": 15,
    "returnPath": "/admin/"
}
```

| Field | Required | Type | Description |
| --- | --- | --- | --- |
| `role` | Yes | string | `SystemAdmin`, `DomainAdmin`, or `User`. Letter case doesn't matter. |
| `domain` or `domains` | For `DomainAdmin` | string, or array of strings | The domain, or up to 50 domains, a Domain Admin can manage. Send one of the two fields, not both. Don't send either for the other roles. |
| `email` | For `User` | email address | The mailbox a User sees. For a Domain Admin, also shows that mailbox's own pages. Optional for `SystemAdmin` and `DomainAdmin`. |
| `expiresInMinutes` | No | integer | How long the link can be used, from 1 to 1440. The default is 15. The sign-in it creates lasts 24 hours either way. |
| `returnPath` | No | string | A dashboard page to open after sign-in. It must be `/admin` or start with `/admin/`, and be at most 1024 characters. |

Domains and email addresses are stored in lowercase, and international domain names in their Unicode form, so `Example.COM` and `example.com` are the same domain.

## Response

```
{
    "url": "http://spamfoo.example.com:16253/admin/login?t=...",
    "expiresAt": "2026-10-13T14:15:00+00:00"
}
```

- `url` starts with `SPAMFOO_PUBLIC_URL` in standalone mode when it's set. Otherwise it uses the address your request was sent to, so call SpamFoo Client at the address the user's browser will use.
- Opening the link shows a **Sign in to SpamFoo** page. The link is used up only when the person clicks **Continue**, so a mail scanner that opens links in advance doesn't use it.
- Each link works once. Changing the proxy secret stops every unused link.

> **Warning** Treat a sign-in link like a password until it's used or expires. Send it only to the person it's for, over HTTPS where you can.

## Errors

| Status | Body | Cause |
| --- | --- | --- |
| `400` | `{"error": "..."}` | A field is missing or invalid, such as `role must be SystemAdmin, DomainAdmin or User.` or `expiresInMinutes must be between 1 and 1440.` The message names the field. |
| `401` | Empty | The proxy secret was missing or wrong. |
| `429` | `{"error": "...", "code": "auth_blocked"}` | Standalone mode only. Too many wrong secrets came from this address. See [Repeated Wrong Secrets](https://help.spamfoo.com/api#blocked). |

## Example

### curl

```
curl -s -X POST http://localhost:16253/generate-login-link \
    -H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \
    -H "Content-Type: application/json" \
    -d '{"role":"User","email":"user@example.com"}'
```

### C#

```
using System.Net.Http.Json;

var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET")
    ?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET.");

using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") };
http.DefaultRequestHeaders.Add("X-SpamFoo-Proxy-Auth", secret);

using var response = await http.PostAsJsonAsync("/generate-login-link",
    new { role = "User", email = "user@example.com" });
response.EnsureSuccessStatusCode();
var link = await response.Content.ReadFromJsonAsync<LoginLink>();
Console.WriteLine(link!.Url);

record LoginLink(string Url, DateTimeOffset ExpiresAt);
```

### Python

```
import os
import requests

response = requests.post(
    "http://localhost:16253/generate-login-link",
    headers={"X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"]},
    json={"role": "User", "email": "user@example.com"},
    timeout=10,
)
response.raise_for_status()
print(response.json()["url"])
```
