If you ship an AI product, your terms of service are part of your security architecture. They are the contractual basis for suspending an account that is scraping outputs to train a competitor, for refusing a use you consider dangerous, for limiting liability when a model is confidently wrong and for telling users what happens to their prompts. Terms that nobody can enforce, that users never validly accepted, or that promise things your systems do not do, are worse than useless: they create liability and give a false sense of control.

This page takes the provider's side: you are writing, versioning and enforcing the terms for your own AI product or API. (Reading the terms of the model vendors you buy from is the mirror-image problem, covered in AI use agreements.) It covers the document set, the clauses that are specific to AI, how to collect acceptance that will hold up, how to map each clause to a technical control, an enforcement ladder in code, and a worked extraction case. This is engineering guidance, not legal advice; your counsel writes the words, and you make sure the systems match them.

The document set and terms as code

A provider usually publishes several documents that work together. The terms of service are the contract: who may use the service, payment, ownership, warranties, liability and termination. The acceptable use policy, often called a usage policy, lists what users may not do with the service and is usually incorporated into the terms by reference so that it can be updated more often. The privacy notice explains personal data handling. Business customers typically get commercial terms and a data processing agreement that override the consumer defaults on points such as training on customer data.

Keep these in one repository as text files with stable clause identifiers, the way you keep code. Every published version gets a content hash and an effective date, and each clause has an ID such as AUP-4.2 that the enforcement systems can cite. That one habit makes everything later in this page possible.

From published terms to enforcement: the provider-side ToS pipelineTerms repositoryToS, AUP, versionedAcceptance ledgerwho agreed to which hashPolicy rulesclause IDs mapped to checksSignup / loginclickwrap gateAPI gatewaylimits, scopesModerationAUP classifiersAbuse analyticsextraction, fraudEnforcementladder + noticeAppealshuman reviewEvidence storedecisions cite clauseuser acceptsEvery enforcement action should name the clause it relies on and the terms version the user accepted.
The provider-side pipeline: versioned terms feed an acceptance ledger and policy rules, enforcement points cite clause IDs, and every action is recorded with evidence and an appeal path.

The clauses that are different for AI

Most of a ToS is standard commercial drafting. The clauses below are the ones that are different for AI, and each one implies a system requirement.

ClauseWhat it says, in substanceWhat engineering must provide
Inputs and outputsWho owns prompts and generated output; output may not be uniquePer-account data separation; no promise of exclusivity in the UI
Data use and trainingWhether inputs are used to improve models, and how to opt outA real opt-out flag honoured by every data pipeline
Accuracy disclaimerOutput may be wrong; users must review before relying on itVisible disclosure in the product, not only in the terms
Acceptable useProhibited content and activities, high-risk usesModeration and abuse detection mapped to AUP clause IDs
Model protectionNo extraction, reverse engineering, or using outputs to build competing modelsRate limits, extraction analytics, account linking
Automated accessOnly documented APIs; no scraping of the consumer appBot detection, API keys, quotas
EligibilityMinimum age, sanctioned regions, business-only tiersAge gate, geo and sanctions screening
Suspension and terminationWhen and how you may restrict access, notice and appealEnforcement ladder, notices, appeal workflow
Changes to termsHow users are told about updates and when they take effectVersioning, notification, re-acceptance

Many large providers' terms restrict using outputs to develop competing models and prohibit attempts to extract model weights or system prompts. Whether such a clause is enforceable against a particular user is a legal question, but it only matters at all if you can detect the behaviour and prove the user agreed to the clause. For the detection side, see model extraction through APIs.

Acceptance that holds up

A contract needs assent. United States courts have repeatedly declined to enforce terms that users were never clearly asked to accept. In Specht v. Netscape (2002) and Nguyen v. Barnes and Noble (2014), terms reachable only through a link that users were not required to view (browsewrap) did not bind them. In Berman v. Freedom Financial Network (2022) the Ninth Circuit held that a sign-up flow must give conspicuous notice of the terms and an unambiguous act that signals assent. The safe pattern is clickwrap: the user takes an explicit action, such as ticking a box or pressing a button labelled with the agreement, next to a clear link to the exact terms.

Then record it. If a dispute arrives in two years, you must be able to show which text this account accepted, when, and through which interface.

import hashlib, json, time

TERMS = {"tos": "tos-2026-09-01.md", "aup": "aup-2026-09-01.md"}

def terms_hash(path):
    with open(path, "rb") as f:
        return hashlib.sha256(f.read()).hexdigest()

def record_acceptance(db, account_id, ui_variant, client_ip):
    row = {
        "account_id": account_id,
        "accepted_at": int(time.time()),
        "documents": {k: {"file": v, "sha256": terms_hash(v)} for k, v in TERMS.items()},
        "ui_variant": ui_variant,        # screenshot of this variant is archived
        "client_ip": client_ip,
        "method": "clickwrap_checkbox",
    }
    db.append("terms_acceptance", json.dumps(row))   # append-only table
    return row

def require_current_terms(db, account_id, material_versions):
    last = db.latest("terms_acceptance", account_id)
    if last is None:
        return "block: no acceptance on record"
    accepted = {k: d["sha256"] for k, d in json.loads(last)["documents"].items()}
    stale = [k for k, h in material_versions.items() if accepted.get(k) != h]
    return "ok" if not stale else "reaccept: " + ",".join(stale)

Archive a screenshot or rendered copy of each sign-up variant, because the court will ask what the user saw. Treat the acceptance table as an append-only audit log with the same protection as billing data. Store hashes of only the material versions in material_versions; typo fixes should not force every user through a new click.

Mapping every clause to a control

