EduSeat Public API

The EduSeat Public API lets your university's student information system (SIS) or admissions portal integrate directly with EduSeat's attendance platform. Sync student rosters, register courses, manage enrollments, and pull attendance and barring data programmatically — no manual CSV uploads required.

API Key Auth
One key per university, issued by an EduSeat Super Admin.
Bulk Endpoints
Sync thousands of students, courses, and enrollments in one call.
100 req/min
A generous per-key rate limit for scheduled sync jobs.

Authentication

Every university is issued a single long-lived api_key by an EduSeat Super Admin (Super Admin panel → API Keys). Exchange it for a short-lived bearer token by calling POST /auth, then send that token as Authorization: Bearer <token> on every subsequent request.

1. Exchange your API key for a token
curl -X POST https://www.eduseat.ng/api/v1/auth \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "eduseat_live_xxxxxxxxxxxxxxxxxxxx"
  }'
2. Use the token on every request
curl https://www.eduseat.ng/api/v1/students?matric=CSC/2021/001 \
  -H "Authorization: Bearer eyJ1aWQiOiI...<token>"

Tokens expire after 24 hours (returned as expires_in in seconds). There is no refresh endpoint — simply call POST /auth again with your api_key to get a new one. Treat both your api_key and issued tokens as secrets — never expose them in client-side code.

Base URL

All endpoints below are relative to this base URL. Every response — success or error — is JSON.

https://www.eduseat.ng/api/v1

Endpoints

POST/auth

Exchange your university's api_key for a bearer token. This is the only endpoint that does not require a token.

Request
{
  "api_key": "eduseat_live_xxxxxxxxxxxxxxxxxxxx"
}
Response
// 200 OK
{
  "success": true,
  "data": {
    "token": "eyJ1aWQiOiI...<token>",
    "expires_in": 86400,
    "university_id": "2e1e478e-23ba-4f50-8e73-298311511ff4",
    "university_name": "University of Example"
  }
}
Error Response
// 401 Unauthorized
{
  "success": false,
  "error": "Invalid API key",
  "code": "INVALID_API_KEY"
}
POST/studentsRequires Bearer token

Create a new student, or update an existing one (matched by matric_number). Creating a student provisions a real EduSeat login for them.

Request
{
  "matric_number": "CSC/2021/001",
  "full_name": "Jane Doe",
  "email": "jane.doe@university.edu.ng",
  "level": "300",
  "department": "Computer Science"
}
Response
// 201 Created
{
  "success": true,
  "data": {
    "action": "created",
    "student": {
      "id": "fd096a8e-3d39-4efc-840c-163c8d8356b2",
      "matric_number": "CSC/2021/001",
      "full_name": "Jane Doe",
      "email": "jane.doe@university.edu.ng",
      "level": "300",
      "department": "Computer Science"
    }
  }
}

email is optional — omit it and EduSeat auto-generates a @eduseat.ng address from the matric number. If you provide an institutional email, EduSeat sends the student a one-time setup link. Calling this again with the same matric_number updates the record instead ("action": "updated", HTTP 200) and never touches login credentials. There is also a matching GET /students?matric= to fetch a student plus their attendance summary.

POST/students/bulkRequires Bearer token

Create or update up to thousands of students in one call, processed in internal batches of 50.

Request
{
  "students": [
    { "matric_number": "CSC/2021/001", "full_name": "Jane Doe",   "level": "300", "department": "Computer Science" },
    { "matric_number": "CSC/2021/002", "full_name": "John Smith", "level": "300", "department": "Computer Science" }
  ]
}
Response
// 200 OK
{
  "success": true,
  "data": {
    "created": 2,
    "updated": 0,
    "failed": 0,
    "errors": []
  }
}

A failure on one row (e.g. a duplicate email) never fails the whole batch — check the errors array, keyed by matric_number, for anything that needs attention.

POST/courses/bulkRequires Bearer token

Create or update courses in bulk, matched by course_code. There is also a single-course POST /courses and GET /courses?code=.

Request
{
  "courses": [
    {
      "course_code": "CSC301",
      "course_name": "Operating Systems",
      "level": "300",
      "department": "Computer Science",
      "faculty": "Science",
      "semester": "first"
    }
  ]
}
Response
// 200 OK
{
  "success": true,
  "data": {
    "created": 1,
    "updated": 0,
    "failed": 0,
    "errors": []
  }
}

Updating an existing course only overwrites the fields you send — omitted fields keep their current value.

POST/enrollments/bulkRequires Bearer token

Enroll students into courses in bulk, referencing both sides by their public identifiers.

Request
{
  "enrollments": [
    { "matric_number": "CSC/2021/001", "course_code": "CSC301" },
    { "matric_number": "CSC/2021/002", "course_code": "CSC301" }
  ]
}
Response
// 200 OK
{
  "success": true,
  "data": {
    "enrolled": 2,
    "skipped": 0,
    "failed": 0,
    "errors": []
  }
}

