Base URL: /wp-json/allfeedback/v1
Response envelope: { "success": true, "data": <payload> } (all endpoints)
Auth: All admin endpoints require manage_options (overridable via allfeedback_required_capability filter). The submit endpoint requires a WordPress nonce only.
Return a paginated, filterable list of surveys.
Query params
| Param | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number (1-based) |
per_page |
int | 20 | Items per page (max 100) |
search |
string | — | Filter by title keyword |
status |
string | any |
One of: draft, published, archived, trashed, any |
orderby |
string | created_at |
Column to sort by |
order |
string | DESC |
ASC or DESC |
Response
{
"surveys": [ { "id": 1, "title": "NPS Survey", "status": "published", ... } ],
"total": 42,
"page": 1,
"per_page": 20
}Create a new survey (always starts as draft).
Body
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Survey title |
description |
string | No | Optional description |
form_schema |
object/array | No | Field definitions |
settings |
object | No | Behavioural settings. Keys: trigger_type, delay_value, delay_unit, scroll_depth, display_frequency, max_impressions, dismiss_wait_value, dismiss_wait_unit, user_state, target_pages, target_page_ids, progress_indicator, trigger_icon. Advanced page-targeting rules stored as nested settings.targeting object. |
styling |
object | No | Visual appearance overrides — separate from behavioural settings. Keys: widget_position (bottom-right | bottom-left | side-tab | ""), widget_icon (string), progress_indicator (dots | numbers | bar | none). Empty string on any key means inherit the global default. |
Response — 201
{ "id": 5, "title": "My Survey", "status": "draft", "styling": null, ... }Return a single survey including full form_schema, settings, and styling.
Non-admins can only read published surveys (widget/shortcode use-case).
Response
{
"id": 1,
"title": "NPS Survey",
"status": "published",
"form_schema": { "version": "1.0", "sections": [...] },
"settings": {
"trigger_type": "immediate", "delay_value": 0, "delay_unit": "seconds",
"display_frequency": "until_submit", "max_impressions": 3,
"user_state": "all", "target_pages": "all", "target_page_ids": [],
"progress_indicator": "dots", "trigger_icon": "message",
"targeting": { "mode": "all", "rules": [], "exclusions": [] }
},
"settings": { ..., "widget_label": "Feedback" },
"styling": { "widget_position": "bottom-right", "widget_icon": "", "progress_indicator": "dots" }
}Apply a full or partial update to an existing survey. Called by the builder autosave on every change. Send only the fields that changed.
Body — same fields as POST /surveys plus status (enum: draft, published, archived, trashed).
Response — updated survey object. When status: "published" is included and other published surveys target overlapping pages, a warnings array is appended:
{
"id": 3, "title": "...", "status": "published",
"warnings": [{
"code": "targeting_conflict",
"message": "2 published forms target the same pages. Visitors will only see the most recently published one.",
"conflicting_surveys": [
{ "id": 1, "title": "NPS Survey", "targeting_scope": "all_pages" },
{ "id": 2, "title": "Exit Survey", "targeting_scope": "specific_pages" }
],
"can_revert_to_draft": true
}]
}Bulk-move multiple surveys to the trash. Already-trashed surveys are skipped.
Body
{ "ids": [1, 2, 3] }Response
{ "trashed": 2, "skipped": [3], "failed": [] }Bulk-permanently-delete multiple surveys. Only surveys with status trashed are deleted — others are skipped. Also permanently deletes all responses belonging to each deleted survey.
Body
{ "ids": [1, 2] }Response
{ "deleted": 2, "skipped": [], "failed": [] }Move a single survey to the trash (sets status to trashed). Returns 409 if already trashed.
Response
{ "trashed": true, "id": 1 }Permanently delete a single survey. The survey must be trashed first — returns 409 otherwise. Also permanently deletes all associated responses.
Response
{ "deleted": true, "id": 1 }Create a copy of a survey with status reset to draft.
Response — 201 — the new survey object.
Transition a survey from any status to published.
Response — updated survey object. Same warnings shape as PUT /surveys/{id} applies when targeting conflicts are detected.
Responses from trashed surveys are never returned by any listing endpoint.
Return a paginated list of responses across all surveys (excluding trashed surveys).
Query params
| Param | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number |
per_page |
int | 20 | Items per page (max 100) |
date_from |
string | — | Lower-bound date filter (Y-m-d) |
date_to |
string | — | Upper-bound date filter (Y-m-d) |
Response
{
"responses": [ { "id": 10, "survey_id": 1, "score": 9, ... } ],
"total": 150,
"page": 1,
"per_page": 20
}Bulk-permanently-delete multiple responses by ID across any survey.
Body
{ "ids": [10, 11, 12] }Response
{ "deleted": 3, "failed": [] }IDs that do not exist are counted as failed.
Return the total number of unread responses across all non-trashed surveys.
Used by the React admin to keep the WP sidebar badge in sync reactively (polled every 60 s).
Response
{ "count": 8 }Bulk mark multiple responses as read across any survey.
Body
{ "ids": [10, 11, 12] }Response
{ "updated": 3, "failed": [] }IDs that do not exist are counted as failed.
Bulk mark multiple responses as unread across any survey.
Body
{ "ids": [10, 11, 12] }Response
{ "updated": 3, "failed": [] }Return a paginated list of responses for a specific survey. Returns an empty list (not 404) if the survey is trashed.
Query params — same as GET /responses.
Response — same shape as GET /responses.
Bulk-permanently-delete multiple responses scoped to a specific survey. Responses that belong to a different survey are counted as failed.
Body
{ "ids": [10, 11] }Response
{ "deleted": 2, "failed": [] }Return a single response. Returns 404 if the response does not belong to the given survey.
Response
{
"id": 10,
"survey_id": 1,
"response_data": { "field_1": "Very satisfied" },
"score": 9,
"page_url": "https://example.com/pricing",
"device_type": "desktop",
"user_id": null,
"is_read": false,
"created_at": "2026-04-15 07:00:00"
}Patch response_data and/or is_read on an existing response.
Body
| Field | Type | Description |
|---|---|---|
response_data |
object/null | Updated field answers |
is_read |
boolean | Mark response as read/unread |
Response — updated response object.
Permanently delete a single response. Returns 404 if the response does not belong to the given survey.
Response
{ "deleted": true, "id": 10 }Track a single session lifecycle event for a survey. Public — no manage_options required, but rate-limited to 60 events per session per 5-minute window (configurable via allfeedback_analytics_rate_limit filter).
Body
| Field | Type | Required | Description |
|---|---|---|---|
event |
string | Yes | One of: viewed, started, abandoned, heartbeat |
session_id |
string | Yes | Client-generated UUID v4 (max 36 chars) |
guest_id |
string | No | Persistent guest visitor token from localStorage (max 36 chars) |
Response — 200
{ "success": true, "data": null }Return aggregate session metrics for a single survey. Admin only.
Response
{
"total_views": 500,
"total_starts": 200,
"total_submissions": 150,
"completion_rate": 75.00,
"abandonment_rate": 25.00,
"avg_completion_time": 120
}
completion_rateandabandonment_rateare percentages (0–100).avg_completion_timeis in seconds. All three fields arenullwhen there are no started sessions yet. Abandonment includes explicit closes and sessions withstarted_atset but nosubmitted_atwhoselast_active_atis older than 30 minutes.
Return the global "all forms" summary — stat cards with week-over-week deltas, a 30-day response chart, enriched recent responses, and device breakdown. Admin only. All aggregation runs in SQL.
Response
{
"stats": {
"total_feedback": { "value": 2847, "change": 12.4 },
"completion_rate": { "value": 87.3, "change": 3.1 },
"abandonment_rate": { "value": 12.7, "change": -2.4 },
"avg_rating": { "value": 4.4, "change": 2.8 },
"active_surveys": { "value": 12, "new_this_week": 2, "change": 16.7 }
},
"chart": [
{ "date": "2026-03-25", "count": 52 },
{ "date": "2026-03-26", "count": 38 }
],
"total_in_period": 3189,
"recent_responses": [
{
"id": 42,
"survey_id": 3,
"survey_title": "Post-purchase NPS",
"survey_type": "NPS",
"score": 10,
"response_text": "Fastest support I've ever gotten from a plugin. Refreshing.",
"created_at": "2026-04-24 11:00:00"
}
],
"device_breakdown": {
"desktop": 1500,
"mobile": 900,
"tablet": 400,
"unknown": 47
}
}Field notes
| Field | Notes |
|---|---|
stats.*.change |
Week-over-week % change (this 7 days vs previous 7 days). null when baseline is zero or no data. |
stats.active_surveys.change |
% of active surveys that are new this week (not a WoW delta). |
stats.abandonment_rate.value |
% of started sessions that were abandoned (explicit closes + timed-out sessions > 30 min inactive). null when no started sessions exist. |
chart |
Always 30 entries (today − 29 → today). Days with no responses have count: 0. |
total_in_period |
Sum of all chart[*].count values. |
recent_responses[*].survey_type |
"NPS", "CSAT", "CES", or null (plain text survey). Derived from the first primary field in form_schema. |
recent_responses[*].score |
Raw numeric score as stored (NPS: 0–10, CSAT: 1–5, etc). null if no scored field. |
device_breakdown |
Keys are desktop, mobile, tablet, or unknown. Only keys with at least one response are returned. |
Return a paginated list of all forms with per-form session and response metrics. Admin only. Uses bulk SQL aggregation — 2 queries regardless of page size, not N+1.
Query params
| Param | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number (1-based) |
per_page |
int | 20 | Items per page (max 100) |
status |
string | — | Filter by status: draft, published, archived, trashed |
Response
{
"forms": [
{
"id": 1,
"title": "Customer NPS Survey",
"status": "published",
"response_count": 150,
"created_at": "2024-01-15 10:00:00",
"updated_at": "2024-02-01 12:00:00",
"session_metrics": {
"total_views": 500,
"total_starts": 200,
"total_submissions": 150,
"completion_rate": 75.00,
"abandonment_rate": 25.00,
"avg_completion_time": 120
},
"response_metrics": {
"total_responses": 150,
"average_score": 8.5,
"nps_score": {
"score": 65.00,
"promoters": 100,
"passives": 30,
"detractors": 20
}
}
}
],
"pagination": {
"total": 5,
"total_pages": 1,
"page": 1,
"per_page": 20
},
"total_forms": 5
}
average_scoreisnullwhen no scored responses exist.nps_score.scoreis0.0when there are no responses. Session metric fields (completion_rate,abandonment_rate,avg_completion_time) arenullwhen no sessions exist for that form.
Return full analytics for a single form: survey metadata + session metrics + complete response metrics (including device breakdown and responses over time). Admin only.
Response
{
"survey": {
"id": 1,
"title": "Customer NPS Survey",
"description": "Help us improve.",
"status": "published",
"response_count": 150,
"created_at": "2024-01-15 10:00:00",
"updated_at": "2024-02-01 12:00:00"
},
"session_metrics": {
"total_views": 500,
"total_starts": 200,
"total_submissions": 150,
"completion_rate": 75.00,
"abandonment_rate": 25.00,
"avg_completion_time": 120
},
"response_metrics": {
"total_responses": 150,
"average_score": 8.5,
"nps_score": {
"score": 65.00,
"promoters": 100,
"passives": 30,
"detractors": 20
},
"response_rate_by_device": {
"desktop": 100,
"mobile": 40,
"tablet": 10
},
"responses_over_time": {
"2024-01-15": 10,
"2024-01-16": 15,
"2024-01-17": 8
}
}
}Error responses
| Code | Reason |
|---|---|
| 404 | Survey not found |
Accept a public widget submission. Requires a valid WordPress nonce (action: allfeedback_submit). No manage_options needed.
Submission flow:
- Nonce verification
- Survey existence + published-status guard (admins may submit to draft surveys as preview)
- Three-tier duplicate detection (skipped if privacy mode or
manage_options):- Logged-in user →
existsByUserId() - Guest with
visitor_token→existsByGuestToken() - Guest without token →
existsByIpHash()(fallback)
- Logged-in user →
allfeedback_allow_response_submissionfilter (pro blocking hooks)allfeedback_response_data_before_savefilter (data transformation)- Persist via
SubmitResponseService→ firesallfeedback:response:submittedevent allfeedback_response_submittedaction for third-party side-effects
Body
| Field | Type | Required | Description |
|---|---|---|---|
nonce |
string | Yes | WordPress nonce for action allfeedback_submit |
response_data |
object | Yes | Field answers keyed by field ID |
score |
int | No | Numeric score (0–100) for NPS/CSAT/CES/star fields |
page_url |
string | No | URL where the survey was displayed (max 2083 chars) |
device_type |
string | No | desktop, tablet, or mobile |
consent_given |
boolean | No | GDPR data-processing consent flag (default: false) |
visitor_token |
string | No | UUID v4 generated in the browser (localStorage key allfb_visitor_id). Used for guest duplicate detection. Invalid/missing values fall back to IP hash. |
Response — 201
{ "id": 42 }Error responses
| Code | Reason |
|---|---|
| 403 | Invalid nonce |
| 403 | Survey not published (and not admin preview) |
| 403 | Blocked by allfeedback_allow_response_submission filter |
| 409 | Duplicate submission (user_id, guest_token, or IP hash match) |
| 422 | Validation failure in response data |
| 404 | Survey not found |
Return a high-level stats summary for the admin dashboard. Admin only.
Response
{
"stats": {
"surveys": 12,
"responses": 843,
"unread": 5
},
"chart": [
{ "date": "2026-03-25", "count": 14 },
{ "date": "2026-03-26", "count": 9 }
],
"recent": [
{
"id": 42,
"survey_id": 3,
"created_at": "2026-04-22 11:05:00",
"summary": "Very satisfied"
}
]
}
chartalways contains exactly 30 entries (today − 29 days → today), withcount: 0for days with no responses.recentcontains the 5 most recent responses across all surveys.
Return the display state for a survey for the current logged-in user. Used by the frontend widget to determine whether to show, suppress, or defer the survey. Requires the user to be logged in.
Response
{
"should_show": true,
"reason": null
}When suppressed, should_show is false and reason explains why:
{
"should_show": false,
"reason": "already_submitted"
}Possible reason values: already_submitted, max_impressions_reached, dismissed_recently, targeting_mismatch.
Return the current setup wizard state. Admin only.
Response
{
"status": "pending",
"completed_steps": ["welcome", "settings"],
"current_step": "create_survey"
}Update the wizard state (advance steps, mark complete). Admin only.
Body
| Field | Type | Description |
|---|---|---|
status |
string | pending or completed |
completed_steps |
array | List of completed step slugs |
current_step |
string | Active step slug |
Response — updated wizard state object.
Return the complete three-level settings object merged with defaults. Every page, section, and field is always present.
Response
{
"general": { "widget": { "color": "#6366F1", "position": "bottom-right" } },
"advanced": {
"privacy": { "disable_user_details": false },
"logging": { "enabled": false, "level": "error", "retention_days": 30 },
"plugin": { "delete_on_uninstall": false }
}
}Persist one or more pages/sections/fields. Partial updates are fully supported — send only what changed.
Body example
{ "advanced": { "logging": { "enabled": true, "level": "debug" } } }Response — complete settings object after the update.
Search published pages and posts. Powers the "Select specific pages & posts" targeting picker in the form builder.
Query params
| Param | Type | Default | Description |
|---|---|---|---|
search |
string | — | Keyword filter on post title |
post_type |
string | — | Comma-separated post type slugs (default: page,post) |
page |
int | 1 | Page number |
per_page |
int | 20 | Items per page (max 50) |
Response
{
"items": [ { "id": 5, "title": "Pricing", "type": "page", "url": "https://example.com/pricing" } ],
"total": 12,
"page": 1,
"per_page": 20
}Return a paginated list of log file metadata (no content).
Query params
| Param | Type | Default | Description |
|---|---|---|---|
page |
int | 1 | Page number |
per_page |
int | 20 | Items per page (max 100) |
orderby |
string | date |
date, name, or size |
order |
string | desc |
asc or desc |
search |
string | — | Filter by filename |
Response
{
"logs": [ { "id": "allfeedback-2026-04-15", "name": "allfeedback-2026-04-15.log", "size": "12.4 KB", "bytes": 12698, "entries": 87, "date": "2026-04-15T07:00:00+00:00" } ],
"total": 5,
"page": 1,
"pages": 1
}Bulk-delete multiple log files.
Body
{ "ids": ["allfeedback-2026-04-13", "allfeedback-2026-04-14"] }Response
{ "deleted": ["allfeedback-2026-04-13"], "skipped": ["allfeedback-2026-04-14"], "failed": [] }skipped = file not found. failed = file exists but could not be deleted.
Return metadata and full content of a single log file. id is the filename stem without .log.
Response
{
"id": "allfeedback-2026-04-15",
"name": "allfeedback-2026-04-15.log",
"size": "12.4 KB",
"bytes": 12698,
"entries": 87,
"date": "2026-04-15T07:00:00+00:00",
"content": "[2026-04-15 07:00:00] INFO Survey created ..."
}Permanently delete a single log file.
Response
{ "deleted": true, "id": "allfeedback-2026-04-15" }| Method | Path | Auth | Handler |
|---|---|---|---|
| GET | /surveys |
Admin | List surveys |
| POST | /surveys |
Admin | Create survey |
| DELETE | /surveys/trash |
Admin | Bulk trash surveys |
| DELETE | /surveys/delete |
Admin | Bulk permanent delete surveys + responses |
| GET | /surveys/{id} |
Public* | Get single survey |
| PUT | /surveys/{id} |
Admin | Update survey |
| DELETE | /surveys/{id}/trash |
Admin | Trash single survey |
| DELETE | /surveys/{id}/delete |
Admin | Permanently delete survey + responses |
| POST | /surveys/{id}/duplicate |
Admin | Duplicate survey |
| POST | /surveys/{id}/publish |
Admin | Publish survey |
| POST | /surveys/{id}/submit |
Nonce | Submit a response (public widget) |
| POST | /surveys/{id}/analytics/event |
Public† | Track session lifecycle event |
| GET | /surveys/{id}/analytics |
Admin | Session metrics for one survey |
| GET | /surveys/{id}/state |
Auth | Display state for logged-in user |
| GET | /analytics/overview |
Admin | Global stats, chart, recent responses, device breakdown |
| GET | /analytics/forms |
Admin | All forms with session + response metrics |
| GET | /analytics/forms/{id} |
Admin | Full analytics for a single form |
| GET | /responses |
Admin | List all responses (all surveys) |
| GET | /responses/unread-count |
Admin | Count unread responses (sidebar badge) |
| DELETE | /responses/delete |
Admin | Bulk delete responses (any survey) |
| POST | /responses/mark-read |
Admin | Bulk mark responses as read |
| POST | /responses/mark-unread |
Admin | Bulk mark responses as unread |
| GET | /surveys/{id}/responses |
Admin | List responses for one survey |
| DELETE | /surveys/{id}/responses/delete |
Admin | Bulk delete responses for one survey |
| GET | /surveys/{id}/responses/{rid} |
Admin | Get single response |
| PUT | /surveys/{id}/responses/{rid} |
Admin | Patch response |
| DELETE | /surveys/{id}/responses/{rid} |
Admin | Delete single response |
| GET | /dashboard/stats |
Admin | High-level stats + 30-day chart |
| GET | /wizard |
Admin | Get wizard state |
| PUT | /wizard |
Admin | Update wizard state |
| GET | /settings |
Admin | Get all settings |
| PUT/PATCH | /settings |
Admin | Update settings |
| GET | /content-search |
Admin | Search pages/posts for targeting |
| GET | /logs |
Admin | List log files |
| DELETE | /logs/delete |
Admin | Bulk delete log files |
| GET | /logs/{id} |
Admin | Get single log file with content |
| DELETE | /logs/{id} |
Admin | Delete single log file |
*GET /surveys/{id} — admins see all statuses; non-admins see only published surveys.
†POST /surveys/{id}/analytics/event — public but rate-limited to 60 events per session per 5-minute window.