A clause that no system enforces is a promise you cannot keep, and a control that no clause authorises is a dispute waiting to happen. Maintain a mapping from clause IDs to controls and review it whenever either side changes. Each control emits events tagged with the clause ID, so an enforcement record can say which rule was broken. The moderation layer, described in LLM abuse detection, should classify against your AUP categories, not a generic taxonomy, so that a flag translates directly into a clause.

Run the mapping in both directions as a check in CI. Every clause marked enforceable must have at least one control with an owner and a test; every control that can restrict an account must cite at least one clause. Data-use clauses deserve particular care: if the terms say opted-out customers' data is not used for training, the training data loader must filter on that flag, and you should be able to show the filter in code and the counts it removed.

An enforcement ladder

Enforcement should be graduated, consistent and explainable. A ladder lets you respond proportionately, and recording the clause and evidence at each step protects both the user and you.

LADDER = ["warn", "throttle", "restrict_feature", "suspend", "terminate"]
SEVERE = {"AUP-1.1", "AUP-1.2"}   # e.g. child safety, credible violence: skip the ladder

def next_action(history, clause_id, confidence):
    if clause_id in SEVERE and confidence >= 0.9:
        return "suspend", "severe clause; human review within 24h"
    if confidence < 0.6:
        return "none", "log only; below action threshold"
    prior = [h for h in history if h["clause"] == clause_id and h["age_days"] < 90]
    step = min(len(prior), len(LADDER) - 1)
    action = LADDER[step]
    if action in {"suspend", "terminate"}:
        return action, "requires human approval before execution"
    return action, "automatic"

def enforcement_record(account_id, clause_id, action, evidence_ids, terms_sha):
    return {"account": account_id, "clause": clause_id, "action": action,
            "evidence": evidence_ids, "terms_version": terms_sha,
            "notice_sent": True, "appeal_url": "/appeals/new"}

Thresholds and the severe list are policy decisions; the point of the code is that they are written down, reviewed and applied the same way to every account. Notify users of actions with the clause cited and an appeal route, unless doing so would tip off a serious abuser, and give appeals to people who did not make the original decision.

Worked example: output harvesting for distillation

An API customer on a self-serve plan sends 1.4 million requests in a week, compared with a median of a few thousand. The prompts are templated, cover a systematic grid of topics, ask for long step-by-step answers and arrive through three accounts that share a payment card and an IP range. The pattern matches the distillation signals your extraction analytics watch for.

The response follows the pipeline. Analytics raise an event tagged TOS-7.3, the clause prohibiting use of outputs to develop competing models, with confidence 0.8. The acceptance ledger shows all three accounts accepted the version containing that clause through the clickwrap flow, with archived screenshots. There is no prior history, so the ladder's first step applies, but because the accounts are linked the team escalates to throttling all three and sends a notice citing the clause and asking about the use case. Usage continues through a fourth account; a reviewer approves suspension. Every step is recorded with evidence IDs and the terms hash, so if the customer disputes the action, the file already exists.

Changing terms and regulatory floors

Terms change: new features, new laws, new abuse patterns. Treat a change like a release. Diff the old and new text in review, mark which clauses are material, tell users in advance in the way your current terms promise, and require re-acceptance for material changes through the require_current_terms gate. Some changes cannot be imposed retroactively on data already collected under older promises, so record which terms version governed each dataset.

Regulation sets floors that terms cannot contract out of. Consumer protection law in many places limits unfair terms and disclaimers. GDPR requires a lawful basis for processing personal data, which a clause in the terms does not supply by itself. The EU Digital Services Act requires services in scope to explain content moderation restrictions and procedures in their terms. The EU AI Act adds transparency duties, such as telling people they are interacting with an AI system; check current application dates with counsel. Your internal policy for employees is a separate document, covered in employee AI usage policies.

Failure modes

  • Browsewrap acceptance. A footer link and no explicit action; the terms may not bind anyone.
  • Promises the pipeline breaks. The terms say opted-out data is not trained on, but a new data export ignores the flag.
  • Unversioned terms. You cannot show which text a user accepted, so the clause you rely on may not apply to them.
  • Clause-free enforcement. Accounts suspended by a classifier with no cited rule, no notice and no appeal, inviting disputes and regulatory complaints.
  • Copied terms. A competitor's or vendor's text pasted in, promising features or protections you do not have.
  • Over-broad disclaimers. Clauses that consumer law will not enforce, giving false comfort.

Trade-offs

Strict, detailed AUPs give enforcement clear footing but raise false-positive costs and deter legitimate users; vague ones are flexible but hard to apply consistently. Frequent re-acceptance keeps the record clean but annoys users, so reserve it for material changes. Automatic enforcement scales but must stay below the threshold where wrong decisions harm paying customers; human review is accurate but slow and expensive. Publishing detailed abuse signals helps honest users comply and helps abusers evade, so describe prohibited behaviour, not detection methods.

What to do next

  1. Move your ToS and AUP into a versioned repository with clause IDs, content hashes and effective dates.
  2. Replace any browsewrap flow with clickwrap and start an append-only acceptance ledger with archived screenshots of each sign-up variant.
  3. Build the clause-to-control map and a CI check that every enforceable clause has a control and every restricting control cites a clause.
  4. Verify each data-use promise in code: find the filter that honours the opt-out and record its counts.
  5. Implement a written enforcement ladder with notices, human approval for suspension and an appeal path.
  6. Treat terms changes as releases with legal review, notice, and re-acceptance for material changes.
Key takeaway: Your AI product's terms of service are a security control only if users validly accepted them, your systems actually do what they promise, and every enforcement action cites a clause. Keep terms versioned with clause IDs, collect clickwrap acceptance in an append-only ledger, map clauses to controls in both directions, enforce through a graduated ladder with notice and appeal, and ship changes like releases. Get the wording from counsel.