Skip to content

REST API ​

Lumtera's admin screens, editor panels and review mode run on these routes. Everything the screens do is open to your own code.

  • Free routes live under lumtera/v1.
  • Lumtera Pro's routes live under lumtera-pro/v1.

Authentication ​

Sign in as you would for any WordPress REST route:

  • In wp-admin or on the site: the login cookie plus a REST nonce in the X-WP-Nonce header. wp.apiFetch adds it for you.
  • From outside WordPress: an Application Password, sent with HTTP Basic authentication.

The one exception is POST /feedback. It is public, because visitors are not signed in.

When a permission check fails, WordPress answers with its usual rest_forbidden error: 401 when signed out, 403 when signed in.

Plain permalinks

If your site uses plain permalinks, use the ?rest_route= form. For example: https://example.com/?rest_route=/lumtera/v1/site-summary. It works under every permalink setting.

Capabilities used on this page ​

Words used belowWhat it means
the report capabilityThe capability from the lumtera_capability filter. Default lumtera_view_reports.
the review capabilityThe capability from the lumtera_review_capability filter. Default lumtera_review_mode.
the dismiss-errors capabilityThe capability from the lumtera_dismiss_errors_capability filter. Default lumtera_dismiss_errors.
the summary capabilitylumtera_view_summary. The Lumtera Reporter role and administrators have it.
the manage-reports capability (Pro)lumtera_manage_reports: Manage client reports. Administrators always have it. Needed to create, delete and share client reports and to save the conformance report.

You give these to roles under Lumtera → Settings → Permissions. See Roles & permissions.

Free routes: lumtera/v1 ​

RouteMethodsPermissionPurpose
/checkPOSTedit_post on post_id, or edit_postsCheck unsaved content
/posts/{id}/issuesGETedit_post, or read-only for report viewersStored results for a post
/posts/{id}/scanPOSTedit_postCheck a saved post and store the result
/posts/{id}/dismissPOSTedit_post (+ dismiss-errors for errors)Dismiss or restore an issue
/bulkPOSTreport capabilityNext batch of Check all content
/page-resultsPOSTreview capability + nonceCheck a whole page from review mode
/page-results/dismissPOSTreview capability + nonceDismiss or restore a whole-page finding
/page-results/preferencePOSTreview capability + nonceThe person's Save results to reports choice
/posts/{id}/manualGET, POSTedit_postChecklist results
/posts/{id}/manual/batchPOSTedit_postSave several checklist results
/changes/proposePOSTedit_post on post_idDraft a fix for one issue
/changes/propose-partPOSTCan edit that partDraft a fix at the source (a shared part)
/changesGETedit_post on post_idA post's changes
/changes/{id}/applyPOSTCan edit the change's page or partApprove and apply a change
/changes/{id}/revertPOSTCan edit the change's page or partUndo an applied change
/changes/{id}/rejectPOSTCan edit the change's page or partReject a proposed change
/changes/{id}/rescanPOSTCan edit the change's partCheck the pages that show a fixed part again
/posts/{id}/ai/link-textPOSTFeature on + edit_postAI suggestions for vague link text
/posts/{id}/ai/headingsPOSTFeature on + edit_postAI suggestions for headings
/posts/{id}/ai/summaryPOSTFeature on + edit_postAI plain-language summary
/images/{id}POST, PUT, PATCHedit_post on the imageSave an image's alt text
/images/{id}/applyPOSTedit_post on the imageCopy alt text into posts
/images/{id}/suggestPOSTFeature on + edit_post on the imageAI alt text suggestions
/feedbackPOSTPublic, rate-limitedThe visitor feedback form
/site-summaryGETsummary or report capabilitySite-wide numbers for an agency hub
/prompts/{prompt}POSTmanage_options for review; report capability for hint-…Answer the review request or hide a Pro tip

Checks and scans ​

POST /check ​

Checks content without storing anything. The editor sidebar calls it as you type.

ArgumentTypeDefaultNotes
post_idint0The post being edited. Used for permissions, the reading level's language and dismissals.
contentstring''Block markup or HTML, up to 512 KB (lumtera_max_check_bytes)
block_idsstring[][]Editor block client IDs, in order, so issues map to blocks. Up to 5,000, each up to 64 characters.
elementorstring''Unsaved Elementor data (JSON), up to the same size as content
limitint0Findings listed per check, 0 to 10,000. 0 lists them all. Counts and score always cover every finding. With a limit, the response also has tally, more ([ { "rule", "title", "count" } ]: findings left out per check) and limit.

Permission: edit_post on post_id when it is given. Otherwise edit_posts.

Rate limit: 120 checks per user per minute (lumtera_check_rate_limit; 0 turns it off). Over the limit returns 429 lumtera_rate_limited. Whole-page checks and the lumtera/check-content ability count against the same limit.

With elementor and a post_id, Lumtera renders and checks the unsaved Elementor elements instead of content. That needs Elementor's own edit permission (403 lumtera_forbidden) and valid JSON (400 lumtera_bad_elements).

Response:

