curl --request POST \
--url https://api.example.com/v1/invites \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"role": "<string>",
"email": "<string>",
"tutor_id": "<string>",
"classroom_id": "<string>",
"group_id": "<string>",
"student_id": "<string>",
"subject_id": "<string>"
}
'import requests
url = "https://api.example.com/v1/invites"
payload = {
"role": "<string>",
"email": "<string>",
"tutor_id": "<string>",
"classroom_id": "<string>",
"group_id": "<string>",
"student_id": "<string>",
"subject_id": "<string>"
}
headers = {
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
role: '<string>',
email: '<string>',
tutor_id: '<string>',
classroom_id: '<string>',
group_id: '<string>',
student_id: '<string>',
subject_id: '<string>'
})
};
fetch('https://api.example.com/v1/invites', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/invites",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'role' => '<string>',
'email' => '<string>',
'tutor_id' => '<string>',
'classroom_id' => '<string>',
'group_id' => '<string>',
'student_id' => '<string>',
'subject_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/invites"
payload := strings.NewReader("{\n \"role\": \"<string>\",\n \"email\": \"<string>\",\n \"tutor_id\": \"<string>\",\n \"classroom_id\": \"<string>\",\n \"group_id\": \"<string>\",\n \"student_id\": \"<string>\",\n \"subject_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/invites")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"role\": \"<string>\",\n \"email\": \"<string>\",\n \"tutor_id\": \"<string>\",\n \"classroom_id\": \"<string>\",\n \"group_id\": \"<string>\",\n \"student_id\": \"<string>\",\n \"subject_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/invites")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"role\": \"<string>\",\n \"email\": \"<string>\",\n \"tutor_id\": \"<string>\",\n \"classroom_id\": \"<string>\",\n \"group_id\": \"<string>\",\n \"student_id\": \"<string>\",\n \"subject_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"scope": "<string>",
"id": "<string>",
"role": "<string>",
"target_id": "<string>",
"email": "<string>",
"token": "<string>",
"join_url": "<string>",
"expires_at": "<string>",
"subject_id": "<string>",
"emailed": false,
"created_at": "<string>"
}{
"error": {
"code": "not_found",
"message": "Session not found",
"status": 404,
"details": {}
}
}{
"error": {
"code": "not_found",
"message": "Session not found",
"status": 404,
"details": {}
}
}{
"error": {
"code": "not_found",
"message": "Session not found",
"status": 404,
"details": {}
}
}{
"error": {
"code": "not_found",
"message": "Session not found",
"status": 404,
"details": {}
}
}Create Invite
Mint a join link or code for a chosen scope, org-scoped to the API key’s org.
Scopes: organization (recruit a tutor/admin by email, or get a reusable org
join link), tutor_student (1-on-1), classroom, group, parent. Supply
email to invite a specific person (creates an invite row; EquateIt emails them
where the platform already does); omit email for a reusable shareable
link/code. Cross-org targets return 404. Honours Idempotency-Key.
curl --request POST \
--url https://api.example.com/v1/invites \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"role": "<string>",
"email": "<string>",
"tutor_id": "<string>",
"classroom_id": "<string>",
"group_id": "<string>",
"student_id": "<string>",
"subject_id": "<string>"
}
'import requests
url = "https://api.example.com/v1/invites"
payload = {
"role": "<string>",
"email": "<string>",
"tutor_id": "<string>",
"classroom_id": "<string>",
"group_id": "<string>",
"student_id": "<string>",
"subject_id": "<string>"
}
headers = {
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
role: '<string>',
email: '<string>',
tutor_id: '<string>',
classroom_id: '<string>',
group_id: '<string>',
student_id: '<string>',
subject_id: '<string>'
})
};
fetch('https://api.example.com/v1/invites', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/invites",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'role' => '<string>',
'email' => '<string>',
'tutor_id' => '<string>',
'classroom_id' => '<string>',
'group_id' => '<string>',
'student_id' => '<string>',
'subject_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/invites"
payload := strings.NewReader("{\n \"role\": \"<string>\",\n \"email\": \"<string>\",\n \"tutor_id\": \"<string>\",\n \"classroom_id\": \"<string>\",\n \"group_id\": \"<string>\",\n \"student_id\": \"<string>\",\n \"subject_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/invites")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"role\": \"<string>\",\n \"email\": \"<string>\",\n \"tutor_id\": \"<string>\",\n \"classroom_id\": \"<string>\",\n \"group_id\": \"<string>\",\n \"student_id\": \"<string>\",\n \"subject_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/invites")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"role\": \"<string>\",\n \"email\": \"<string>\",\n \"tutor_id\": \"<string>\",\n \"classroom_id\": \"<string>\",\n \"group_id\": \"<string>\",\n \"student_id\": \"<string>\",\n \"subject_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"scope": "<string>",
"id": "<string>",
"role": "<string>",
"target_id": "<string>",
"email": "<string>",
"token": "<string>",
"join_url": "<string>",
"expires_at": "<string>",
"subject_id": "<string>",
"emailed": false,
"created_at": "<string>"
}{
"error": {
"code": "not_found",
"message": "Session not found",
"status": 404,
"details": {}
}
}{
"error": {
"code": "not_found",
"message": "Session not found",
"status": 404,
"details": {}
}
}{
"error": {
"code": "not_found",
"message": "Session not found",
"status": 404,
"details": {}
}
}{
"error": {
"code": "not_found",
"message": "Session not found",
"status": 404,
"details": {}
}
}Authorizations
Org API key as Token token=ei_live_...
Headers
Body
POST /v1/invites → mint a join link or code for a chosen scope.
Delivery is decided by email + scope (there is no send_email flag): supply
email to invite a specific person — this creates an invite row and ClassQuill
queues them an email where the platform already does (organization always, via
a DB trigger; tutor_student when an email is given). Omit email to get a
reusable shareable link/code. Per-scope required/irrelevant fields are validated
server-side (422). organization_id, tokens, and timestamps are always
server-set; the invite RPCs are never called (they are auth.uid()-gated).
emailed: true in the response means the invite email was QUEUED, not that it
was delivered — queueing happens synchronously in this request, but the actual
send is async (a 1-minute-cadence cron drains the queue via Resend). Treat
join_url as the reliable fallback: surface it to the caller/inviter rather
than assuming the email will arrive.
organization, classroom, group, tutor_student, parent organization only: 'tutor' (default) | 'admin' | 'student'.
Required for the organization email path; optional elsewhere.
Required for scope=tutor_student.
Required for scope=classroom.
Required for scope=group.
Required for scope=parent (the child).
Optional, scope=tutor_student only — echoed back + audited.
Response
Successful Response
A minted invite. token carries the invite token (organization/tutor_student)
or the code (parent / org-join-code scopes). join_url is null for parent (the
code is entered manually).
emailed is true when ClassQuill queued an email for this invite (organization,
or tutor_student with an email) — it does NOT confirm delivery. The actual send
is async: a background job drains the queue and calls Resend, which can still
fail, bounce, or land in spam after this response returns. Callers should
surface join_url as a fallback rather than relying on the email arriving.
Was this page helpful?