> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.awardspring.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.awardspring.com/_mcp/server.

# Create a student.

POST https://api.awardspring.com/api/v1/students
Content-Type: application/json

Adds one student account, with the same rules as adding a student from the Users list in the AwardSpring
admin interface: a first name, a last name and a well-formed email are required, each at most 50
characters. An optional `student_id` must not already be on another account. The new student gets the
same welcome email the admin interface sends, with a link to set a password.

An email that already has an account: by default the email must not already be used by any account
at the institution, ignoring upper and lower case; if it is, this returns `400 email_already_exists`.
With `if_email_in_use` set to `add_role`, this does what the admin interface does instead, and no
second account is made. When an active account at the institution has the email, the student role is added
to that account, which keeps its name, email, password and student ID (a `student_id` sent with the request
is still checked against other accounts but is not saved), and this returns `200` with that account as
the student. An account that already has the student role is returned unchanged, also with `200`. The
role is never added to an account that holds an administrator role, and this returns
`400 email_already_exists` instead, as it does for an email whose account is deactivated, merged, or one
the admin interface would not change. When only an archived account has the email, that account gets no
role: a new student is made with `201` and the email moves to them, as in the admin interface.
The `AwardSpring-Existing-Account` header says which of these happened: `role_added`,
`role_already_held` or `email_moved_from_archived`. It is absent for an ordinary create.

Welcome email: the response carries an `AwardSpring-Welcome-Email` header. It is `queued`
when the email is on its way, `not_sent` when it could not be sent, and `none` when the role went
onto an account that already had the email (`if_email_in_use` set to `add_role`), which gets no
welcome email. The student is created either way; for
`not_sent`, send them a set-password link with Reset Password on their account in the admin
interface. A response replayed for an `Idempotency-Key` carries neither header.

Idempotency: the request supports an optional `Idempotency-Key` header (any
printable-ASCII string, 1–255 chars — UUIDs work well). When supplied, the same key plus the same
request body within 24 hours returns the original response verbatim without creating a duplicate
student. The same key with a different body returns `422 idempotency_key_request_mismatch`; a
concurrent retry while the first call is still executing returns `409 idempotency_key_in_flight`.

Reference: https://docs.awardspring.com/api-reference/students/create

## Authentication

- `X-Spring-API-Key` header (required) — Secret API key issued per institution. Send it on every request.

## Servers

- `https://api.awardspring.com` (UnitedStates, default)
- `https://api.awardspring.ca` (Canada)

## Request

### Body (application/json)

This endpoint expects a CreateStudentV1Request.

- `first_name` (string, required, nullable) — The student's first name. Required, at most 50 characters. Example: `"Jordan"`.
- `last_name` (string, required, nullable) — The student's last name. Required, at most 50 characters. Example: `"Ellis"`.
- `email` (string, required, nullable) — The student's email, which is also how they sign in. Required, at most 50 characters, and not already used by any account at the institution, ignoring upper and lower case, unless `if_email_in_use` is `add_role`. Example: `"jordan.ellis@school.edu"`.
- `student_id` (string, optional, nullable) — Optional. The student number the institution assigned (for example an SIS or campus id), at most 50 characters, and not already on another account at the institution. Example: `"20260142"`.
- `if_email_in_use` (enum, optional, nullable) — Optional. What to do when an account at the institution already has the email. `error`, the default, returns `400 email_already_exists`. `add_role` does what the Users list's Add user window does: the student role is added to that account instead of making a second one. Any other value returns `400 validation_failed`.
  - Allowed values: `error`, `add_role`

## Response

### 200

OK

- `object` (string, optional, nullable) — Stripe-style resource discriminator. Stable, snake_case string naming the resource type. Serialized first so it reads as the leading field of every V1 resource body.
- `id` (string, optional, nullable) — The stored ID of the student's user record. Use it with `GET /api/v1/students/{id}` and as the `user_id` filter on the applications and awards lists.
- `first_name` (string, optional, nullable)
- `last_name` (string, optional, nullable)
- `email` (string, optional, nullable)
- `student_id` (string, optional, nullable) — The student number the institution assigned (for example an SIS or campus id). Not guaranteed unique — two student accounts can carry the same number — and may be empty when the institution has not recorded one.
- `enrollment_status` (string, optional, nullable) — The student's enrollment standing with the institution: `registered` (an active student account) or `prospective` (added ahead of enrollment, e.g. a recruit).
- `created_date` (integer, optional) — When the student account was created, as UTC epoch seconds.

