Developer docs

Feedback Flows API

Mark work, check integrity, sync a roster, and pull results back into your LMS or MIS. Every call uses the same key; sandbox keys return mocks and never charge credits.

Base URL https://feedbackflows.org/api/v1 · Header X-API-Key

Getting started

  1. Get a key (see below). Store it as a secret; it is shown once.
  2. Send it on every request: X-API-Key: YOUR_KEY.
  3. Call GET /account/status to confirm the key and remaining credits.
  4. Call a cheap analysis endpoint, or list students, then move on to jobs.

How to get a key

  • Site admin still issues keys at /admin/api-keys (the existing flow). Use this for non-enterprise integrators and onboarding sandbox keys.
  • Enterprise Owners and Managers with the Integrations feature enabled can issue, scope, rotate and revoke keys for their own organisation on the console Integrations tab. Site admin still sees every key.
  • Keys are hashed at rest. Copy the secret immediately; we cannot show it again.
  • Sandbox keys skip billing and return canned responses for AI and job endpoints, so you can wire an integration without spending credits.

Scopes on a key: marking, integrity, analytics, media, admin. The admin scope implies the others. A missing scope returns 403 with SCOPE_FORBIDDEN.

First call

cURL

curl -X GET 'https://feedbackflows.org/api/v1/account/status' \
  -H 'X-API-Key: YOUR_API_KEY'

Python

import requests

