API Reference

Sites & settings

A site is one review property on its own domain. The settings object drives the theme, appearance, navigation labels, review workflow and injected scripts.

Endpoints

One account, many sites — the API is not single-site. POST /sites creates additional properties, each with its own domain, content and settings. Limits come from your plan (GET /plan → max_sites); hitting the limit returns 403 with a clear error. Two creation rules worth knowing:

  • Email must be verified before the first site can be created (signups are the upsell source, so the inbox has to be real).
  • Free plan sites live on *.vouchdock.com subdomains only and start active immediately. Attaching your own domain requires a plan with the custom_domains feature — the site starts as pending_dns until POST /sites/:id/check-dns confirms the domain points at us.
MethodEndpointDescription
GET/sitesList your sites
POST/sitesCreate a site — {"domain", "site_title", "language"}
GET/sites/:idSite detail including the full settings object
PATCH/sites/:idUpdate title, meta, language, status, settings
DELETE/sites/:idDelete site and all its content
POST/sites/:id/check-dnsVerify the domain routes to us — three methods, any one passes: direct A record, HTTP probe of /.well-known/vouchdock-verify (works through Cloudflare/proxies), or a _vouchdock TXT record. Response includes per-method details and the site's verify_token.
GET/sites/:id/exportFull JSON export — categories, offers, reviews, replies, gallery
POST/sites/:id/import?dry_run=1Import with dry-run preview and per-item conflict report
POST/sites/:id/settings/applyCopy this site's settings to your other sites: {"target_site_ids": ["…"]}
GET/sites/:id/usersList this site's community accounts (display name, email, verified/consent/ban state, join date) — panel → Site → Community.
POST/sites/:id/users/:uid/banBan or unban an account: {"banned": true, "reason": "spam"}. Banned users can't log in; their reviews stay unless deleted separately.
GET/sites/:id/smtpRead this site's own SMTP relay config (password always masked).
PATCH/sites/:id/smtpSet host/port/user/password/from — all site mail (verification, welcome, digest) sends through it instead of the platform relay. AES-GCM at rest; sending the mask or an empty password keeps the stored one.
POST/sites/:id/smtp/testSend a real test message and return the SMTP conversation error verbatim, if any.

Site payload

FieldTypeDescription
site_titlestringBrand name — header, footer, fallback meta title.
meta_titlestringHomepage <title> override.
languagestringISO code (en, pl…) — html lang and built-in UI strings.
statusstringactive | suspended (suspended sites stop serving public pages).
settingsobjectThe big one — documented key by key below.

The settings object

PATCH {"settings": {…}} merges what you send. Every key below is optional; unset keys fall back to theme defaults.

appearance

KeyTypeWhat it does
appearance.layoutstringwide (default) | boxed — page container width.
appearance.site_logostringLogo URL (media library). Empty → site title text.
appearance.favicon_urlstringFavicon URL.
appearance.link_colorstringHex, e.g. #6d4aff. Empty → theme default.
appearance.button_colorstringHex — CTA buttons.
appearance.breadcrumbs_bgstringHex background behind breadcrumbs.
appearance.top10_countnumberHow many offers the homepage toplist shows (default 10).
appearance.rotate_ties_dailyboolDefault true — offers sharing a rank are shuffled deterministically once per day, so equal-scored long tail rotates instead of freezing in insertion order.
appearance.new_strip_countnumber"New this week" strip size on the homepage (default 6, 0 = off) — auto-filled with the newest live offers.
appearance.new_strip_daysnumberHow fresh an offer must be for the strip (default 14 days, 0 = no age limit).
appearance.home_railsboolDefault true — per-category offer rails on the homepage (rendered only when 2+ categories have live offers).
appearance.rail_countnumberOffers per homepage category rail (default 4).
appearance.category_page_sizenumberOffers per category page with a pager (default 24, 0 = show all). Pager uses ?page=N with rel prev/next.
appearance.rankedboolDefault true — rank numbers on cards. False = unranked grid.
appearance.collapsible_menuboolBurger menu on mobile (useful with long category lists).
appearance.hide_offer_urlboolHide the product's domain under CTA buttons.
appearance.review_page_top5bool"Top rated in this category" box on offer pages.
appearance.show_newsletter_footerboolNewsletter signup in the footer (feeds leads).
appearance.nav_categoriesboolShow categories in the top navigation.
appearance.nav_offers_subitemsboolTop 3 offers per category as nav sub-items.
appearance.sidebar_positionstringright (default) | left | hide_front | hide_all.
appearance.sidebar_modules.articlesboolLatest articles module.
appearance.sidebar_modules.spotlightboolSpotlight offer module.
appearance.sidebar_modules.new_offersboolNewest offers module.
appearance.sidebar_modules.new_reviewsboolLatest reviews module.

nav & texts

KeyTypeWhat it does
nav.reviews_label / categories_label / blog_labelstringRename built-in navigation entries.
texts.meta_descriptionstringHomepage meta description.
texts.expired_noticestringNotice shown when an offer is marked unavailable.

review