json
{
  "issues": [ { "rule": "link-no-name", "title": "Link has no text", "category": "links",
                "wcag": "2.4.4", "wcag_name": "Link Purpose (In Context)",
                "wcag_url": "https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html",
                "criteria": [ { "id": "2.4.4", "name": "Link Purpose (In Context)", "url": "…", "level": "A" },
                              { "id": "4.1.2", "name": "Name, Role, Value", "url": "…", "level": "A" } ],
                "docs_url": "…", "level": "A", "how_to_fix": "…", "rationale": "…",
                "id": "<fingerprint>", "severity": "error", "confidence": "certain",
                "message": "…", "context": "<a …>", "block": "<client id>", "element": "",
                "origin": null, "reopened": null } ],
  "dismissed": [],
  "counts": { "error": 1, "warning": 0, "notice": 0 },
  "possible": 0,
  "score": 85,
  "rules_run": 88,
  "readability": { "grade": 7.2, "words": 412, "sentences": 31, "target": 9, "lang": "en",
                   "formula": "flesch-kincaid", "formula_name": "Flesch–Kincaid grade level",
                   "score": 7.2, "scale": "grade", "label": "Easy to read" },
  "reading_note": "",
  "can_dismiss_errors": true
}
  • id is the issue's fingerprint: an MD5 of the check ID and the issue's markup. A repeat of the same markup in one post adds its occurrence number, so each copy has its own fingerprint. Dismissals, fixes, Pro tasks and Pro ignore rules are keyed on it.
  • criteria lists every WCAG criterion the check maps to. wcag is the main one.
  • confidence is certain, likely or possible. possible counts how many issues are only possible ones.
  • rules_run is the number of checks switched on. It is 88 when every check is on.
  • origin is null for the post's own content. For markup from a shared part (a template part, synced pattern, menu, widget and so on) it is { "type", "id", "key", "owner", "label", "type_label", "edit_url" }. See Site parts.
  • reopened is null, or explains why an issue someone dismissed shows again: { "reason": "changed" | "severity", "time", "dismissed_by", "note", "text" }.
  • readability is null when it isn't measured: for an unsupported language, or text that's too short. reading_note then says why. formula depends on the post's language (Polylang and WPML are read). scale tells you how to read score.
  • dismissed lists hidden issues with the same fields, plus dismissed_by, note, time, source and can_restore. source is post for a dismissal on this post. For an issue ignored site-wide in Pro, source is global, note is the reason, and restore_path and expires are added.
  • can_dismiss_errors is false when there's no post_id.
  • Posts built with a page builder also get builder: { "name", "edit_url", "live" }. live is true for Elementor, whose editor checks unsaved changes as you work.
  • With limit set, tally gives every finding's count by confidence and severity, as { "sure": { "error", "warning", "notice" }, "possible": { … } }, so a list that shows only some findings can still filter by both.

GET /posts/{id}/issues ​

The stored issues for a post, from its last check. Nothing is checked again.

Permission: edit_post on the post. People with the report capability may also read a published post of a checked content type that they can read. They get the results read-only.

Response:

json
{
  "issues": [ … ],
  "summary": { "errors": 0, "warnings": 0, "notices": 0, "dismissed": 0, "score": 100,
               "grade": null, "scanned_at": 1790419821, "version": "1.2.1" },
  "read_only": false
}

Issues have the same fields as in /check, with block empty. summary is null if the post hasn't been checked. read_only is true for someone who may see the reports but not edit the post.

Both GET /posts/{id}/issues and POST /posts/{id}/scan accept limit (int, default 0), as /check does. With a limit, at most that many findings are listed per check, and the response adds tally, more and limit. summary always covers every finding.

POST /posts/{id}/scan ​

Checks the saved post and stores the result. Returns the same shape as /check.

Permission: edit_post on the post.

