Developers · API documentation

Errors and limits

Error format, codes, rate limits, pagination and safe retries in the EduPay API.

How the API responds when something goes wrong, how many calls fit in a minute, how to go through large lists and how to repeat a request without duplicating anything.

Error format

Every /api/v1 error comes in application/problem+json (RFC 9457):

{
  "type": "about:blank",
  "title": "Unprocessable Content",
  "status": 422,
  "detail": "Some fields are invalid.",
  "code": "validation_failed",
  "errors": { "address.line": ["The address field is required."] },
  "request_id": "9a6c1f2e-3b4d-4e5f-8a9b-0c1d2e3f4a5b"
}
  • code is stable: it does not change between versions. Decide by it.
  • detail is for people to read, in the language requested in Accept-Language (pt-BR, en or es). The text may change.
  • errors only in validation_failed: messages per field.
  • scopes only in insufficient_scope: the missing permissions.
  • request_id is the same as the X-Request-Id header. Give it to support.

Codes

code Status Meaning
unauthenticated 401 Key missing, wrong, expired or revoked
api_disabled 403 The API is turned off for the school
insufficient_scope 403 The key does not have the route's permission
forbidden 403 Access denied
student_limit_reached 403 The school's plan has reached its student limit
not_found 404 The record does not exist in this school
method_not_allowed 405 HTTP method not accepted at this address
bad_request 400 Malformed request
invalid_cursor 400 Invalid page cursor: restart the list
idempotency_key_required 400 The route requires the Idempotency-Key header
idempotency_key_invalid 400 Idempotency-Key in the wrong format
conflict 409 The request conflicts with the current state of the record
idempotency_in_progress 409 Another request with the same Idempotency-Key is still running
enrollment_conflict 409 The student already has a live enrollment in the course or track (see existing_enrollment_id)
enrollment_not_cancellable 409 The enrollment is already cancelled or completed
installment_not_payable 409 The installment cannot have a second copy (paid, cancelled or waived)
boleto_still_valid 409 The current boleto is still valid and cannot be cancelled at the bank
gateway_not_connected 409 The school has not connected its payment account
student_inactive 409 The student is inactive
student_without_portal_access 409 The student has no e-mail or no portal account
portal_not_configured 409 The school's student portal has no address
portal_link_ambiguous_email 409 Two students at the school use the same e-mail
validation_failed 422 Invalid fields (details in errors)
idempotency_key_reused 422 The Idempotency-Key was already used with different data
rate_limited 429 Rate limit reached
internal_error 500 Internal error at EduPay
gateway_error 502 The bank declined or did not respond
http_error others The request could not be completed

Rate limits

Limit Per minute
Reads (GET) per key 120
Writes (POST, PATCH, DELETE) per key 30
All of the school's keys combined 300

Every response has X-RateLimit-Limit and X-RateLimit-Remaining. Once you go over the limit, the response is 429 with Retry-After (in seconds). Wait that long and repeat.

The Moodle plugin routes have their own limit and do not count here.

Paginated lists

Lists return up to limit items (1 to 200; without it, 50), in creation order:

{
  "data": [ { "id": 101, "...": "..." }, { "id": 102, "...": "..." } ],
  "meta": { "next_cursor": "eyJpZCI6MTAyfQ", "has_more": true }
}

For the next page, repeat the call with cursor= set to meta.next_cursor, keeping the other filters. Stop when has_more is false. The cursor never skips or repeats a record, even if new records come in meanwhile.

To sync only what changed, use updated_since (date and time in ISO 8601).

Retry safely

Writes (POST and PATCH) accept the Idempotency-Key header; creating an enrollment requires it. Send a unique code per request, from 1 to 255 visible characters (a UUID or the order number in your system).

Situation What happens
Same key, same request, within 24 hours The first response comes back, with Idempotent-Replayed: true. Nothing is written again
Same key, different data 422 with code idempotency_key_reused
Same key while the first request is still running 409 with code idempotency_in_progress: wait a few seconds and repeat
The first request returned an error The key is free again: fix the request and repeat it with the same key

The key is valid per school and for 24 hours. "Same request" means the same method, the same address and the same data, in any field order.

When to retry

  • 429: after Retry-After.
  • 500, 502, 503 or timeout: with increasing waits (1 s, 2 s, 4 s...) and the same Idempotency-Key.
  • 400, 401, 403, 404, 409 and 422: repeating the same request will not help. Fix the request, the key or the permission.