This guide shows how to keep students and enrollments the same in both systems, without duplicating anyone. The full fields of each route are in the Reference.
The catalog: the IDs an enrollment uses
To enroll a student, you need the ID of the course (or track), the cohort and the payment plan. They come from the catalog (catalog:read):
| Route | What it returns |
|---|---|
GET /offerings |
Courses (offerings), with price and status |
GET /trails |
Tracks (several courses sold together) |
GET /cohorts |
Cohorts, with their status (only an open cohort accepts enrollments) |
GET /financial-plans |
Payment plans: accepted methods, installments, due date and enrollment fee |
GET /sales-pages |
Published sales pages, with the displayed price |
Store these IDs in the integration's configuration. They rarely change.
External code: your ID inside EduPay
Students and enrollments have the external_id field, with the record's code in your system (up to 100 characters, unique within the school). Save it when you create the record and use it to find the record later:
curl "https://edupay.estudiosite.com/api/v1/students?external_id=ALU-0091" \
-H "Authorization: Bearer $EDUPAY_KEY"
The school can also fill in the code in the panel, on the student and enrollment screens.
Sync with updated_since
To fetch only what changed since last time, store the start time of each sync and pass it in the next one:
curl "https://edupay.estudiosite.com/api/v1/students?updated_since=2026-10-29T12:00:00Z&limit=200" \
-H "Authorization: Bearer $EDUPAY_KEY"
Go through the pages with cursor until meta.has_more is false. It works for students, enrollments, installments and the catalog. To know right away, without polling, use Webhooks.
Students
Read (students:read): GET /students accepts email (case-insensitive), document_number (with or without punctuation), external_id and status. GET /students/{id} returns the full record, with the address in address.
Create (students:write): POST /students. The student is created with a student portal account and gets the welcome e-mail with a temporary password. Send "send_welcome_email": false to skip it.
curl -X POST https://edupay.estudiosite.com/api/v1/students \
-H "Authorization: Bearer $EDUPAY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4b0c8f2e-6a7d-4f1e-9c3b-1d2e3f4a5b6c" \
-d '{
"external_id": "ALU-0091",
"full_name": "Maria Souza",
"email": "maria@exemplo.com",
"document_number": "52998224725",
"phone": "11999990000",
"address": { "zip_code": "01310-100", "line": "Av. Paulista", "number": "1000",
"neighborhood": "Bela Vista", "city": "São Paulo", "state": "SP", "country": "BR" }
}'
The same rules as registration in the panel apply, and they vary from school to school:
- CPF (Brazilian individual taxpayer ID), phone and address may be required; the e-mail is required when the student signs in to the portal with their e-mail;
- the school can turn on CPF verification;
- CPF and
external_idcannot repeat within the school. The CPF of a deleted student is also rejected: the school restores the old record in the panel; - the school's plan may have a student limit (
403withcodestudent_limit_reached).
Field errors come back in errors, with the API names (address.line, not the column name).
Update: PATCH /students/{id} changes only what you send. If the student did not have a portal account yet and the login data now exists, the account is created and the welcome e-mail goes out — the same as editing in the panel.
Open the portal already signed in (portal:link): POST /students/{id}/portal-link returns a url that opens the student portal without a password. It is valid for 5 minutes and only once: create it at the moment of the click, never leave it saved on a page. The student must be active and have an e-mail and a portal account. On first access, the portal asks them to set a new password.
Enrollments
Read (enrollments:read): GET /enrollments accepts student_id, external_id, offering_id, trail_id, cohort_id and status. With include=student,installments,contract, the related data comes along (each one with the permission of its own area). GET /enrollments/{id}/contract returns the contract status and GET /enrollments/{id}/contract/pdf downloads the PDF.
Create (enrollments:write): POST /enrollments, always with Idempotency-Key. The student comes from student_id or is created in the same request in student (same fields as student creation). The product comes from offering_id or trail_id.
curl -X POST "https://edupay.estudiosite.com/api/v1/enrollments?include=installments" \
-H "Authorization: Bearer $EDUPAY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: PED-2026-1187" \
-d '{
"external_id": "PED-2026-1187",
"student_id": 532,
"offering_id": 14,
"cohort_id": 3,
"financial_plan_id": 2,
"payment_method": "pix"
}'
The enrollment follows exactly the same path as an enrollment made in the panel:
- it generates the installments from the payment plan and, if there is a template, the contract for signature;
- if the cohort is linked to a Moodle cohort, it adds the student to Moodle right away;
- it issues the charges: the card link goes out right away; boleto and PIX go out when the due date enters the school's issuing window (a few days before), as in the panel;
- it notifies the student by e-mail.
Details:
- Amounts in cents. Without
base_value, the course price applies (or the sum of the track's courses). Withoutenrollment_fee, the plan's fee applies. - The payment method must be accepted by the plan and by the school's payment account. If it is not,
422onpayment_method. - Course, track and plan must be active, and the cohort must be open.
- Duplicate enrollment: if the student already has a live enrollment (awaiting contract, awaiting payment or active) in the same course or track, the response is
409withcodeenrollment_conflictand the ID of the existing one inexisting_enrollment_id. - The enrollment is recorded under the name of whoever created the key in the panel.
Cancel: POST /enrollments/{id}/cancel, with an optional reason (student_withdrawal, transfer, default or administrative). Open installments are cancelled along with their charges, and the student is notified and removed from Moodle. The termination fee is still only available in the panel. A boleto that is still valid cannot be cancelled at the bank and stays payable until it expires: meta.charges_still_payable says how many.
Enrollment statuses
status |
Meaning |
|---|---|
pending_contract |
Waiting for the student to sign the contract |
pending_payment |
Waiting for the first payment |
active |
Active |
cancelled |
Cancelled |
completed |
Completed (all installments settled) |
Repeat without duplicating
The network drops, a timeout hits, the system restarts halfway through. That is what the Idempotency-Key header is for: send a unique code per request (a UUID or the order number in your system). Repeating the same request with the same key within 24 hours returns the first response, with the Idempotent-Replayed: true header, and nothing is written again. Details in Errors and limits.