### 201

Created

- `object` (string, optional, nullable) — Stripe-style resource discriminator. Stable, snake_case string naming the resource type. Serialized first so it reads as the leading field of every V1 resource body.
- `id` (string, optional, nullable) — The stored ID of the student's user record. Use it with `GET /api/v1/students/{id}` and as the `user_id` filter on the applications and awards lists.
- `first_name` (string, optional, nullable)
- `last_name` (string, optional, nullable)
- `email` (string, optional, nullable)
- `student_id` (string, optional, nullable) — The student number the institution assigned (for example an SIS or campus id). Not guaranteed unique — two student accounts can carry the same number — and may be empty when the institution has not recorded one.
- `enrollment_status` (string, optional, nullable) — The student's enrollment standing with the institution: `registered` (an active student account) or `prospective` (added ahead of enrollment, e.g. a recruit).
- `created_date` (integer, optional) — When the student account was created, as UTC epoch seconds.

## Errors

### 400 Bad Request Error

Bad Request

- `error` (V1ErrorBody, optional) — Stripe-shaped error object for the `/api/v1/*` surface. Serialized snake\_case (via V1SnakeCaseNamingStrategy) and always nested under the `error` key of a V1ErrorEnvelope: `{ "error": { "type": "...", "code": "...", "message": "...", "param": null, "doc_url": null, "recovery": "..." } }`

### 403 Forbidden Error

Forbidden

- `error` (V1ErrorBody, optional) — Stripe-shaped error object for the `/api/v1/*` surface. Serialized snake\_case (via V1SnakeCaseNamingStrategy) and always nested under the `error` key of a V1ErrorEnvelope: `{ "error": { "type": "...", "code": "...", "message": "...", "param": null, "doc_url": null, "recovery": "..." } }`

### 409 Conflict Error

Conflict

- `error` (V1ErrorBody, optional) — Stripe-shaped error object for the `/api/v1/*` surface. Serialized snake\_case (via V1SnakeCaseNamingStrategy) and always nested under the `error` key of a V1ErrorEnvelope: `{ "error": { "type": "...", "code": "...", "message": "...", "param": null, "doc_url": null, "recovery": "..." } }`

### 422 Unprocessable Entity Error

Unprocessable Content

- `error` (V1ErrorBody, optional) — Stripe-shaped error object for the `/api/v1/*` surface. Serialized snake\_case (via V1SnakeCaseNamingStrategy) and always nested under the `error` key of a V1ErrorEnvelope: `{ "error": { "type": "...", "code": "...", "message": "...", "param": null, "doc_url": null, "recovery": "..." } }`

### 429 Too Many Requests Error

Rate limit exceeded for this API key. Retry after the interval in the `Retry-After` header.

- `error` (V1ErrorBody, optional) — Stripe-shaped error object for the `/api/v1/*` surface. Serialized snake\_case (via V1SnakeCaseNamingStrategy) and always nested under the `error` key of a V1ErrorEnvelope: `{ "error": { "type": "...", "code": "...", "message": "...", "param": null, "doc_url": null, "recovery": "..." } }`

## Types

### V1ErrorBody

Stripe-shaped error object for the `/api/v1/*` surface. Serialized snake\_case (via V1SnakeCaseNamingStrategy) and always nested under the `error` key of a V1ErrorEnvelope: `{ "error": { "type": "...", "code": "...", "message": "...", "param": null, "doc_url": null, "recovery": "..." } }`