r = requests.get(
    "https://feedbackflows.org/api/v1/account/status",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(r.status_code, r.json())

Credits, limits, and idempotency

  • Usage is billed in credits against the account that owns the key. credits_remaining of -1 means unlimited.
  • Synchronous routes: 60 requests per minute per key. Job submissions: 10 per hour. A 429 includes Retry-After. Rejected calls are not counted.
  • Async job POSTs accept an Idempotency-Key header. Retry the same key for 24 hours and you get the original 202 instead of a second charge.
  • JSON responses include a request_id you can quote to support.

Try it

Paste a key and send a real request from this page. Live keys spend credits; use a sandbox key while you are wiring things up. The key is kept in sessionStorage in this browser only.

In-page console
Requests go to this site’s /api/v1. No proxy.
GET
Idle

Response

{ }

cURL for this request


        

Dedicated testers

Standalone pages with forms for ZIP uploads and longer workflows. Each page can be downloaded and run against production or a local server.

API v2 testers: style comparison · cost estimate · exports · AI tutor jobs · worksheet jobs · reference-check jobs · interactive-feedback jobs

Marking

Interactive feedback

Paste work and a list of criteria.

Test live Download

Smart essay marker

Synchronous essay marking against criteria.

Test live Download

AI tutor

Formative draft feedback with a persona.

Test live Download

Worksheet

Generate a practice sheet from weaknesses or history.

Test live Download

Integrity

AI detector and references

Hybrid detector, legacy detector, and bibliography check.

Test live Download

Batch style jobs

ZIP of submissions compared against writing samples.

Test live Download

Jobs

Rubric marking jobs

Class ZIP against a saved rubric, plus status.

Test live Download

Automarking jobs

Question paper plus student ZIP.

Test live Download

Exam and media jobs

Single-script exam ZIP or video/audio file, then poll.

Test live Download

Learner packs

Queue a pack from selected marking results.

Test live Download

LMS / data

Account status

Key check, credits, /test, admin stats.

Test live Download

LMS sync

Students, classes, enrolment, rubrics, class results.

Test live Download

Student analytics

AI history and progress report.

Test live Download

Class assignment analysis

Cohort write-up from existing feedback.

Test live Download

Endpoint reference

Paths below are relative to https://feedbackflows.org/api/v1. Field names match the live handlers.

Account

GET /test

Confirms the key is valid. No scope required.

GET /account/status

Returns business_name, api_key_status, and credits_remaining.

{
  "business_name": "Northbridge College",
  "api_key_status": "active",
  "credits_remaining": 985
}

POST /jobs/estimate-cost

Body: job_type (batch_style, rubric_marking, automarking, media, exam, learner_pack, smart_essay, ai_detector, worksheet, or reference_check), optional file_count / student_count.

Marking

POST /interactive-feedback · scope marking

{
  "student_text": "The industrial revolution was a turning point...",
  "marking_criteria": ["Clarity of argument", "Use of evidence"]
}

POST /smart-essay-marker · scope marking

Synchronous. Prefer POST /smart-essay/jobs for long scripts.

{
  "essay_text": "The full essay...",
  "marking_criteria": ["Clarity of argument", "Use of evidence"]
}

POST /ai-tutor · scope marking

Body: student_draft, optional mode (general or rubric), marking_rubric when mode is rubric, persona (encouraging or direct).

POST /students/{student_id}/worksheet · scope marking

Body: optional weaknesses_list, format (short_answer, …), level, num_questions. If weaknesses are omitted, history is used.

Integrity

POST /ai-detector · scope integrity

Body: document_text, optional academic_mode. Prefer POST /ai-detector/jobs for long documents.

POST /reference-check (legacy) · scope integrity

Simplified detector. New integrations should use /ai-detector or the job.

POST /full-reference-check · scope integrity

Body: document_text, style (APA, Harvard, MLA, Chicago), optional enable_semantic_check.

POST /style-comparison · scope integrity

Body: student_id, submission_text. The student must already have writing samples on file.

Jobs

All create endpoints return 202 with job_id and a status path. Optional callback_url (HTTPS) and Idempotency-Key header apply unless noted. ZIP bodies use zip_base64.

POST /batch-style/jobs · GET /batch-style/jobs/{job_id}

POST /rubric-marking/jobs · GET /rubric-marking/jobs/{job_id} · POST /rubric-marking/jobs/{job_id}/cancel

Create body includes rubric_id, zip_base64, optional filename, marking_style, grading_system.

POST /automarking/jobs · GET /automarking/jobs/{job_id}

Body: questions (each with question_text, available_marks, mark_scheme) or a saved template, plus zip_base64.

POST /exam-marker/jobs

Body: student_id, zip_base64. Status is polled on GET /automarking/jobs/{job_id}.

POST /media-marker/jobs · GET /media-marker/jobs/{job_id} · scope media

Body: criteria, media_base64, optional filename, education_level, persona, presentation_type.

POST /smart-essay/jobs · GET /smart-essay/jobs/{job_id}

Body: essay_text, marking_criteria, optional education_level, persona.

POST /ai-detector/jobs · GET /ai-detector/jobs/{job_id} · scope integrity or marking

Body: document_text, optional academic_mode.

POST /learner-packs/jobs · GET /learner-packs/jobs/{job_id}

{
  "student_id": 41,
  "template_id": 3,
  "selected_result_refs": [
    {"kind": "auto", "id": 120},
    {"kind": "rubric", "id": 88}
  ],
  "callback_url": "https://example.edu/hooks/feedback-flows"
}

POST /ai-tutor/jobs · GET /ai-tutor/jobs/{job_id}

POST /worksheets/jobs · GET /worksheets/jobs/{job_id}

POST /reference-check/jobs · GET /reference-check/jobs/{job_id}

POST /interactive-feedback/jobs · GET /interactive-feedback/jobs/{job_id}

Exports

/rubric-marking/jobs/{id}/export.csv · /rubric-marking/results/{id}/export.pdf

/automarking/jobs/{id}/export.csv · /automarking/results/{id}/export.pdf

/smart-essay/jobs/{id}/export.pdf · /learner-packs/jobs/{id}/export.pdf

/media-marker/jobs/{id}/export.pdf · /worksheets/jobs/{id}/export.pdf

/reference-check/jobs/{id}/export.pdf · /classes/{id}/export.csv

Enterprise results awaiting required human review return 403 REVIEW_REQUIRED.

Illustrative credit costs

Defaults: style comparison 1/file; rubric and automarking 10/student; media 20; exam 5/student; learner pack 3; smart essay 10; AI detector 1; worksheet 3; reference check 5. Server configuration may override these values.

Roster and results

GET / POST /students · PATCH /students/{student_id}

Create: name, optional uln, class_ids. Duplicate names return 409. Org seat limits return 403.

GET / POST /classes · PATCH /classes/{class_id}

Create: name, optional subject. Patch may set concluded.

PATCH /classes/{class_id}/students

{
  "add_student_ids": [41, 42],
  "remove_student_ids": [17]
}

GET /classes/{class_id}/results · scope analytics

Paginated rubric and automarking outcomes for every student in the class.

GET / POST /rubrics · GET /rubrics/{rubric_id}

Create: name, criteria (string or list of strings), optional marking_guidance, pass_rate.

Also: GET /classes/{class_id}/assignments, GET /question-templates, GET /students/{id}/ai-history, POST /students/{id}/progress-report, POST /classes/{id}/assignments/{id}/analyze (analytics scope).

List endpoints accept ?page=, ?per_page=, and ?search=.

Webhooks

Pass callback_url when you create a job. When the job finishes we POST JSON to that URL.

  • Header X-FeedbackFlows-Signature in the form t=<unix>,v1=<hex>.
  • HMAC-SHA256 over <timestamp>.<raw-body> using the key’s webhook secret.
  • Reject deliveries whose timestamp is more than five minutes old. That is what stops a captured request being replayed.
  • Header X-FeedbackFlows-Delivery is a UUID; use it to ignore retries.
  • Callback URLs must be public HTTPS. We re-resolve DNS at delivery time and refuse private or loopback addresses.
import hashlib, hmac, time

def verify(header, raw_body, secret, tolerance=300):
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    timestamp = int(parts["t"])
    if abs(time.time() - timestamp) > tolerance:
        return False
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(parts["v1"], expected)

Errors

CodeMeaning
200Success.
201Resource created (student, class, rubric).
202Job queued.
400Missing or invalid fields.
401Missing, invalid, or expired key.
402Not enough credits.
403Scope missing, demo lock, or student limit.
404Resource not found.
409Conflict (duplicate student, concluded class).
413Payload too large.
429Rate limited; see Retry-After.
500Unexpected server error. Quote request_id.

Error bodies are JSON: error, code, status, request_id. They are never HTML.