KeyTypeWhat it does
review.moderationboolDefault true — new reviews wait for approval. False = publish instantly.
review.email_confirmationboolReviewer must confirm via emailed link before the review goes live.
review.clickable_authorsboolAuthor names link to their other reviews.
review.noindex_singleboolDefault true — single-review permalinks render noindex, follow and drop out of the sitemap (thin UGC used to be 60–83% of the index). The pages stay crawlable and linked from the offer page. Flip to false to index user reviews (recommended only with long-form, quality-gated reviews).
review.criteriastring[]Per-criterion ratings, e.g. ["Value", "Support", "Ease of use"] — the public form asks for each, averages display on the offer page.
review.require_accountboolDefault true — only logged-in visitors with a verified email (and confirmed 18+ date of birth) can post reviews and comments. Identity comes from their account session, so reviewer name and email fields disappear from the public form.
review.excerpt_lengthnumberOffer pages render each review as an excerpt (default 280 chars, 0 = no truncation) with a "Read full review" link to the review's own page — keeps Google from seeing two full copies of every review. Short reviews that fit pass through unchanged.

home

KeyTypeWhat it does
home.sortstringHomepage toplist ordering: rating (default — rank, with daily rotation of equal-scored offers), newest (created first), popular (most clicks in a rolling 30-day window).

growth & newsletter

KeyTypeWhat it does
growth.account_prompt_viewsnumberShow the floating "create a free account" card after this many pageviews (default 3, 0 = off). Logged-out visitors only; dismissal is remembered.
newsletter.enabledboolTurn on the weekly digest — top offers emailed every Monday 08:00 UTC to subscribers with marketing consent.
newsletter.subject / newsletter.introstringDigest subject line and intro paragraph.

lead_form

KeyTypeWhat it does
lead_form.enabledboolHomepage lead capture form.
lead_form.title / subtitle / success_messagestringForm copy and post-submit message.
lead_form.fields.name / fields.phoneboolAsk for name and/or phone in addition to email.
lead_form.postback_urlstringWe POST every captured lead here (server-side) — your CRM webhook.

Trust pages (About, Disclosure, FAQ, Contact)

Every site ships four site-adaptive trust pages next to Terms and Privacy — no setup needed. Each is personalised with the site's title, domain and contact email, and each footer link can be toggled:

KeyDefaultWhat it does
misc.show_about_linktrueFooter link + /about/ — who runs the site, how reviews are created (editorial team, registered users, AI-assisted content), editorial independence.
misc.verticalstringdating (default), cam or gallery — chooses the schema.org type for offer pages (Product / ProfilePage / ImageGallery) and the human copy on index pages ("dating sites" / "live cam models" / "photo galleries"). Wrong vertical = wrong markup in the eyes of search engines.
misc.editor_namestringOptional named editor shown as "Reviewed by …" on offer pages, next to the DB-driven "Last updated" date. E-E-A-T signal — leave empty to hide the byline.
misc.indexnow_enabledbooleanfalseTurn on IndexNow notifications. No account or API registration is needed: the key is generated by the platform (Misc tab → Generate key) and ownership is proven by serving /{key}.txt from the site root. The API pings api.indexnow.org on review approval and offer create/update, throttled to one ping per URL per 5 minutes. The panel also offers "Notify all pages now" to submit the whole sitemap-equivalent URL set once.
misc.org_logo_urlstringAbsolute https URL of the brand logo — emitted as Organization.logo in the sitewide schema.org graph.
misc.social_same_asstringComma-separated social profile URLs — emitted as Organization.sameAs for entity consolidation.
misc.indexnow_keystring""The per-site IndexNow key (generated automatically, 32 hex chars per the spec's 8–128 char rule). Served at https://{domain}/{key}.txt. Regenerate from the panel to rotate; search engines re-verify on the next notification (first response may be HTTP 202 while pending).
misc.show_disclosure_linktrueFooter link + /affiliate-disclosure/ — listings may contain affiliate products; commission at no extra cost; monetisation never affects scores.
misc.show_faq_linktrueFooter link + /faq/ — independence, authorship, accounts, email verification, password reset, unsubscribe.
misc.show_contact_linktrueFooter link + /contact/ — contact routing by topic (corrections, GDPR, moderation, business).
texts.contact_emailsupport@<domain>Contact address used across all trust and legal pages.

All six pages (/about/, /affiliate-disclosure/, /faq/, /contact/, /terms/, /privacy/) are listed in sitemap.xml and llms.txt.

Friendly sites (cross-site links)

Link your sites together and steer link equity where you want it. The block renders in the footer; every link is fully SEO-controllable:

KeyTypeWhat it does
links.friendly_titlestringSection heading. Default: "Friendly sites".
links.friendly_sites[]arrayEach entry: url, anchor (the phrase the target ranks for), nofollow ("true" = no vouch), home_only ("true" = homepage only, concentrating link equity on the strongest page).

JavaScript injection

KeyTypeWhat it does
js_headerstring (HTML/JS)Injected verbatim into <head> — analytics tags (GA, Plausible), verification metas.
js_bodystringRight after <body> opens — GTM noscript, etc.
js_footerstringBefore </body> — chat widgets, heatmaps.
Turn on a sidebar module + set GA
curl -X PATCH $API/sites/$SITE_ID \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{
    "settings": {
      "appearance": {"sidebar_modules": {"new_reviews": true}, "review_page_top5": true},
      "js_header": "<script async src=\"https://www.googletagmanager.com/gtag/js?id=G-XXXX\"></script>…"
    }
  }'
# live on your domain within ~5 seconds

Export & import

GET /sites/:id/export returns the whole site as JSON — categories, offers (with pros/cons, ratings, SEO), reviews, replies, gallery items. Import into another site with POST /sites/:id/import; add ?dry_run=1 first to get a create/skip plan with slug-conflict explanations before anything is written.

Gallery items round-trip as URLs: on import they're re-attached as external links, which keeps working because the media host is shared.