- `type` (string, optional, nullable) — Coarse error category from V1ErrorType (e.g. `invalid_request_error`).
- `code` (string, optional, nullable) — Stable fine-grained machine code from ErrorCode (e.g. `donor_not_found`).
- `message` (string, optional, nullable) — Human-readable summary safe to surface to the caller.
- `param` (string, optional, nullable) — The request parameter the error relates to, when attributable to a single field; otherwise null.
- `doc_url` (string, optional, nullable) — Link to documentation for this error, when one exists; otherwise null.
- `recovery` (string, optional, nullable) — Plain-language guidance on how to recover from this error, suitable for showing to the person who triggered it. Not present on every error.
- `details` (any, optional) — Optional structured payload (e.g. a list of ValidationDetail for validation failures). Omitted from the wire when null so the canonical Stripe shape stays minimal.

## Examples

### Example 1

**Request**

```json
{
  "first_name": "string",
  "last_name": "string",
  "email": "string"
}
```

**Response**

```json
{
  "object": "string",
  "id": "string",
  "first_name": "string",
  "last_name": "string",
  "email": "string",
  "student_id": "string",
  "enrollment_status": "string",
  "created_date": 1
}
```

**SDK Code**

```python
import requests

url = "https://api.awardspring.com/api/v1/students"

payload = {
    "first_name": "string",
    "last_name": "string",
    "email": "string"
}
headers = {
    "X-Spring-API-Key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.awardspring.com/api/v1/students';
const options = {
  method: 'POST',
  headers: {'X-Spring-API-Key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"first_name":"string","last_name":"string","email":"string"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.awardspring.com/api/v1/students"

	payload := strings.NewReader("{\n  \"first_name\": \"string\",\n  \"last_name\": \"string\",\n  \"email\": \"string\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-Spring-API-Key", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.awardspring.com/api/v1/students")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-Spring-API-Key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"first_name\": \"string\",\n  \"last_name\": \"string\",\n  \"email\": \"string\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.awardspring.com/api/v1/students")
  .header("X-Spring-API-Key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"first_name\": \"string\",\n  \"last_name\": \"string\",\n  \"email\": \"string\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.awardspring.com/api/v1/students', [
  'body' => '{
  "first_name": "string",
  "last_name": "string",
  "email": "string"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-Spring-API-Key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.awardspring.com/api/v1/students");
var request = new RestRequest(Method.POST);
request.AddHeader("X-Spring-API-Key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"first_name\": \"string\",\n  \"last_name\": \"string\",\n  \"email\": \"string\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-Spring-API-Key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "first_name": "string",
  "last_name": "string",
  "email": "string"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.awardspring.com/api/v1/students")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Example 2

**Request**

```json
{
  "first_name": "string",
  "last_name": "string",
  "email": "string"
}
```

**Response**

```json
{
  "object": "string",
  "id": "string",
  "first_name": "string",
  "last_name": "string",
  "email": "string",
  "student_id": "string",
  "enrollment_status": "string",
  "created_date": 1
}
```

**SDK Code**

```python
import requests

url = "https://api.awardspring.com/api/v1/students"

payload = {
    "first_name": "string",
    "last_name": "string",
    "email": "string"
}
headers = {
    "X-Spring-API-Key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.awardspring.com/api/v1/students';
const options = {
  method: 'POST',
  headers: {'X-Spring-API-Key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"first_name":"string","last_name":"string","email":"string"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.awardspring.com/api/v1/students"

	payload := strings.NewReader("{\n  \"first_name\": \"string\",\n  \"last_name\": \"string\",\n  \"email\": \"string\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-Spring-API-Key", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.awardspring.com/api/v1/students")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-Spring-API-Key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"first_name\": \"string\",\n  \"last_name\": \"string\",\n  \"email\": \"string\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.awardspring.com/api/v1/students")
  .header("X-Spring-API-Key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"first_name\": \"string\",\n  \"last_name\": \"string\",\n  \"email\": \"string\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.awardspring.com/api/v1/students', [
  'body' => '{
  "first_name": "string",
  "last_name": "string",
  "email": "string"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-Spring-API-Key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.awardspring.com/api/v1/students");
var request = new RestRequest(Method.POST);
request.AddHeader("X-Spring-API-Key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"first_name\": \"string\",\n  \"last_name\": \"string\",\n  \"email\": \"string\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-Spring-API-Key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "first_name": "string",
  "last_name": "string",
  "email": "string"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.awardspring.com/api/v1/students")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```