Show where AI tutor answers end up in student work
For learning platforms, schools and education teams that offer an AI tutor. The tutor labels every answer it gives. When a submission contains copied tutor text, a check finds the paragraphs and the tutor session, so a teacher can look at the actual conversation instead of relying on a guess.
Endpoints: POST /api/watermark/encode or /tokens, /decode
· MCP tools: watermark_encode, watermark_decode
· Cost: 1 credit per tutor answer; checks free unless a label is found
The problem
Students use AI tutors, and that is often the point. Teachers still need to see when tutor answers are handed in as a student's own work. AI detectors estimate whether text "looks" machine-written; they produce false accusations, especially against students writing in a second language. A label is different: it is a fact about your tutor's output, added when the answer was generated.
How it works
- The tutor labels each answer with an opaque session id, for example
ai;tutor;s=s-20931, throughPOST /api/watermark/encode. Tutors that stream their answers use/tokens, as shown in Label AI-generated text. - Submissions are checked paragraph by paragraph with
POST /api/watermark/decode. Paragraphs without a label cost nothing. - A teacher opens the session for each labelled paragraph and sees the question the student asked and the answer the tutor gave.
Use it fairly
- Tell students that tutor answers carry an invisible label. Transparency is fair, and it changes behaviour on its own.
- Treat a found label as the start of a conversation, not as a penalty. Quoting the tutor with attribution can be perfectly acceptable.
- Never treat a missing label as proof that a student worked alone.
- Keep names out of labels and follow your data-protection rules for minors.
Add it to your workflow
The examples read your API token from TEXTWATERMARKING_API_TOKEN. Each example below is run
against the API before this page is published.
Label tutor answers (Python)
If labelling fails, the student still gets the answer; log the failure in your own system.
tutor_label.py
# Label each answer your AI tutor gives (Python 3.10+, pip install requests). 1 credit per answer.
# The label is an opaque session id: anyone with an API token can read it, so never put a student name in it.
import os
import requests
API = "https://textwatermarking.com/api/watermark"
HEADERS = {"Authorization": f"Bearer {os.environ['TEXTWATERMARKING_API_TOKEN']}"}
def label_answer(answer: str, session_id: str) -> str:
"""Return the answer with an invisible 'ai;tutor;s=<session>' label, or unchanged if labelling fails."""
try:
r = requests.post(f"{API}/encode", headers=HEADERS, timeout=10,
json={"text": answer, "secret": f"ai;tutor;s={session_id}"})
r.raise_for_status()
return r.json()["encoded"]
except requests.RequestException:
return answer # never block the student; log the failure in your own system
if __name__ == "__main__":
print(label_answer("Photosynthesis turns light energy into chemical energy stored in glucose.", "s-20931"))Check a submission (Python)
Run it when work is handed in, or on demand from the grading view. It also finds tutor text pasted in the middle of a paragraph.
check_submission.py
# Find paragraphs in a submission that carry your tutor's label (Python 3.10+, pip install requests).
# Checking is free for paragraphs without a label; 1 credit for each labelled paragraph found.
import os
import sys
import requests
API = "https://textwatermarking.com/api/watermark"
HEADERS = {"Authorization": f"Bearer {os.environ['TEXTWATERMARKING_API_TOKEN']}"}
def labelled_paragraphs(submission: str) -> list[tuple[int, str]]:
"""[(paragraph number, session id), ...] for paragraphs that contain a tutor answer."""
found = []
paragraphs = [p for p in submission.split("\n\n") if p.strip()]
for number, paragraph in enumerate(paragraphs, start=1):
r = requests.post(f"{API}/decode", json={"text": paragraph}, headers=HEADERS, timeout=10)
if r.status_code == 422: # a mark that is not a plain label: not from your tutor
continue
r.raise_for_status()
label = r.json()["decoded"]
if label.startswith("ai;tutor;s="):
found.append((number, label.removeprefix("ai;tutor;s=")))
return found
if __name__ == "__main__":
# Usage: python check_submission.py < essay.txt
for number, session in labelled_paragraphs(sys.stdin.read()):
print(f"paragraph {number}: tutor session {session}")
Output such as paragraph 2: tutor session s-20931 points the teacher to the conversation. No
output means no labelled paragraph was found.
What it costs
| Item | Value |
|---|---|
| Label one tutor answer | 1 credit |
| Check one submission | free for paragraphs without a label; 1 credit per labelled paragraph found |
| Example volume | 300 students × 20 answers a month = 6,000 credits (Pro: 25,000 a month) |
| Rate limit | 600 calls per 15 minutes per API token; contact us for a school-wide rollout |
What it does not do
- It does not detect other AI tools. Only your tutor's labelled answers are found.
- It does not survive rewording. A student who rephrases the answer, retypes it or runs it through another model removes the label.
- Some editors and file conversions drop the label. Test with the tools your students use, including the format submissions are uploaded in.
- An excerpt that leaves out the first words of an answer has no label. The label sits at the start of each answer.
Frequently asked questions
Is this an AI detector?
If no label is found, did the student write it alone?
Does the label contain personal data?
Do word processors keep the label?
What does it cost?
Related
- Label AI-generated text: streaming, failure handling and the general limits
- API endpoints:
/encode,/tokensand/decode - Quickstart: account, token and first call in five minutes