# Create a single-use sign-in link for one of your students

- Endpoint: `POST /v1/{organizationId}/auth/signin`
- Production: `https://api.main-team.org/v1/{organizationId}/auth/signin`
- Sandbox: `https://apisnd.main-team.org/v1/{organizationId}/auth/signin`
- Operation: `createSigninLink` (Sign-in links)
- Authentication: `Authorization: Bearer <token>`, a short-lived token you sign with your API key and secret
- Permission: `auth/signin:$org:$ID`

## Description

Returns a URL that signs the student in to this organization’s student panel. **It works once, and for 120 seconds** (`expiresIn`) from when it is issued, whether or not anyone opened it. Whoever opens it first is signed in as the student, and a second visit is refused, so:

- redirect the student’s browser to it straight away;
- never log it, email it or show it anywhere else;
- ask for a new link for every sign-in.

Each call issues a new link and leaves the earlier ones as they were until they expire. Nothing is issued to your account: no token, cookie or session, only the URL.

**The student needs access to this organization first:** their `activatedPlatformsThisSeason` must hold this organization’s `slug` or `common`. `registerStudent` gives `common` unless you send a list; otherwise grant access with `updateOrgStudent`. Without it the answer is 403 with a message saying so, and nothing is changed.

The student does not need a confirmed email address: a student you have just registered can be sent a link straight away.

`redirect` chooses where on the panel the student lands. Leave it out to land on the organization’s `defaultRedirect`, as `getOrganization` shows it.

**Make this the first call for a student new to this organization.** The organization creates its own record of a student the first time they open a link to it, and `linkStudentSupervisor` and the application, certificate and report operations on this organization need that record.

Only students your account registered can be signed in; another account’s student answers 404, like an unknown id.

## Parameters

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `organizationId` | path | yes | string | The organization’s `_id`: 24 hexadecimal digits, as `listOrganizations` (`GET /v1/organization`) lists it. |

## Request body

Required fields: `studentId`.

```json
{
  "studentId": "6650a1b2c3d4e5f6a7b8c9d0",
  "redirect": "/dashboard"
}
```

## Response

`200` Success: `message` is "Sign-in link generated successfully.".

```json
{
  "success": true,
  "message": "Sign-in link generated successfully.",
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 57,
    "totalPages": 3
  },
  "data": {
    "url": "https://auth.main-team.org/api/user/oauth/invoke?accessToken=exampletokenexampletokenexampletoken&redirectUrl=%2Fdashboard",
    "organization": "stem",
    "studentId": "6650a1b2c3d4e5f6a7b8c9d0",
    "expiresIn": 120
  }
}
```

## Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | [`bad_request`](https://hub.main-team.org/api/errors#bad_request) | The body is not valid JSON, breaks a field’s rules, or has a field this operation does not accept ("property <name> should not exist"). |
| 400 | [`bad_request`](https://hub.main-team.org/api/errors#bad_request) | `redirect` is not a path on the panel: it needs one leading `/`, and no whitespace or backslash anywhere. |
| 401 | [`unauthorized`](https://hub.main-team.org/api/errors#unauthorized) | The token is missing or malformed, is not signed with your account’s `apiSecret`, breaks the `iat` and `exp` rules, has expired or been revoked, or its account is not active. All of these answer the same. |
| 403 | [`forbidden`](https://hub.main-team.org/api/errors#forbidden) | The token is valid, but no role on your account allows `auth/signin` on the organization in the path, or a role denies it. |
| 403 | [`forbidden`](https://hub.main-team.org/api/errors#forbidden) | The student has no access to this organization: their `activatedPlatformsThisSeason` holds neither its `slug` nor `common`. Grant it with `updateOrgStudent`, then ask again. |
| 404 | [`not_found`](https://hub.main-team.org/api/errors#not_found) | `organizationId` is not the `_id` of an organization. |
| 404 | [`not_found`](https://hub.main-team.org/api/errors#not_found) | No student of yours has this `studentId`. A student registered by another account answers the same. |
| 413 | [`payload_too_large`](https://hub.main-team.org/api/errors#payload_too_large) | The body is larger than 100 kB. |
| 415 | [`unsupported_media_type`](https://hub.main-team.org/api/errors#unsupported_media_type) | The body declares a charset that is not a UTF one (send UTF-8), or a `Content-Encoding` other than gzip, deflate or br. |
| 429 | [`too_many_requests`](https://hub.main-team.org/api/errors#too_many_requests) | Your account has made more than 100 requests to this operation in the current 60-second window. Wait the seconds in `Retry-After` before sending again. |
| 500 | [`internal_error`](https://hub.main-team.org/api/errors#internal_error) | Something failed on our side. Retry later, and quote `request_id` if it goes on. |
| 500 | [`internal_error`](https://hub.main-team.org/api/errors#internal_error) | The link could not be issued. Send the request again. |

## Code samples

### curl

```bash
# $TOKEN: a short-lived token you minted with your API key and secret
curl -sS -X POST 'https://api.main-team.org/v1/<organizationId>/auth/signin' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "studentId": "6650a1b2c3d4e5f6a7b8c9d0",
  "redirect": "/dashboard"
}
JSON
```

### Node.js

```js
const token = process.env.TOKEN; // a short-lived token you minted with your API key and secret

const res = await fetch('https://api.main-team.org/v1/<organizationId>/auth/signin', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "studentId": "6650a1b2c3d4e5f6a7b8c9d0",
    "redirect": "/dashboard"
  }),
});
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.error.code}: ${body.error.message}`);
console.log(body.data);
```

### PHP

```php
<?php
$token = getenv('TOKEN'); // a short-lived token you minted with your API key and secret

$ch = curl_init('https://api.main-team.org/v1/<organizationId>/auth/signin');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $token,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'studentId' => '6650a1b2c3d4e5f6a7b8c9d0',
        'redirect' => '/dashboard',
    ]),
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
if ($response === false) {
    throw new RuntimeException(curl_error($ch));
}
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$body = json_decode($response, true);
if ($status >= 400) {
    $error = $body['error'];
    throw new RuntimeException("$status {$error['code']}: {$error['message']}");
}
print_r($body['data']);
```
