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.
| Method | Endpoint | Description |
| GET | /sites | List your sites |
| POST | /sites | Create a site — {"domain", "site_title", "language"} |
| GET | /sites/:id | Site detail including the full settings object |
| PATCH | /sites/:id | Update title, meta, language, status, settings |
| DELETE | /sites/:id | Delete site and all its content |
| POST | /sites/:id/check-dns | Verify 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/export | Full JSON export — categories, offers, reviews, replies, gallery |
| POST | /sites/:id/import?dry_run=1 | Import with dry-run preview and per-item conflict report |
| POST | /sites/:id/settings/apply | Copy this site's settings to your other sites: {"target_site_ids": ["…"]} |
| GET | /sites/:id/users | List this site's community accounts (display name, email, verified/consent/ban state, join date) — panel → Site → Community. |
| POST | /sites/:id/users/:uid/ban | Ban or unban an account: {"banned": true, "reason": "spam"}. Banned users can't log in; their reviews stay unless deleted separately. |
| GET | /sites/:id/smtp | Read this site's own SMTP relay config (password always masked). |
| PATCH | /sites/:id/smtp | Set 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/test | Send a real test message and return the SMTP conversation error verbatim, if any. |
Site payload
| Field | Type | Description |
site_title | string | Brand name — header, footer, fallback meta title. |
meta_title | string | Homepage <title> override. |
language | string | ISO code (en, pl…) — html lang and built-in UI strings. |
status | string | active | suspended (suspended sites stop serving public pages). |
settings | object | The 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
| Key | Type | What it does |
appearance.layout | string | wide (default) | boxed — page container width. |
appearance.site_logo | string | Logo URL (media library). Empty → site title text. |
appearance.favicon_url | string | Favicon URL. |
appearance.link_color | string | Hex, e.g. #6d4aff. Empty → theme default. |
appearance.button_color | string | Hex — CTA buttons. |
appearance.breadcrumbs_bg | string | Hex background behind breadcrumbs. |
appearance.top10_count | number | How many offers the homepage toplist shows (default 10). |
appearance.rotate_ties_daily | bool | Default 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_count | number | "New this week" strip size on the homepage (default 6, 0 = off) — auto-filled with the newest live offers. |
appearance.new_strip_days | number | How fresh an offer must be for the strip (default 14 days, 0 = no age limit). |
appearance.home_rails | bool | Default true — per-category offer rails on the homepage (rendered only when 2+ categories have live offers). |
appearance.rail_count | number | Offers per homepage category rail (default 4). |
appearance.category_page_size | number | Offers per category page with a pager (default 24, 0 = show all). Pager uses ?page=N with rel prev/next. |
appearance.ranked | bool | Default true — rank numbers on cards. False = unranked grid. |
appearance.collapsible_menu | bool | Burger menu on mobile (useful with long category lists). |
appearance.hide_offer_url | bool | Hide the product's domain under CTA buttons. |
appearance.review_page_top5 | bool | "Top rated in this category" box on offer pages. |
appearance.show_newsletter_footer | bool | Newsletter signup in the footer (feeds leads). |
appearance.nav_categories | bool | Show categories in the top navigation. |
appearance.nav_offers_subitems | bool | Top 3 offers per category as nav sub-items. |
appearance.sidebar_position | string | right (default) | left | hide_front | hide_all. |
appearance.sidebar_modules.articles | bool | Latest articles module. |
appearance.sidebar_modules.spotlight | bool | Spotlight offer module. |
appearance.sidebar_modules.new_offers | bool | Newest offers module. |
appearance.sidebar_modules.new_reviews | bool | Latest reviews module. |
nav & texts
| Key | Type | What it does |
nav.reviews_label / categories_label / blog_label | string | Rename built-in navigation entries. |
texts.meta_description | string | Homepage meta description. |
texts.expired_notice | string | Notice shown when an offer is marked unavailable. |
review
| Key | Type | What it does |
review.moderation | bool | Default true — new reviews wait for approval. False = publish instantly. |
review.email_confirmation | bool | Reviewer must confirm via emailed link before the review goes live. |
review.clickable_authors | bool | Author names link to their other reviews. |
review.noindex_single | bool | Default 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.criteria | string[] | Per-criterion ratings, e.g. ["Value", "Support", "Ease of use"] — the public form asks for each, averages display on the offer page. |
review.require_account | bool | Default 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_length | number | Offer 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
| Key | Type | What it does |
home.sort | string | Homepage 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
| Key | Type | What it does |
growth.account_prompt_views | number | Show the floating "create a free account" card after this many pageviews (default 3, 0 = off). Logged-out visitors only; dismissal is remembered. |
newsletter.enabled | bool | Turn on the weekly digest — top offers emailed every Monday 08:00 UTC to subscribers with marketing consent. |
newsletter.subject / newsletter.intro | string | Digest subject line and intro paragraph. |
lead_form
| Key | Type | What it does |
lead_form.enabled | bool | Homepage lead capture form. |
lead_form.title / subtitle / success_message | string | Form copy and post-submit message. |
lead_form.fields.name / fields.phone | bool | Ask for name and/or phone in addition to email. |
lead_form.postback_url | string | We 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:
| Key | Default | What it does |
misc.show_about_link | true | Footer link + /about/ — who runs the site, how reviews are created (editorial team, registered users, AI-assisted content), editorial independence. |
misc.vertical | string | dating (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_name | string | Optional 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_enabled | boolean | false | Turn 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_url | string | Absolute https URL of the brand logo — emitted as Organization.logo in the sitewide schema.org graph. |
misc.social_same_as | string | Comma-separated social profile URLs — emitted as Organization.sameAs for entity consolidation. |
misc.indexnow_key | string | "" | 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_link | true | Footer link + /affiliate-disclosure/ — listings may contain affiliate products; commission at no extra cost; monetisation never affects scores. |
misc.show_faq_link | true | Footer link + /faq/ — independence, authorship, accounts, email verification, password reset, unsubscribe. |
misc.show_contact_link | true | Footer link + /contact/ — contact routing by topic (corrections, GDPR, moderation, business). |
texts.contact_email | support@<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:
| Key | Type | What it does |
links.friendly_title | string | Section heading. Default: "Friendly sites". |
links.friendly_sites[] | array | Each 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
| Key | Type | What it does |
js_header | string (HTML/JS) | Injected verbatim into <head> — analytics tags (GA, Plausible), verification metas. |
js_body | string | Right after <body> opens — GTM noscript, etc. |
js_footer | string | Before </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.