Already-enrolled pairs count toward skipped, not failed — this is safe to call repeatedly. Single enroll/unenroll is available at POST/DELETE /enrollments with the same { matric_number, course_code } body.

GET/attendance?matric=CSC/2021/001Requires Bearer token

Get a student's full attendance record — a per-course summary plus every individual check-in.

Response
// 200 OK
{
  "success": true,
  "data": {
    "matric_number": "CSC/2021/001",
    "courses": [
      {
        "course_id": "1f4dfdb9-44da-46df-9fc9-ca95c88515b4",
        "course_code": "CSC301",
        "course_name": "Operating Systems",
        "faculty": "Science",
        "total_sessions": 12,
        "attended_sessions": 10,
        "attendance_percentage": 83.33
      }
    ],
    "records": [
      { "id": "...", "session_id": "...", "course_id": "...", "checked_in_at": "2026-03-04T09:02:11Z", "status": "present" }
    ]
  }
}

Swap the query for ?course=CSC301 to instead get every enrolled student's attendance for one course.

GET/attendance/barring-list?threshold=75Requires Bearer token

List every student below a given attendance percentage, across all their courses — the source of truth for exam-barring decisions. Uses the attendance_summary view.

Response
// 200 OK
{
  "success": true,
  "data": {
    "threshold": 75,
    "count": 2,
    "students": [
      { "matric_number": "CSC/2021/014", "full_name": "Ade Bello",  "level": "300", "course_code": "CSC301", "attendance_percentage": 62.5 },
      { "matric_number": "CSC/2021/029", "full_name": "Amaka Obi",  "level": "300", "course_code": "CSC305", "attendance_percentage": 70 }
    ]
  }
}

threshold defaults to 75 if omitted. One row is returned per student per course they're below threshold in.

POST/webhooksRequires Bearer token

Register a webhook URL to receive push notifications instead of polling. There is also a GET /webhooks to list your university's registered webhooks.

Request
{
  "url": "https://portal.university.edu.ng/webhooks/eduseat",
  "events": ["attendance.recorded", "session.ended", "student.status_changed"]
}
Response
// 201 Created
{
  "success": true,
  "data": {
    "id": "9c2f...",
    "url": "https://portal.university.edu.ng/webhooks/eduseat",
    "events": ["attendance.recorded", "session.ended", "student.status_changed"],
    "is_active": true,
    "created_at": "2026-09-07T08:00:00Z",
    "secret": "5f2b9e...   (shown once — save it)"
  }
}

secret is returned only at creation time — store it to verify the X-EduSeat-Signature header (sha256=<hmac>, HMAC-SHA256 of the raw JSON body) on delivered payloads. Every delivery also carries X-EduSeat-Event and a unique X-EduSeat-Delivery id; a failed delivery (non-2xx, timeout, or network error) is retried once. Valid events: attendance.recorded, barring.updated, session.started, session.ended, student.status_changed, student.synced.

Error Codes

Every error response has the shape below. Check code programmatically — the error string is for humans and may change wording over time.

{
  "success": false,
  "error": "Human-readable message",
  "code": "MACHINE_READABLE_CODE"
}
CodeHTTP StatusMeaning
UNAUTHORIZED401Missing or malformed Authorization header — expected "Bearer <token>".
INVALID_TOKEN401The bearer token is malformed or its signature does not match.
TOKEN_EXPIRED401The bearer token has expired. Call POST /auth again with your api_key.
INVALID_API_KEY401The api_key was not recognized, or has been revoked.
INVALID_INPUT400The request body is missing required fields or is malformed.
NOT_FOUND404The requested student or course does not exist.
STUDENT_NOT_FOUND404No student matches the given matric_number.
COURSE_NOT_FOUND404No course matches the given course_code.
DUPLICATE_EMAIL409That email is already registered to a different account.
DUPLICATE_STUDENT409A student with this matric_number already exists.
AUTH_CREATE_FAILED500Couldn't create the student's login account. Retry, or contact support@eduseat.ng if it persists.
DB_ERROR500A database error occurred while processing the request.
RATE_LIMITED429You exceeded 100 requests per minute for this API key. Back off and retry.
SERVER_MISCONFIGURED500The server is missing required configuration. Contact support@eduseat.ng.
INTERNAL_ERROR500An unexpected error occurred.

Rate Limits

Each API key is limited to 100 requests per minute. The window is a fixed 60-second bucket per key, not a sliding average. Exceeding it returns:

// 429 Too Many Requests
{
  "success": false,
  "error": "Rate limit exceeded: max 100 requests per minute",
  "code": "RATE_LIMITED"
}

Prefer the bulk endpoints (/students/bulk, /courses/bulk, /enrollments/bulk) over looping single-record calls — one bulk call moves hundreds of records without touching the limit. On a 429, back off and retry after a few seconds rather than retrying immediately.