Errors: 404 lumtera_not_found, 400 lumtera_not_scanned (Lumtera doesn't check this content type).

POST /posts/{id}/dismiss ​

Dismisses an issue, or restores it.

ArgumentTypeRequiredNotes
fingerprintstringYes32 lowercase hex characters
rulestringYesThe check ID
severityerror, warning or noticeYes
notestringUp to 1,000 characters
contentstringUnsaved editor content, to find issues not saved yet
elementorstringUnsaved Elementor data
restorebooltrue to restore instead of dismiss

Permission: edit_post on the post. Dismissing or restoring an error also needs the dismiss-errors capability.

The server finds the issue itself and uses its real check and severity, whatever the request says. It looks in the stored results, then the saved post, then content or elementor. A dismissal never hides the same markup later reported as more serious.

Returns { "ok": true }, then checks the post again and stores the result.

Errors: 404 lumtera_not_dismissed (restoring an issue that isn't dismissed), 400 lumtera_unknown_issue, 403 lumtera_forbidden.

POST /bulk ​

Checks the next batch of content, for Check all content. Each call checks up to 50 posts, and stops early after about 2 seconds.

ArgumentTypeDefaultNotes
afterint0The cursor from the previous response
only_unscannedboolfalseOnly posts that have never been checked

Permission: the report capability, and the lumtera_can_check_all filter (default true). Lumtera Pro's read-only client role is turned away through that filter.

Response: { "scanned", "after", "remaining", "done", "skipped", "totals" }. Call again with the returned after until done is true. skipped lists post IDs passed over because their last check crashed PHP and they haven't changed since.

Each call fires lumtera_bulk_batch_done. The last batch of a run that started at after: 0 also fires lumtera_bulk_scan_done. See Hooks & filters.

Whole-page results ​

Review mode's Whole page tab uses these routes. See Review mode.

Permission for all three: signed in, the review capability, and a valid nonce from the review panel (action lumtera_page_results). A bad or expired nonce returns 403 lumtera_bad_nonce. These routes are meant for the review panel, not for scripts.

POST /page-results ​

Checks a page snapshot from the browser. The content checks run on the snapshot, and the browser's whole-page findings are cleaned and added. The answer gives every finding's source and fingerprint.

ArgumentTypeRequiredNotes
urlstringYesA page on this site, up to 2,048 characters
noncestringYesThe review panel's nonce
post_idintThe post the page shows. Default 0.
htmlstringThe page snapshot, up to 3 MB (lumtera_max_page_bytes)
findingsobject[]The audit engine's findings, up to 500
widthintThe browser window's width, 0 to 20,000
savebooltrue to store the result for the reports. Default false.

Extra permission: when the page shows a post, edit_post on that post. An address that shows a different post than post_id returns 400 lumtera_url.

The result is stored only when all of these are true:

  • save is true.
  • Let people save review-mode findings (theme, menus, footer) to the reports is on under Lumtera → Settings → General.
  • The person ticked Save results to reports in the panel (see /page-results/preference).
  • The page shows a post the person can edit, or the person has the report capability. A page that shows no post, such as an archive, belongs to everyone.

It shares the 120-per-minute limit of /check (429 lumtera_rate_limited).

Response: { "url", "counts", "items", "measured", "dismissed", "stored", "saving": { "enabled", "opted" } }.

  • items are content findings. measured are the browser's whole-page findings, each with its index in findings and dismissed. dismissed lists hidden findings with their dismissal.
  • Each finding has fingerprint, kind, rule, title, severity, confidence, message, context, how_to_fix, docs_url, wcag, wcag_url, wcag_name, level, selector, source, scope and can_dismiss: { "site", "url" }.

Errors: 400 lumtera_url (not a page on this site), 403 lumtera_forbidden.

POST /page-results/dismiss ​

Dismisses or restores one whole-page finding.

ArgumentTypeRequiredNotes
urlstringYesThe page
noncestringYesThe review panel's nonce
fingerprintstringYes32 lowercase hex characters
scopesite or urlDefault site. site hides the finding on every page. Only findings from template-level parts (such as a header) can use site. Others are always dismissed on this page only.
notestringUp to 1,000 characters
restorebooltrue to restore

Extra permission, per finding:

  • site scope needs the report capability.
  • url scope needs edit_post on the post the page shows. A page that shows no post needs the report capability.
  • An error also needs the dismiss-errors capability.

The finding and its severity come from this person's last check of the page (kept for an hour) or the stored results, never from the request.

Returns { "ok": true, "scope": "site" }, or { "ok": true } after a restore.

Errors: 400 lumtera_unknown_issue, 404 lumtera_not_dismissed, 403 lumtera_forbidden, 400 lumtera_url.

POST /page-results/preference ​

Remembers whether this person saves results to reports. It is off until they tick the box.

ArgumentTypeRequired
noncestringYes
saveboolYes

Returns { "save": true, "enabled": true }. enabled is the site setting.

Manual checklists ​

Results of the guided checklists for a post. Each item is one WCAG criterion that needs a person, such as 2.4.7.

Permission for both routes: edit_post, and the post must be of a content type Lumtera checks.

/posts/{id}/manual ​

GET returns the current results, what the page seems to contain, and which items seem not to apply. POST records or clears one result.

ArgumentTypeRequiredNotes
teststringYesA criterion ID from the checklist, such as 2.4.7. The older test names (keyboard, zoom-reflow, text-spacing, screen-reader, forms, media, motion-timing, hover-focus, orientation) are still accepted and saved to their criteria.
resultstringYespass, fail, na, or '' to clear the result
notestringUp to 2,000 characters
failedstring[]With an older test name and fail: the criteria that failed. The others are saved as passed. Empty means all failed. Up to 10.

Response of GET:

json
{
  "results": {
    "2.4.7": { "result": "fail", "note": "Focus is lost in the menu.", "time": 1790000000,
               "userName": "Maya", "date": "September 25, 2026" }
  },
  "summary": { "total": 54, "done": 1, "passed": 0, "failed": 1, "na": 0 },
  "label": "Manual checks: 1 of 54 done, 1 failed",
  "features": {
    "found": { "hasForm": true, "hasVideo": false, "hasAudio": false, "hasCarousel": false, "…": false },
    "source": "content",
    "date": "",
    "suggested": [ "1.2.1", "1.2.2", "2.2.1" ]
  }
}

POST returns the same without features. suggested lists items that seem not to apply to this page.

Error: 400 lumtera_manual_invalid. Saving fires lumtera_manual_test_saved. See Hooks & filters.

POST /posts/{id}/manual/batch ​

Saves several checklist results at once. The checklist's Mark as not applicable action uses it.

ArgumentTypeRequiredNotes
resultsobject[]Yes1 to 100 items of { "test", "result", "note" }. test must be a criterion ID (older test names are not accepted here).

Returns the same shape as POST /posts/{id}/manual. Error: 400 lumtera_manual_invalid.

Fixes and changes ​

The single-issue fix flow: propose a fix, review the before and after, apply it, and undo it if needed. It is free and has no limits. See Fixing issues.

Proposing never changes a page. Applying saves a revision first when the post type keeps revisions. Lumtera Pro's approval policy can narrow who may apply, through the lumtera_change_allowed filter.

A change looks like:

json
{
  "id": 12, "post_id": 42, "post_title": "Order bread online",
  "rule": "image-missing-alt", "rule_title": "Image has no alternative text",
  "fingerprint": "…", "status": "proposed", "origin": "rule", "origin_detail": "…",
  "summary": "…", "where": "…", "before": "<img …>", "after": "<img alt=\"…\" …>",
  "value": "…", "value_kind": "alt", "value_label": "Alt text", "value_help": "…", "value_max": 150,
  "value_source": "rule", "from_library": false, "alternatives": [], "note": "",
  "ai_available": true, "revisions": true, "verify": "", "verify_detail": null,
  "compare_url": "", "edit_url": "…", "target": null
}
  • status is proposed, approved, applied, rejected, stale, failed or reverted.
  • target is null for a change to the post itself. For a change to a shared part it holds type, key, label, type_label, menu_item, creates and notice, plus rescan progress.

POST /changes/propose ​

Drafts a fix for one issue on a post.

ArgumentTypeRequiredNotes
post_idintYesThe post
fingerprintstringYesThe issue's fingerprint
valuestringYour own text (alt text, link text and so on), up to 1,000 characters
source'', rule or airule uses Lumtera's built-in fixer. ai asks the connected AI provider for a draft (needs the matching feature on under Lumtera → Settings → AI suggestions).
replacesintThe ID of an earlier proposal this one replaces
rule, blockstringFrom the block editor: the check ID and the block's position, such as 0/2

Permission: edit_post on post_id. It shares the 120-per-minute limit of /check (429 lumtera_rate_limited).

Returns the proposed change.

POST /changes/propose-part ​

Drafts one fix in the shared part an issue comes from, such as a header template part or a menu. The fix then reaches every page that shows the part.

ArgumentTypeRequiredNotes
part_typestringYescontent, synced_pattern, template_part, template, navigation, menu, widget, theme, plugin or unknown
part_keystringThe part's key, up to 191 characters
rulestringYesThe check ID
fingerprintstringYesThe issue's fingerprint
valuestringYour own text, up to 1,000 characters
source'', rule or aiAs above

Permission: the route needs edit_posts or edit_theme_options. Then the part itself is checked: edit_post on a synced pattern, edit_theme_options for everything else. It shares the /check rate limit.

Errors include: 422 lumtera_no_fix (no automatic fix for this check, or a page-structure check), 422 lumtera_part_unsupported, 403 lumtera_forbidden.

GET /changes ​

Up to 50 changes for a post.

ArgumentTypeRequired
post_idintYes

Permission: edit_post on post_id. Returns an array of changes.

POST /changes/{id}/apply, /revert, /reject, /rescan ​

RouteWhat it does
/changes/{id}/applyApproves a proposed change (the person applying it is the approver) and applies it. Takes an optional value (up to 1,000 characters) to edit the text first.
/changes/{id}/revertUndoes an applied change
/changes/{id}/rejectRejects a proposed change
/changes/{id}/rescanFor a fix in a shared part: checks the next few pages that show the part again. Returns { "id", "total", "done", "failed", "more", "before", "now", "finished" }. Call again until finished is true.

Permission: whoever may edit the change's page (edit_post) or its shared part (see above). The lumtera_change_allowed filter can refuse approve, reject, apply and revert.

Each returns the change. Errors carry the change in data.change where there is one.

Common errors: 404 lumtera_change_not_found, 409 lumtera_change_state (the change isn't in a state for that action), 409 lumtera_stale (the page changed since the fix was drafted), 403 lumtera_forbidden or lumtera_filtered, 500 lumtera_save_failed.

AI writing suggestions ​

Three routes ask the AI provider connected to WordPress for writing suggestions. Each needs its switch on under Lumtera → Settings → AI suggestions (settings keys link_text, headings and summary) and WordPress's AI Client. See AI suggestions.

Permission: the feature is switched on, and edit_post on the post. The post must exist.

Rate limit: 30 AI requests per user in 10 minutes, shared by all AI features, alt text and AI fix drafts included (lumtera_ai_rate_limit). Over the limit returns 429 lumtera_ai_rate_limited.

All three accept title (string): the post title as typed. When it's empty, the saved title is used.

Suggestions are drafts. Nothing is saved. The editor shows them, and the author chooses whether to use one.

POST /posts/{id}/ai/link-text ​

ArgumentTypeRequiredNotes
textstringYesThe current link text
hrefstringThe link's address, up to 2,048 characters
htmlstringThe paragraph around the link
rulestringThe finding: link-ambiguous-text (default), link-url-as-text or link-same-text-different-url

Response: { "suggestions": [ "…" ], "note": "…" }, with up to three suggestions of at most 90 characters. Suggestions that repeat the current text, or would still fail the link text checks, are removed.

POST /posts/{id}/ai/headings ​

ArgumentTypeRequiredNotes
modestringYesbold (turn a bold paragraph into a heading) or outline (suggest where subheadings go)
textstringFor boldThe bold paragraph
nextstringbold: the text that follows it
previousstringbold: the heading before it
paragraphsstring[]For outlineThe post's paragraphs, in order. Up to 80 are used. At least 3 must have text.

Response for bold: { "suggestions": [ "…" ], "note": "…" }, with up to two suggestions.

Response for outline: { "headings": [ { "index": 3, "text": "…" } ], "note": "…" }, with up to four headings. index is the zero-based position in paragraphs that the heading goes before. Headings are at most 80 characters.

POST /posts/{id}/ai/summary ​

ArgumentTypeRequiredNotes
contentstringYesThe post content (HTML or block markup), up to 512 KB. It needs at least 100 words.

Response: { "summary": "…", "words": 96, "note": "…" }. The summary is at most 120 words.

Errors for all three: 400 lumtera_ai_no_input (nothing to work with), 501 lumtera_ai_unavailable or lumtera_ai_no_model, 502 lumtera_ai_failed or lumtera_ai_bad_answer. Each route has a lumtera_ai_{feature}_pre filter to answer without the AI Client. See Hooks & filters.

Images and alt text ​

POST /images/{id} ​

Saves an image's alt text in the Media Library. Also accepts PUT and PATCH.

Permission: edit_post on the image. id must be an image.

ArgumentTypeRequiredNotes
altstringYes
source'' or aiai records that the text began as an AI suggestion, and who accepted it

Response: { "id", "alt", "status": "missing|review|ok", "judged", "used", "empty": [ { "id", "title", "editable" } ], "manual": [ … ] }. empty lists posts that show the image without alt text that Lumtera can fill. manual lists posts where the alt text has to be added by hand.

POST /images/{id}/apply ​

Adds the image's alt text to posts that show it without any. Existing alt text is never replaced.

Permission: edit_post on the image. Each post is skipped unless you can edit it.

Parameters: posts (optional), an array of post IDs: add the alt text only to these. It is matched against the posts that show this image without alt text, so it can never change any other post. Leave it out to update all of them. The alt text manager sends it when someone unticks a post, for example one where the image is decorative.

Response: { "updated": [ … ], "skipped": 0, "skipped_reasons": { "permission": 0, "filtered": 0 } } plus the image state. Error: 400 lumtera_no_alt if the image has no alt text yet.

POST /images/{id}/suggest ​

Asks the connected AI provider for alt text suggestions.

Permission: AI alt text suggestions are switched on, and edit_post on the image. See AI suggestions.

ArgumentTypeNotes
post_idintOptional. The post to take context from. It's ignored if you can't read it.

Response: { "suggestions": [ "…" ], "decorative": false, "note": "…" }, with up to three suggestions.

It shares the AI rate limit above. Errors: 429 lumtera_ai_rate_limited, 400 lumtera_ai_no_file (the file can't be read or is over 4 MB), 501 lumtera_ai_unavailable or lumtera_ai_no_model, 502 lumtera_ai_failed or lumtera_ai_bad_answer.

Visitor feedback ​

POST /feedback ​

Receives the accessibility feedback form when JavaScript is on. Without JavaScript the same form posts to admin-post.php and gets the same checks. See Accessibility feedback.

Permission: public. Visitors are not signed in, and the form carries no nonce, so it works in cached pages.

ArgumentTypeRequiredNotes
messagestringYesUp to 5,000 characters
typestringYesbarrier, alternative or other
page_urlstringThe page the problem is on, a web address up to 2,000 characters
reply_formatstringemail (default), large_print, easy_read, audio, braille or other
namestringUp to 100 characters
emailstringA valid email address
consentboolWith name or emailPermission to store the name and email
lumtera_tsstringYesThe signed time stamp the form prints
lumtera_hpstringThe hidden honeypot field. Leave it empty.

Every field is sanitised. Nothing sent is shown as HTML.

Spam and abuse checks, in order:

  1. Honeypot. If lumtera_hp has text, the answer is the usual thank-you and nothing is stored.
  2. Rate limit. Each IP address may send 5 messages per hour (lumtera_feedback_rate_limit; 0 turns it off). IPv6 addresses are counted per /64 network. Only a salted hash of the address is kept. Behind a proxy, return the real visitor address from lumtera_feedback_client_ip. Without JavaScript, the form also allows 30 posts per hour from one address, sent or not.
  3. Fields. Missing or invalid fields return invalid with their errors.
  4. Time stamp. lumtera_ts must be signed by the site and at least 3 seconds old. Otherwise the status is too_fast.
  5. Spam filter. Akismet runs only if the site owner ticked Check messages with Akismet under Lumtera → Settings → Feedback. The lumtera_feedback_is_spam filter has the last word. Spam gets the usual thank-you and is not stored.

Responses:

StatusHTTPBody
Sent201{ "status": "sent", "reference": "…", "message": "…" }
Invalid fields200{ "status": "invalid", "errors": { "message": "…", "email": "…" } }
Too fast200{ "status": "too_fast", "errors": { "form": "…" } }
Over the limit200{ "status": "rate_limited", "errors": { "form": "…" } }

Refusals are answered with HTTP 200 on purpose, so a visitor's browser shows no error. Read status, not the HTTP code. Nothing is stored in any of these cases.

errors is keyed by field name, or form for the whole form. The messages are written to be shown to the visitor.

Site summary ​

GET /site-summary ​

A summary of the site for an agency hub, such as the Lumtera Pro client portfolio. The hub reads it with an Application Password. No page content is included.

Permission: the summary capability or the report capability.

Response (summary version 2):

json
{
  "name": "Crumb & Co. Bakery",
  "url": "https://example.com",
  "admin_url": "https://example.com/wp-admin/admin.php?page=lumtera",
  "version": "1.2.1",
  "totals": { "content": 18, "scanned": 18, "average": 92, "errors": 8, "warnings": 12,
              "notices": 8, "failing": 4, "unscanned": 0, "passing": 14 },
  "coverage": { "total": 55, "automated": 44, "automated_full": 1, "automated_partial": 43,
                "needs_person": 11, "with_evidence": 35, "complete": false,
                "label": "35 of 55 WCAG 2.2 A/AA criteria have evidence",
                "sentence": "…" },
  "rules": [ { "title": "Form field has no label", "wcag": "4.1.2", "severity": "error", "issues": 3, "posts": 1 } ],
  "worst": [ { "title": "Contact", "errors": 4, "url": "https://example.com/wp-admin/post.php?post=42&action=edit" } ],
  "time": 1790559281,
  "summary_version": 2,
  "statement": { "state": "none", "status": "", "reviewed": "", "review_due": "", "overdue": false },
  "feedback": { "open": 0, "overdue": null, "source": "free" },
  "parts": 3,
  "pro_active": false,
  "can_rescan": true,
  "run_diff": null
}
  • version is the Lumtera plugin version. summary_version is the version of this shape. Version 1 had no summary_version field.
  • Version 2 only adds fields: coverage, summary_version, statement, feedback, parts, pro_active, can_rescan and run_diff. Hubs built for version 1 read the rest as before.
  • coverage is shown beside the average score, never merged into it. Covering a criterion means a check looks at it, not that the site meets it.
  • rules lists the five most common checks. worst lists up to five published items. Drafts and private titles stay on the site.
  • statement.state is published, draft or none. review_due is a year after the last review.
  • feedback.open counts new and in-progress messages. Without Pro, overdue is null (unknown, not zero).
  • parts is the number of shared site parts found. See Site parts.
  • can_rescan is true when this connection may start a full check with POST /bulk.

With Lumtera Pro active on the site, Pro adds to it:

  • pro_active: true while the license is active.
  • can_report: the license is active and the user may create a report: manage_options and Manage client reports (lumtera_manage_reports), the same rule POST /reports checks, so a hub only offers a report link the site will accept. Administrators always have both, so connect the hub as an administrator.
  • can_share: can_report, and the plan includes share links (Growth and up).
  • feedback.overdue: the number of overdue messages, and feedback.source becomes pro.
  • run_diff: { "new", "fixed", "persisting", "at", "since", "version_changed" } between the last two full checks, or null.

Add your own fields with the lumtera_site_summary filter. Don't remove or change the type of existing fields. Older hubs rely on them.

Review request and Pro tips ​

POST /prompts/{prompt} ​

Records the current person's answer to the Overview's review request or to a Pro tip. It changes only that person's user meta.

ArgumentTypeNotes
prompt (in the path)stringreview, or hint- plus a tip: hint-review-page, hint-repeated-issue, hint-forms, hint-pdfs, hint-evidence, hint-schedule, hint-alerts
dostringRequired. For review: later (ask again in 30 days), never or reviewed. For a tip: dismiss.

Returns what happens now, in words, for example "Lumtera will ask again in 30 days." An unknown prompt or answer returns 400 lumtera_prompt. The same answers work without JavaScript through admin-post.php?action=lumtera_prompt.

Lumtera Pro routes: lumtera-pro/v1 ​

These routes come with Lumtera Pro. Unless a route says otherwise, its permission check also needs an activated license. An expired license still counts: features keep working, and only updates and support stop. Some need a plan: Growth and up for consistency checks and the client portfolio. Signed-in checks are on every plan, for as many roles as the plan allows. See License.
RouteMethodsPermissionPlan
/tasksGET, POSTedit_post on post_idEvery plan
/tasks/{id}POSTedit_post on the task's postEvery plan
/tasks/{id}/historyGETedit_post on the task's postEvery plan
/tasks/{id}/issuePOSTedit_post on the task's post + a connected tracker + report capabilityEvery plan
/assigneesGETedit_posts (+ edit_post on post_id if given)Every plan
/ignoreGET, POSTmanage_optionsEvery plan
/ignore/{id}DELETEmanage_optionsEvery plan
/page-checksPOSTreport capabilityEvery plan
/page-checks/{id}GET, DELETEreport capabilityEvery plan
/page-checks/scheduleGET, POST, PUT, PATCHreport capabilityEvery plan
/page-checks/schedule/runPOSTreport capabilityEvery plan
/documents/scanPOSTreport capabilityEvery plan
/form-flowsGETreport capabilityEvery plan
/form-flows/armPOST, DELETEPOST: report capability. DELETE: any signed-in user.Every plan (POST)
/form-flows/formsPOSTreport capabilityEvery plan
/form-flows/{id}POST, DELETEreport capabilityEvery plan
/role-scansGET, POSTmanage_optionsEvery plan (roles per plan)
/role-scans/passPOSTmanage_optionsEvery plan (roles per plan)
/sessions/{id}/itemsPOSTreport capabilityEvery plan
/sessions/{id}/envPOSTreport capabilityEvery plan
/sessions/{id}/signoffPOSTreport capabilityEvery plan
/sessions/{id}/reopenPOSTmanage_optionsEvery plan
/consistency/runPOSTreport capabilityGrowth and up
/remediation/changesGETPropose or approve fixesEvery plan
/remediation/changes/{id}POSTPropose or approve fixesEvery plan
/remediation/approvePOSTApprove fixesEvery plan
/remediation/rejectPOSTPropose or approve fixesEvery plan
/remediation/applyPOSTPropose or approve fixes (checked per change)Every plan
/remediation/undoPOSTPropose or approve fixes (checked per change)Every plan
/remediation/previewGETPropose or approve fixesEvery plan
/remediation/proposePOSTPropose or approve fixesEvery plan
/remediation/runPOSTPropose or approve fixesEvery plan
/remediation/run/cancelPOSTPropose or approve fixesEvery plan
/remediation/run/dismissPOSTPropose or approve fixesEvery plan
/portfolio/rescansGETmanage_optionsGrowth and up
/reportsPOSTmanage_options + Manage client reports (lumtera_manage_reports) (license checked in the route)Every plan; share link Growth and up

Fix tracking ​

RouteMethodArgumentsPermission
/tasksGETpost_id (required)edit_post
/tasksPOSTpost_id, fingerprint (required); assignee (default 0); note; content (the block editor's content, to find issues not saved yet)edit_post. Assigning someone else needs edit_others_posts.
/tasks/{id}POSTstatus (open, in_progress, fixed, wont_fix), assignee, noteedit_post on the task's post. wont_fix on an error needs the dismiss-errors capability.
/tasks/{id}/historyGETedit_post on the task's post
/tasks/{id}/issuePOSTedit_post on the task's post, a connected issue tracker, and the report capability
/assigneesGETpost_id (optional)edit_posts, and edit_post on post_id when given

POST /tasks finds the issue on the server by its fingerprint: first in the stored results, then by checking the saved post. rule, severity, message and context are still accepted from older clients, but ignored.

Errors: 404 lumtera_pro_finding, 400 lumtera_pro_assignee (that person can't edit the post), 403 lumtera_pro_assign_others, lumtera_pro_wont_fix.

GET /tasks returns an object keyed by fingerprint. A task looks like:

json
{
  "id": 7, "post_id": 42, "fingerprint": "…", "rule": "link-no-name", "title": "Link has no text",
  "wcag": "2.4.4", "severity": "error", "message": "…", "context": "…",
  "status": "in_progress", "verified": false, "status_label": "In progress",
  "assignee": 3, "assignee_name": "Maya", "updated": 1790000000,
  "post_title": "Order bread online", "edit_url": "…",
  "issue": { "tracker": "github", "tracker_name": "GitHub", "url": "https://github.com/acme/site/issues/12", "key": "#12" }
}

issue is null until an issue is created for the task in a tracker.

POST /tasks/{id}/issue creates the task's issue in the connected tracker (GitHub, GitLab, Jira, Linear, Asana, ClickUp or Trello) and returns the task. A task that already has an issue returns it unchanged. Errors: 404 lumtera_pro_tracker_task, 400 lumtera_pro_tracker_none, 409 lumtera_pro_tracker_busy (being created right now), 429 when the tracker asks to wait, and 502 for other tracker errors.

History entries are { "action", "detail", "user", "time" }. /assignees returns [ { "id", "name" } ]. People with edit_others_posts get everyone who can edit posts (and, with post_id, can edit that post). Everyone else gets only themselves.

Ignore rules ​

Rules that hide a finding on every page.

RouteMethodNotes
/ignoreGETEvery rule, newest first, including expired ones
/ignorePOSTAdds a rule. Returns it with HTTP 201.
/ignore/{id}DELETERemoves a rule. Returns { "deleted": true, "previous": { … } }.

Permission: manage_options and an active license.

ArgumentTypeRequiredNotes
rulestringYesThe check ID. Whole-page check IDs are accepted too.
matchexact or patternDefault exact
valuestringYesUp to 1,000 characters. For exact: the finding's markup, as shown under Markup. For pattern: an optional tag name followed by .class and #id parts, such as a.social-link or .cookie-banner.
reasonstringYesUp to 500 characters. Shown wherever the finding is hidden.
expiresstring or intA date (Y-m-d, end of that day in the site's time zone) or a Unix time. Empty for never.
severitystringerror, warning or notice: the most severe finding the rule may hide. Empty for any.

A rule looks like:

json
{
  "id": "k3x9q2m7b1zc", "rule": "link-ambiguous-text", "match": "pattern", "value": "a.read-more",
  "severity": "", "reason": "Theme archive links; the heading gives context.",
  "user": 1, "time": 1790000000, "expires": 0,
  "check": "Link text is vague", "by": "Maya", "expired": false
}

Errors (400 unless noted): lumtera_pro_ignore_rule, lumtera_pro_ignore_match, lumtera_pro_ignore_value, lumtera_pro_ignore_pattern, lumtera_pro_ignore_reason, lumtera_pro_ignore_expiry, lumtera_pro_ignore_full (500 rules at most), 409 lumtera_pro_ignore_exists, and 404 lumtera_pro_ignore_missing on delete.

Adding or removing a rule checks the affected posts again in the background. It also records issue.ignored_globally or issue.unignored_globally in the activity log.

Page checks ​

RouteMethodNotes
/page-checksPOSTStores a browser check. See the arguments below.
/page-checks/{id}GET, DELETERead one result, or remove it. Removing a desktop result also removes the page from scheduled checks.
/page-checks/scheduleGET, POST, PUT, PATCHRead or change the schedule
/page-checks/schedule/runPOSTRuns one batch of scheduled checks now

Permission: the report capability and an active license.

POST /page-checks arguments:

ArgumentTypeNotes
urlstringRequired. A page of this site, full or relative (/about/).
htmlstringRequired. The rendered page, up to 6 MB.
titlestringUp to 300 characters
findingsobject[]The audit engine's whole-page findings, up to 500
viewportstringdesktop (default) or phone. A page has one stored result per viewport.
rolestringFor a signed-in check: the role the page was opened as
scanstringFor a signed-in check: the scan ID from /role-scans/pass
contrast, layoutarraySent by earlier versions of the screen. Still accepted.

A signed-in result is saved only when the person may manage signed-in checks and scan matches a pass they made for that page and role. Otherwise the answer is 403 lumtera_pro_role.

A result looks like { "id", "url", "title", "errors", "warnings", "notices", "score", "checked", "source", "browser", "viewport", "role", "role_label", "issues": [ … ] }. source is browser or scheduled. role is empty for a signed-out check.

Errors: 400 lumtera_pro_url (not a page on this site).

POST /page-checks/schedule arguments:

ArgumentTypeRequiredNotes
frequencystringYesoff, daily or weekly
templatesboolInclude key page templates
sitemapboolAlso check pages from the site's sitemap
liststringYour own list of addresses, one per line, up to 200,000 characters

GET returns the schedule and its state, including limit: the plan's pages per scheduled run (Personal 25, Growth 100, Agency 250, Unlimited 500; lumtera_pro_limit filter). Other fields include pages, summary, skipped (list lines left out and why), running, done, total, last and next.

Documents ​

POST /documents/scan checks PDFs in the Media Library.

ArgumentTypeDefaultNotes
idint0Check one file
afterint0Without id: the cursor. Pass the cursor from the previous response until done is true.
pagedint1Which page of the results table to return at the end

Permission: the report capability and an active license. Error: 404 lumtera_pro_not_found (not a PDF).

Form tests ​

Form tests are on every Pro plan. See Form tests.

The Page checks screen submits a form on a page with test data, while a safety guard stops the submission from doing anything. These routes store the forms found and their results.

Permission: the report capability, and a license that includes form tests.

RouteMethodNotes
/form-flowsGETEvery form found. Returns { "forms": [ … ] }.
/form-flows/armPOSTStarts a test: sets a dry-run token for this user in an HttpOnly cookie for 120 seconds. Returns { "armed": true, "expires" }. Error: 403 lumtera_pro_form_arm.
/form-flows/armDELETEEnds a test and removes the cookie. Any signed-in user may call it, since it only removes their own token. Returns { "armed": false }.
/form-flows/formsPOSTThe forms a browser check found on a page. They replace that page's earlier list.
/form-flows/{id}POSTStores one form's test result
/form-flows/{id}DELETERemoves a form from the list

POST /form-flows/forms arguments: url (required, a page on this site), title (up to 300 characters), forms (up to 30). Each form has key, adapter (cf7, wpforms, gravity, woo-checkout, woo-blocks or native), name, selector, required, fields and skip.

POST /form-flows/{id} arguments:

ArgumentTypeRequiredNotes
statusstringYestested, skipped or failed
reasonstringUp to 40 characters
findingsarrayUp to 100 findings
requiredintRequired fields found, 0 to 500
errorsintError messages found, 0 to 500

{id} is the form's 32-character ID. Errors: 400 lumtera_pro_url, 404 lumtera_pro_form_missing.

Signed-in checks ​

Signed-in checks are on every plan: 1 role on Personal and Growth, any number on Agency and Unlimited (lumtera_pro_limit feature role_scans). See Signed-in checks.

Checks pages as a test user of a role, such as a shop customer.

Permission: manage_options and an active license. The plan's role limit is checked on every call, including each pass.

RouteMethodNotes
/role-scansGETThe roles checked, their settings and test users
/role-scansPOSTSaves every role's settings, then adds or removes one role
/role-scans/passPOSTA one-time pass to open one page as a role

POST /role-scans arguments:

ArgumentTypeNotes
rolesobjectKeyed by role: { "shop": [ … ], "cart": bool, "list": "one address per line", "scheduled": bool }
addstringA role to add. Lumtera creates its test user. Up to 5 roles.
removestringA role to remove, with its test user

Returns the state plus notes. The admin who saves becomes the one scheduled checks run for. Each role's list keeps up to 100 addresses. wp-admin, the login page and addresses that do something when opened are left out. Errors: 400 or 500 lumtera_pro_roles; 403 lumtera_pro_role_limit when adding a role would go over the plan's number of roles.

POST /role-scans/pass arguments: url and role (both required). Returns { "url", "scan", "role", "label", "note" }. Open url in a frame within 120 seconds, then send the check to POST /page-checks with role and scan. Errors: 400 lumtera_pro_pass_user, lumtera_pro_pass_url.

Test sessions ​

Test sessions are on every Pro plan. See Test sessions.

Sessions are created on the Test sessions screen. These routes record the testing.

Permission: the report capability, and a license that includes evidence. Reopening needs manage_options.

RouteMethodArguments
/sessions/{id}/itemsPOSTcriterion (required, a checklist criterion ID such as 2.1.1), result (required: pass, fail, na or ''), note (up to 2,000 characters), at ({ "browser", "at", "version" }, each up to 60 characters), attachments (up to 10 Media Library IDs)
/sessions/{id}/envPOSTbrowser, at (the assistive technology), version: the session's default setup
/sessions/{id}/signoffPOSTSigns the session off and locks it. At least one item must be done.
/sessions/{id}/reopenPOSTreason (required, up to 500 characters)

Each returns the session: { "items": { "2.1.1": { "result", "note", "tester", "date", "at", "attachments" } }, "counts", "env", "locked", "signoff" }. A signed-off session refuses changes until it is reopened.

Consistency ​

Consistency checks are on the Growth, Agency and Unlimited plans. See Consistency checks.

POST /consistency/run compares the stored browser checks across pages: consistent navigation, consistent identification, consistent help and multiple ways to find pages. No arguments.

Permission: the report capability, and a plan that includes consistency checks.

Returns { "pages", "items", "html" }: the pages compared, the number of findings, and the screen's updated results.

Fixes queue ​

Proposes, approves, applies and undoes fixes in batches. See Fixes queue.

Permission: an active license, the fixes table in place, and either capability from Lumtera → Settings → Fix approvals:

  • Propose fixes (lumtera_propose_changes)
  • Approve and apply fixes (lumtera_approve_changes)

Each action also checks every change: the person must be able to edit its page or shared part. Changes on pages they can't edit are never listed. With the approval policy on (Agency and up), nobody can approve their own proposal.

RouteMethodArgumentsNotes
/remediation/changesGETview (waiting, approved, applied, attention, closed; default waiting), page25 per page. Returns { "view", "page", "pages", "total", "rows", "counts", "cap", "run", "own" }.
/remediation/changes/{id}POSTvalue (required, up to 1,000 characters)Edits a proposed change's text
/remediation/approvePOSTids (up to 500) or all: trueNeeds Approve and apply fixes (403 lumtera_pro_not_approver)
/remediation/rejectPOSTids
/remediation/applyPOSTids or all: true (every approved change)Starts a background run
/remediation/undoPOSTids or all: true (every applied change)Starts a background run
/remediation/previewGETSee belowHow many findings and pages a proposal run would cover
/remediation/proposePOSTSee belowStarts a background proposal run
/remediation/runPOSTThe current run's progress. It may also advance a run that is late, so it is POST only.
/remediation/run/cancelPOSTStops the current run
/remediation/run/dismissPOSTClears a finished run's message

The run routes accept POST only: reading a run's progress can advance it, and a GET must never change anything. To read a run without touching it, use the run field of GET /remediation/changes. (Earlier builds used GET /remediation/run and DELETE /remediation/run.)

/remediation/preview and /remediation/propose take:

ArgumentTypeNotes
typestringRequired. group (one issue across pages), rule (every finding of a check) or part (a shared part)
rulestringThe check ID. With part, empty means every check of the part.
fingerprintstringFor group
part_type, part_keystringFor part

Each run handles up to the plan's fixes per queue run: Personal 25, Growth 100, Agency 250, Unlimited 500 (cap in the responses; lumtera_pro_limit filter). preview returns { "found", "queued", "left", "cap", "pages", "sample", "origin", "part" }.

Errors: 400 lumtera_pro_none_selected, 403 lumtera_pro_not_approver, 403 lumtera_pro_batch_none when a lumtera_pro_limit filter gives the plan no queue changes (-1).

Portfolio ​

The client portfolio is on the Growth, Agency and Unlimited plans. See Client portfolio.

GET /portfolio/rescans returns the progress of rescans started from the Client portfolio screen, keyed by client site ID: { "<site id>": { "name", "status", "text" } }. status is '', queued, running, done or failed. The screen starts rescans itself. This route only reads progress.

Permission: manage_options, and a plan that includes the portfolio.

Reports ​

POST /reports runs on a client site that has Lumtera Pro. An agency hub calls it to create a report and a share link to put in a client email. See Client reports.

Permission: manage_options, plus Manage client reports (lumtera_manage_reports, given under Lumtera → Settings → Permissions; administrators always have it) and the report capability. Lumtera Pro's read-only client role is always refused. The route itself checks the license: without an active one it returns 403 lumtera_pro_inactive.

ArgumentTypeDefaultNotes
titlestring''Up to 200 characters
clientstring''Up to 200 characters
introstring''Up to 2,000 characters
include_pagesbooltrueInclude the per-page list
share_daysint30How long the share link works, 1 to 365 days

Limit: 10 reports per hour per site. Over it returns 429 lumtera_pro_rate.

Returns HTTP 201:

json
{ "id": 318, "title": "…", "created_at": 1790000000,
  "share_url": "https://client.example/…", "expires": 1792592000, "note": "" }

Share links need the Growth plan or above on the client site. Without it, share_url and expires are null and note says which plan is needed. Error: 500 lumtera_pro_report.