{"openapi":"3.0.3","info":{"title":"FeelingSurf API","version":"1.5.0","description":"Programmatic access to a FeelingSurf account: manage the websites that\nreceive automated visits, and read account information.\n\n## Quick start\n\nEvery endpoint except the health check needs an API key.\n\n**1. Check connectivity** — no key required:\n\n```\ncurl https://api.feelingsurf.fr/v1/ping\n```\n\n**2. Get your API key.** Generate one in your dashboard under\n[Settings → API](https://www.feelingsurf.fr/settings/api). Every account can use the\nAPI; Premium gets a higher rate limit and the Premium-only site settings.\n\n**3. Make your first authenticated request** — send the key as a bearer token:\n\n```\ncurl https://api.feelingsurf.fr/v1/me -H \"Authorization: Bearer fsk_your_key\"\n```\n\nThat returns your account summary. From there: list your sites with\n`GET /v1/sites`, add one with `POST /v1/sites`, and so on — every endpoint is\ndocumented below.\n\n## Authentication\n\nEvery request carries `Authorization: Bearer fsk_...`. The key is distinct\nfrom the FeelingSurfViewer desktop app token — treat it like a password, and\nmanage or revoke it any time under Settings → API.\n\nSend request bodies as JSON and set a `Content-Type: application/json` header\non `POST` and `PATCH` requests. Responses are JSON too, except the screenshot\nimage endpoints, which stream raw image bytes.\n\n## MCP server\n\nThe API is also a remote MCP server, so AI assistants such as Claude Code\nor Cursor can manage your account from a conversation. Every authenticated\nendpoint below is a tool (`list_sites`, `update_site`, `create_proxy`…)\nexcept the blurred screenshot preview. Updates and deletions are flagged as\ndestructive, so your assistant asks before running them.\n[Overview and example requests](https://www.feelingsurf.fr/mcp).\n\nThe endpoint is `https://api.feelingsurf.fr/mcp` (Streamable HTTP). Connecting and listing\nthe tools works without a key; calling one takes the same API key\nand shares the REST API's rate limit, where an authenticated tool listing\nand each tool call count as a request.\n\n**Claude Code:**\n\n```\nclaude mcp add --transport http feelingsurf https://api.feelingsurf.fr/mcp --header \"Authorization: Bearer fsk_your_key\"\n```\n\n**Cursor and other clients configured with JSON:**\n\n```\n{\n  \"mcpServers\": {\n    \"feelingsurf\": {\n      \"url\": \"https://api.feelingsurf.fr/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer fsk_your_key\" }\n    }\n  }\n}\n```\n\n## Rate limits\n\nRequests per account per minute (fixed window): 20 on Free,\n60 on Premium. Every authenticated REST response, and every\nMCP tool listing or call made with a key, carries `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` and `X-RateLimit-Reset` (the unix epoch when the\nwindow resets). Over the limit, REST returns `429 rate_limited` with a\n`Retry-After` header (seconds); an MCP tool call returns a `rate_limited`\ntool error instead, with the same `Retry-After` header.\n\n## Errors\n\nResponses are JSON. Errors use a stable envelope —\n`{\"error\": {\"code\": \"…\", \"message\": \"…\"}}` — with a machine-readable,\nlocale-independent `code` (the `message` is English and may change):\n\n- `unauthorized` (401)\n- `premium_required` (403) — a site setting your plan does not include\n- `forbidden` (403) — a banned or disabled account\n- `not_found` (404)\n- `validation_error` (422)\n- `site_limit_reached` / `template_limit_reached` / `proxy_limit_reached` / `limit_reached` (403, on create once a quota is full)\n- `rate_limited` (429)\n- `server_error` (500)\n- `service_unavailable` (503) — temporary maintenance; carries a `Retry-After` header\n\n## Changelog\n\nAdditive changes (new endpoints, new response fields) ship as minor\nversions and never break an existing integration. Treat unknown response\nfields as forgiving: they may appear without a major bump.\n\n**1.5.0**\n\n- Cookies are no longer an action type. A site's or template's cookies\n  are now the `cookies` list, set on the browser session before\n  the visit begins and no longer counted against the action limit.\n\n**1.4.0**\n\n- Added the MCP server at `/mcp`: the API's endpoints as tools for AI assistants.\n\n**1.3.0**\n\n- The API is open to every plan: Free accounts get 20 requests per minute, Premium 60.\n- Site settings your plan does not include are returned as delivered (for example durations capped at your plan's maximum), not as stored.\n\n**1.2.0**\n\n- Added `/v1/proxies` and `/v1/proxies/{id}`: list, add, pause or resume,\n  and remove the proxies FeelingSurfViewer surfs through.\n\n**1.1.0**\n\n- Added `GET /v1/sites/deleted`: the sites you deleted within the last\n  90 days and can still restore, most recently deleted first.\n- Added `POST /v1/sites/{id}/restore`: brings a deleted site back with the\n  settings it had, subject to your free site slots.\n\n**1.0.0**\n\n- Initial release: account, sites, site templates, traffic sources,\n  labels, surf sessions, referrals, traffic availability and screenshots.\n"},"servers":[{"url":"https://api.feelingsurf.fr","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"System","description":"Liveness."},{"name":"Account","description":"The authenticated account."},{"name":"Sites","description":"The websites that receive automated visits."},{"name":"Site templates","description":"Reusable bundles of site settings."},{"name":"Traffic sources","description":"Custom referers a site can report visits as coming from."},{"name":"Proxies","description":"Your own proxies, which FeelingSurfViewer surfs through to run more sessions."},{"name":"Labels","description":"Labels for organizing your sites."},{"name":"Screenshots","description":"FeelingSurfViewer screenshots captured while surfing a site."},{"name":"Traffic availability","description":"Surfer-traffic availability by country (reference data)."}],"paths":{"/v1/ping":{"get":{"tags":["System"],"summary":"Liveness probe","description":"Unauthenticated health check.","security":[],"responses":{"200":{"description":"Service is up.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"}}}}}}}}},"/v1/me":{"get":{"tags":["Account"],"summary":"Get the authenticated account","description":"Account summary plus the earn-side rollup (credits earned today / last 7 days), active FeelingSurfViewer session count, and site-slot usage.\n","responses":{"200":{"description":"Account summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSummary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"tags":["Account"],"summary":"Update account settings","description":"Update non-sensitive account settings (e.g. `visit_own_sites`, `timezone`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSettingsUpdate"}}}},"responses":{"200":{"description":"The updated account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSummary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/v1/surf-sessions":{"get":{"tags":["Account"],"summary":"List your FeelingSurfViewer sessions","description":"Recent FeelingSurfViewer sessions on your account (the earning side), most recent first. These are your own FeelingSurfViewer instances and their IPs.\n","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"A page of surf sessions.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SurfSession"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/referrals":{"get":{"tags":["Account"],"summary":"Get your referral program","description":"Your shareable referral link, how many users you've referred, your commission rate, and commission credits earned.\n","responses":{"200":{"description":"The referral summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferralSummary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/traffic-availability":{"get":{"tags":["Traffic availability"],"summary":"Traffic availability by country","description":"Surfer-traffic availability per country, to guide a site's or template's location targeting. A country with `availability: none` has no surfers delivering visits, so targeting it yields nothing. A fixed reference dataset (~250 countries), returned in full — not paginated.\n","responses":{"200":{"description":"Every country with its availability.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TrafficAvailability"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/traffic-sources":{"get":{"tags":["Traffic sources"],"summary":"List your traffic sources","description":"Your own sources plus the shared/built-in ones, referenced by id from a site's `traffic_sources`.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"The caller's traffic sources.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TrafficSource"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Traffic sources"],"summary":"Create a traffic source","description":"Exceeding the per-account source cap returns 403 `limit_reached`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrafficSourceCreate"}}}},"responses":{"201":{"description":"The created traffic source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrafficSource"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/v1/traffic-sources/{id}":{"get":{"tags":["Traffic sources"],"summary":"Get one traffic source","description":"Works for your own sources and for the shared defaults (e.g. id 1, \"Direct visits\").","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"responses":{"200":{"description":"The traffic source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrafficSource"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Traffic sources"],"summary":"Update a traffic source","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrafficSourceUpdate"}}}},"responses":{"200":{"description":"The updated traffic source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrafficSource"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"tags":["Traffic sources"],"summary":"Delete a traffic source","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"responses":{"204":{"description":"The traffic source was deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/proxies":{"get":{"tags":["Proxies"],"summary":"List your proxies","description":"Every proxy on your account, oldest first.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"The caller's proxies.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Proxy"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Proxies"],"summary":"Add a proxy","description":"Registers a static proxy. FeelingSurfViewer checks it before any session goes through it, so a new proxy starts with `proven` false. Exceeding the per-account proxy cap returns 403 `proxy_limit_reached`; a host and port already on your account returns 422 `validation_error`.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProxyCreate"}}}},"responses":{"201":{"description":"The added proxy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Proxy"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/v1/proxies/{id}":{"get":{"tags":["Proxies"],"summary":"Get one proxy","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"responses":{"200":{"description":"The proxy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Proxy"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Proxies"],"summary":"Pause or resume a proxy","description":"Only `enabled` can change. Pausing or resuming clears `last_error` and its error history, and a resumed proxy is checked again at once, so this also brings back a proxy that was switched off after failing. To change the host, port or credentials, delete the proxy and add it again.\n","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProxyUpdate"}}}},"responses":{"200":{"description":"The updated proxy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Proxy"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"tags":["Proxies"],"summary":"Remove a proxy","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"responses":{"204":{"description":"The proxy was removed."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/labels":{"get":{"tags":["Labels"],"summary":"List your labels","description":"Referenced by id from a site's `labels`.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"The caller's labels.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Label"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Labels"],"summary":"Create a label","description":"Exceeding the per-account label cap returns 403 `limit_reached`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelCreate"}}}},"responses":{"201":{"description":"The created label.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Label"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/v1/labels/{id}":{"get":{"tags":["Labels"],"summary":"Get one label","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"responses":{"200":{"description":"The label.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Label"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Labels"],"summary":"Update a label","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelUpdate"}}}},"responses":{"200":{"description":"The updated label.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Label"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"tags":["Labels"],"summary":"Delete a label","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"responses":{"204":{"description":"The label was deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/sites":{"get":{"tags":["Sites"],"summary":"List your sites","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"The caller's (non-deleted) sites.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Site"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Sites"],"summary":"Create a site","description":"Only `url` is required; every other field defaults to a new-site configuration. Exceeding the account's slot count returns 403 `site_limit_reached`.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteCreate"}}}},"responses":{"201":{"description":"The created site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/v1/sites/deleted":{"get":{"tags":["Sites"],"summary":"List sites you can still restore","description":"Sites you deleted within the retention window, most recently deleted first. Sites removed because a plan downgrade reduced your slot count are not listed: those come back on their own when you upgrade again.\n","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"The caller's restorable sites.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DeletedSite"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/sites/{id}/restore":{"post":{"tags":["Sites"],"summary":"Restore a deleted site","description":"Brings back a site you deleted, with the settings it had. Geo targeting, blocked domains and actions your plan cannot use stay suspended and come back empty. Returns 404 once the retention window has passed, and 403 `site_limit_reached` when you have no free slot.\n","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"The restored site, with full configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/sites/{id}":{"get":{"tags":["Sites"],"summary":"Get one site","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"The site, with full configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Sites"],"summary":"Update a site","parameters":[{"$ref":"#/components/parameters/SiteId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteUpdate"}}}},"responses":{"200":{"description":"The updated site.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"tags":["Sites"],"summary":"Delete a site","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"204":{"description":"The site was deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/site-templates":{"get":{"tags":["Site templates"],"summary":"List your site templates","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"The caller's saved site templates.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SiteTemplate"}},"meta":{"$ref":"#/components/schemas/PageMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Site templates"],"summary":"Create a site template","description":"A template is a reusable bundle of site settings (no url/paused/labels). Only `name` is required; every other field defaults to a new-template configuration. Exceeding the per-account cap returns 403 `template_limit_reached`.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteTemplateCreate"}}}},"responses":{"201":{"description":"The created template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteTemplate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}}},"/v1/site-templates/{id}":{"get":{"tags":["Site templates"],"summary":"Get one site template","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"responses":{"200":{"description":"The template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteTemplate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Site templates"],"summary":"Update a site template","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteTemplateUpdate"}}}},"responses":{"200":{"description":"The updated template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteTemplate"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"},"400":{"$ref":"#/components/responses/BadRequest"},"422":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"tags":["Site templates"],"summary":"Delete a site template","parameters":[{"$ref":"#/components/parameters/ResourceId"}],"responses":{"204":{"description":"The template was deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/sites/{id}/screenshots":{"get":{"tags":["Screenshots"],"summary":"List a site's FeelingSurfViewer screenshots","description":"Screenshots captured while the app surfed the site. Returns the most recent (up to 24); this list is not paginated.","parameters":[{"$ref":"#/components/parameters/SiteId"}],"responses":{"200":{"description":"The site's screenshots.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Screenshot"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/screenshots/{uid}":{"get":{"tags":["Screenshots"],"summary":"Get a screenshot image","description":"Streams the image bytes. The id comes from a screenshot's `images[].url`.","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The screenshot image bytes (WebP).","content":{"image/webp":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/screenshots/{uid}/preview":{"get":{"tags":["Screenshots"],"summary":"Get a screenshot's blurred preview","parameters":[{"name":"uid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The blurred, lower-resolution preview image (WebP), for use as a thumbnail.","content":{"image/webp":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"404":{"$ref":"#/components/responses/NotFound"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key as a bearer token: `Authorization: Bearer fsk_...`."}},"parameters":{"SiteId":{"name":"id","in":"path","required":true,"description":"Site identifier.","schema":{"type":"integer","format":"int64"}},"ResourceId":{"name":"id","in":"path","required":true,"description":"Resource identifier.","schema":{"type":"integer","format":"int64"}},"Limit":{"name":"limit","in":"query","required":false,"description":"Page size (clamped to 1..200).","schema":{"type":"integer","default":50,"minimum":1,"maximum":200}},"Offset":{"name":"offset","in":"query","required":false,"description":"Number of items to skip.","schema":{"type":"integer","default":0,"minimum":0}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unauthorized","message":"Missing API key."}}}}},"Forbidden":{"description":"The request is not allowed. A banned or disabled account is rejected with `forbidden`; a site setting your plan does not include is rejected with `premium_required`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"forbidden","message":"API access is not enabled for this account."}}}}},"NotFound":{"description":"Resource not found (or not owned by the caller).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Site not found."}}}}},"BadRequest":{"description":"The request body was missing or not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","message":"Request body must be a JSON object."}}}}},"ValidationError":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"Field \"paused\" must be a boolean."}}}}},"RateLimited":{"description":"Too many requests — the per-account, per-minute rate limit was exceeded. Wait the number of seconds given in the `Retry-After` response header, then retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Rate limit exceeded."}}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","description":"Error details.","properties":{"code":{"type":"string","description":"Stable, locale-independent error code.","example":"unauthorized"},"message":{"type":"string","description":"Human-readable description (English)."}}}}},"AccountSummary":{"type":"object","example":{"id":98221,"username":"acme","plan":"premium","credits":5000,"earning":{"credits_today":0,"credits_last_7_days":0},"surfing":{"active_sessions":1},"site_slots":{"used":1,"max":12,"available":11},"visit_own_sites":false,"timezone":null,"currency":"eur","language":"en","newsletter":true,"invoice_emails":true,"subscription":{"active":true,"renews_at":"2026-12-25T12:00:00Z","ends_at":null}},"properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier."},"username":{"type":"string","description":"Your username."},"plan":{"type":"string","enum":["free","premium"],"description":"Membership plan."},"credits":{"type":"number","format":"float","description":"Current credit balance."},"earning":{"type":"object","description":"Credits earned by surfing.","properties":{"credits_today":{"type":"number","format":"float","description":"Credits earned by surfing today."},"credits_last_7_days":{"type":"number","format":"float","description":"Rolling sum over the last 7 days (today inclusive)."}}},"surfing":{"type":"object","description":"Your live surfing activity.","properties":{"active_sessions":{"type":"integer","description":"FeelingSurfViewer sessions active in the last few minutes."}}},"site_slots":{"type":"object","description":"Site-slot usage; create returns 403 once `used` reaches `max`.","properties":{"used":{"type":"integer","description":"Site slots currently in use (your number of sites)."},"max":{"type":"integer","description":"Total site slots your plan allows."},"available":{"type":"integer","description":"Unused site slots (max minus used)."}}},"visit_own_sites":{"type":"boolean","description":"Whether the app also surfs your own sites."},"timezone":{"type":"string","nullable":true,"description":"IANA timezone for active-hours scheduling; UTC is used when null."},"currency":{"type":"string","enum":["eur","usd"],"description":"Your billing currency."},"language":{"type":"string","enum":["en","fr","es","pt","ru","vi","id","tr","de","it","nl"],"description":"Your account language (ISO code)."},"newsletter":{"type":"boolean","description":"Newsletter email opt-in."},"invoice_emails":{"type":"boolean","description":"Whether invoice emails are sent."},"subscription":{"type":"object","description":"Premium subscription lifecycle.","properties":{"active":{"type":"boolean","description":"Whether an active premium subscription exists."},"renews_at":{"type":"string","format":"date-time","nullable":true,"description":"Next renewal date while active and not cancelled."},"ends_at":{"type":"string","format":"date-time","nullable":true,"description":"When premium access ends after a cancellation."}}}}},"ReferralSummary":{"type":"object","properties":{"referral_link":{"type":"string","description":"Shareable signup link that credits you for the referral."},"referred_count":{"type":"integer","description":"Number of (non-banned) users you referred."},"commission_percent":{"type":"integer","description":"Your referral commission: you get this percentage of every credit your referrals earn, forever (10 for Free accounts, 20 for Premium).\n"},"credits_earned":{"type":"object","description":"Credits earned from referrals.","properties":{"total":{"type":"number","format":"float","description":"All-time commission credits."},"today":{"type":"number","format":"float","description":"Commission credits earned today."},"last_7_days":{"type":"number","format":"float","description":"Rolling sum over the last 7 days (today inclusive)."}}}},"example":{"referral_link":"https://www.feelingsurf.fr/r/98220","referred_count":0,"commission_percent":20,"credits_earned":{"total":0,"today":0,"last_7_days":0}}},"AccountSettingsUpdate":{"type":"object","description":"Writable account settings. Provide at least one. Email, password and billing are intentionally not editable via the API.\n","properties":{"visit_own_sites":{"type":"boolean","description":"Whether the app also surfs your own sites."},"timezone":{"type":"string","nullable":true,"description":"An IANA timezone name (e.g. \"Europe/Paris\"), or null to clear (scheduling then uses UTC)."},"currency":{"type":"string","enum":["eur","usd"],"description":"Your billing currency."},"language":{"type":"string","enum":["en","fr","es","pt","ru","vi","id","tr","de","it","nl"],"description":"Your account language (ISO code)."},"newsletter":{"type":"boolean","description":"Newsletter email opt-in."},"invoice_emails":{"type":"boolean","description":"Whether invoice emails are sent."}}},"PageMeta":{"type":"object","description":"Pagination metadata for a list response.","properties":{"limit":{"type":"integer","description":"Page size applied (clamped)."},"offset":{"type":"integer","description":"Number of items skipped before this page."},"total":{"type":"integer","description":"Total number of items across all pages."}},"example":{"limit":50,"offset":0,"total":1}},"TrafficAvailability":{"type":"object","description":"Surfer-traffic availability for a country (guides location targeting).","properties":{"code":{"type":"string","description":"ISO 3166-1 alpha-2 country code."},"name":{"type":"string","description":"Localized country name."},"continent":{"type":"string","description":"ISO continent code (EU, AS, NA, AF, SA, OC, AN)."},"availability":{"type":"string","enum":["none","low","medium","high"],"description":"Relative volume of surfer traffic available in this country."}},"example":{"code":"AD","name":"Andorra","continent":"EU","availability":"low"}},"SurfSession":{"type":"object","description":"A FeelingSurfViewer session on your account. The IP is your own instance's IP. Sessions older than 7 days, and inactive ones that earned nothing, are not returned.\n","properties":{"ip_address":{"type":"string","description":"The IP address this session runs from."},"country_code":{"type":"string","nullable":true,"description":"ISO country code of the IP, or null if unknown."},"credits_earned":{"type":"number","format":"float","description":"Credits this session has earned."},"active":{"type":"boolean","description":"Whether the session was active in the last few minutes."},"last_activity_at":{"type":"string","format":"date-time","nullable":true,"description":"When the session was last active."},"app_version":{"type":"string","nullable":true,"description":"FeelingSurfViewer app version, e.g. \"2.5.0\"."},"app_version_outdated":{"type":"boolean","description":"Whether this app version is behind the current release."},"app_version_supported_until":{"type":"string","format":"date-time","nullable":true,"description":"When support for this app version ends, or null."},"low_quality_ip":{"type":"boolean","description":"The IP is classified low-quality (earns more slowly)."},"overused_ip":{"type":"boolean","description":"True when this IP has tripped one of your account's session limits — for example too many simultaneous sessions from the same IP or IP subnet, or your account-wide session cap. While flagged, further sessions from the IP are rejected, so it earns less until it drops back under the limit.\n"},"proxy_addresses":{"type":"array","items":{"type":"string"},"description":"The proxies this address is the exit of, as `host:port` (IPv6 hosts bracketed), so you can tell which of your proxies the session goes through. Several when proxies share an exit, empty when the session uses your own connection.\n"}},"example":{"ip_address":"203.0.113.42","country_code":"US","credits_earned":128.5,"active":true,"last_activity_at":"2026-06-25T09:04:36Z","app_version":"2.5.0","app_version_outdated":false,"app_version_supported_until":null,"proxy_addresses":[],"low_quality_ip":false,"overused_ip":false}},"DeletedSite":{"type":"object","description":"A site you deleted and can still restore. Deliberately leaner than `Site`: its configuration is suspended, and what comes back depends on your plan at restore time.\n","properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier."},"url":{"type":"string","description":"The website's URL."},"name":{"type":"string","nullable":true,"description":"Optional user-chosen name."},"total_visits":{"type":"integer","description":"Visits received before deletion."},"created_at":{"type":"string","format":"date-time","nullable":true,"description":"When the site was created (UTC)."},"deleted_at":{"type":"string","format":"date-time","nullable":true,"description":"When the site was deleted (UTC). Restorable from this date."}}},"Site":{"type":"object","description":"A website that receives automated visits. Relation-backed settings (geo targeting, blocked domains, actions, traffic sources, labels) are added alongside their write endpoints.\n","properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier."},"url":{"type":"string","description":"The website's URL."},"name":{"type":"string","nullable":true,"description":"Optional user-chosen name."},"paused":{"type":"boolean","description":"Whether the user has paused delivery."},"blocked":{"type":"boolean","description":"Whether the platform has blocked the site (e.g. dead link)."},"limit_reached":{"type":"boolean","description":"Whether a configured visit cap has been reached."},"devices":{"type":"object","description":"Device targeting split, as percentages summing to 100.","properties":{"desktop":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to desktop devices."},"mobile":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to mobile devices."},"tablet":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to tablet devices."}}},"visit_duration":{"type":"object","description":"Visit duration range, in seconds.","properties":{"min_seconds":{"type":"integer","minimum":10,"maximum":600,"description":"Shortest visit duration, in seconds."},"max_seconds":{"type":"integer","minimum":10,"maximum":600,"description":"Longest visit duration, in seconds."}}},"visit_distribution":{"type":"string","enum":["even","asap"],"description":"How visits are paced — \"even\" spreads them across the day, \"asap\" delivers as fast as possible."},"limits":{"type":"object","description":"Visit caps; null means no limit.","properties":{"hourly":{"type":"integer","minimum":1,"maximum":500,"description":"Max visits per hour — always at least 1 (an hourly cap is mandatory)."},"daily":{"type":"integer","nullable":true,"minimum":1,"maximum":10000,"description":"Max visits per day; null means no limit."},"total":{"type":"integer","nullable":true,"minimum":1,"maximum":500000,"description":"Max visits in total (lifetime); null means no limit."}}},"schedule":{"type":"object","description":"Active-hours scheduling.","properties":{"active_hours_mask":{"type":"string","nullable":true,"description":"A weekly on/off schedule: a 168-character string, one character per hour (24 hours × 7 days). Each is `1` (active that hour) or `0` (paused). The first character is Monday 00:00 and each next is the following hour, ending Sunday 23:00, in `time_zone`. `null` or all-`1`s means always on; at least one hour must be active, so an all-`0`s mask is rejected.\n"},"time_zone":{"type":"string","nullable":true,"description":"IANA time zone for the active-hours schedule (e.g. \"Europe/Paris\"); null uses your account timezone."}}},"traffic_quality":{"type":"object","description":"Traffic quality controls.","properties":{"low_quality_ips":{"type":"boolean","description":"Whether low-quality IPs (VPN/proxy/datacenter) may deliver visits to this site."},"ipv6":{"type":"boolean","description":"Whether IPv6 visitors may deliver visits to this site."}}},"browsing":{"type":"object","description":"Browsing options.","properties":{"max_sub_windows":{"type":"integer","minimum":0,"maximum":1,"description":"How many pop-up windows the visited site may open during a visit."}}},"traffic_sources":{"type":"array","items":{"type":"integer"},"description":"IDs of assigned traffic sources (resolve via GET /v1/traffic-sources)."},"labels":{"type":"array","items":{"type":"integer"},"description":"IDs of assigned labels (resolve via GET /v1/labels)."},"total_visits":{"type":"integer","description":"Lifetime visits delivered."},"created_at":{"type":"string","format":"date-time","nullable":true,"description":"When it was created (UTC), or null.","example":"2026-06-25T07:58:02Z"}},"example":{"id":2761880,"url":"https://example.com","name":"My landing page","paused":false,"blocked":false,"limit_reached":false,"devices":{"desktop":100,"mobile":0,"tablet":0},"visit_duration":{"min_seconds":10,"max_seconds":10},"visit_distribution":"even","limits":{"hourly":50,"daily":null,"total":null},"schedule":{"active_hours_mask":null,"time_zone":null},"traffic_quality":{"low_quality_ips":true,"ipv6":true},"browsing":{"max_sub_windows":0},"traffic_sources":[1],"labels":[],"total_visits":0,"created_at":"2026-06-25T07:58:02Z"}},"SiteAction":{"type":"object","description":"An action FeelingSurfViewer performs during the visit (click a link, scroll, etc.).","properties":{"type":{"type":"string","enum":["click","click_rss","click_sitemap","scroll","refresh"],"description":"What the app does — click a link, click an RSS or sitemap link, scroll, or refresh."},"ratio":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits that perform this action (default 100 = every visit)."},"element":{"type":"object","description":"Present for click-type actions.","properties":{"selection":{"type":"string","enum":["random","first","last"],"nullable":true,"description":"Which element to click when several match."},"type":{"type":"string","enum":["any","internal_link","external_link","link","button"],"nullable":true,"description":"What to click: any element, a same-domain link (`internal_link`), an off-domain link (`external_link`), any link (`link`), or a button.\n"},"url_filter":{"type":"string","nullable":true,"description":"Only click links whose URL contains this text; ignored when the element type is `button`."},"text_filter":{"type":"string","nullable":true,"description":"Only click elements whose visible text contains this."}}}}},"SiteCookie":{"type":"object","description":"A cookie set on the browser session before the visit begins.","required":["name","value"],"properties":{"name":{"type":"string","maxLength":256,"description":"Cookie name."},"value":{"type":"string","maxLength":256,"description":"Cookie value."},"domain":{"type":"string","nullable":true,"maxLength":256,"description":"Domain to set the cookie on (a leading dot is dropped; subdomains are covered either way); null sets it on the visited site's host."}}},"SiteActionInput":{"description":"A site action to apply. Same shape as SiteAction, but type and ratio are required.","allOf":[{"$ref":"#/components/schemas/SiteAction"},{"type":"object","required":["type","ratio"]}]},"SiteDetail":{"description":"A site with its full configuration (returned by GET /v1/sites/{id}).","allOf":[{"$ref":"#/components/schemas/Site"},{"type":"object","properties":{"location_targeting":{"type":"object","description":"Country/continent targeting.","properties":{"mode":{"type":"string","enum":["include","exclude"],"description":"Whether the locations list is an allow-list (include) or a block-list (exclude)."},"locations":{"type":"array","description":"The countries/continents to include or exclude.","items":{"type":"object","properties":{"type":{"type":"string","enum":["country","continent"],"description":"Whether code is a country or a continent."},"code":{"type":"string","description":"ISO country code (alpha-2) or continent code, per type."}}}}}},"blocked_domains":{"type":"object","description":"Domains blocked during visits.","properties":{"rate":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits to which domain blocking applies."},"domains":{"type":"array","items":{"type":"string"},"description":"Hostnames to block (e.g. `ads.example.com`; no scheme or path), one per entry. Up to 10."}}},"actions":{"type":"object","description":"Auto-actions performed during visits.","properties":{"window":{"type":"string","enum":["latest","first"],"description":"Which opened browser window the actions target — the most recent (latest) or the first (first)."},"items":{"type":"array","description":"The actions to perform.","items":{"$ref":"#/components/schemas/SiteAction"}}}},"cookies":{"type":"array","maxItems":10,"description":"Cookies set before each visit.","items":{"$ref":"#/components/schemas/SiteCookie"}},"visit_history":{"type":"object","description":"Recent visit counts.","properties":{"by_hour":{"type":"array","items":{"type":"integer"},"description":"Visits per hour over the last 24h, oldest first (last = current hour)."},"by_day":{"type":"array","items":{"type":"integer"},"description":"Visits per day over the last 8 days, oldest first (last = today)."}}}}}],"example":{"id":2761880,"url":"https://example.com","name":"My landing page","paused":false,"blocked":false,"limit_reached":false,"devices":{"desktop":100,"mobile":0,"tablet":0},"visit_duration":{"min_seconds":10,"max_seconds":10},"visit_distribution":"even","limits":{"hourly":50,"daily":null,"total":null},"schedule":{"active_hours_mask":null,"time_zone":null},"traffic_quality":{"low_quality_ips":true,"ipv6":true},"browsing":{"max_sub_windows":0},"traffic_sources":[1],"labels":[],"total_visits":0,"created_at":"2026-06-25T07:58:02Z","location_targeting":{"mode":"include","locations":[]},"blocked_domains":{"rate":100,"domains":[]},"actions":{"window":"latest","items":[]},"cookies":[],"visit_history":{"by_hour":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"by_day":[0,0,0,0,0,0,0,0]}}},"SiteUpdate":{"type":"object","description":"Writable fields of a site. For PATCH, provide at least one; only the fields present are changed.\n","properties":{"name":{"type":"string","nullable":true,"description":"User-chosen name; null clears it."},"paused":{"type":"boolean","description":"Pause or resume visit delivery."},"devices":{"type":"object","description":"Device targeting split, as percentages summing to 100.","properties":{"desktop":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to desktop devices."},"mobile":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to mobile devices."},"tablet":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to tablet devices."}}},"visit_duration":{"type":"object","description":"Visit duration range, in seconds.","properties":{"min_seconds":{"type":"integer","minimum":10,"maximum":600,"description":"Shortest visit duration, in seconds."},"max_seconds":{"type":"integer","minimum":10,"maximum":600,"description":"Longest visit duration, in seconds."}}},"visit_distribution":{"type":"string","enum":["even","asap"],"description":"How visits are paced — \"even\" spreads them across the day, \"asap\" delivers as fast as possible."},"limits":{"type":"object","description":"Visit caps; null means no limit.","properties":{"hourly":{"type":"integer","minimum":1,"maximum":500,"description":"Max visits per hour — always at least 1 (an hourly cap is mandatory)."},"daily":{"type":"integer","nullable":true,"minimum":1,"maximum":10000,"description":"Max visits per day; null means no limit."},"total":{"type":"integer","nullable":true,"minimum":1,"maximum":500000,"description":"Max visits in total (lifetime); null means no limit."}}},"schedule":{"type":"object","description":"Active-hours scheduling.","properties":{"active_hours_mask":{"type":"string","nullable":true,"description":"A weekly on/off schedule: a 168-character string, one character per hour (24 hours × 7 days). Each is `1` (active that hour) or `0` (paused). The first character is Monday 00:00 and each next is the following hour, ending Sunday 23:00, in `time_zone`. `null` or all-`1`s means always on; at least one hour must be active, so an all-`0`s mask is rejected.\n"},"time_zone":{"type":"string","nullable":true,"description":"IANA time zone for the active-hours schedule (e.g. \"Europe/Paris\"); null uses your account timezone."}}},"location_targeting":{"type":"object","description":"Country/continent targeting.","properties":{"mode":{"type":"string","enum":["include","exclude"],"description":"Whether the locations list is an allow-list (include) or a block-list (exclude)."},"locations":{"type":"array","description":"The countries/continents to include or exclude.","items":{"type":"object","properties":{"type":{"type":"string","enum":["country","continent"],"description":"Whether code is a country or a continent."},"code":{"type":"string","description":"ISO country code (alpha-2) or continent code, per type."}}}}}},"traffic_quality":{"type":"object","description":"Traffic quality controls.","properties":{"low_quality_ips":{"type":"boolean","description":"Whether low-quality IPs (VPN/proxy/datacenter) may deliver visits to this site."},"ipv6":{"type":"boolean","description":"Whether IPv6 visitors may deliver visits to this site."}}},"browsing":{"type":"object","description":"Browsing options.","properties":{"max_sub_windows":{"type":"integer","minimum":0,"maximum":1,"description":"How many pop-up windows the visited site may open during a visit."}}},"blocked_domains":{"type":"object","description":"Domains blocked during visits.","properties":{"rate":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits to which domain blocking applies."},"domains":{"type":"array","items":{"type":"string"},"description":"Hostnames to block (e.g. `ads.example.com`; no scheme or path), one per entry. Up to 10."}}},"actions":{"type":"object","description":"Auto-actions performed during visits.","properties":{"window":{"type":"string","enum":["latest","first"],"description":"Which opened browser window the actions target — the most recent (latest) or the first (first)."},"items":{"type":"array","description":"The actions to perform.","items":{"$ref":"#/components/schemas/SiteActionInput"}}}},"cookies":{"type":"array","maxItems":10,"description":"Cookies to set before each visit; replaces the stored list.","items":{"$ref":"#/components/schemas/SiteCookie"}},"traffic_sources":{"type":"array","items":{"type":"integer"},"description":"IDs of traffic sources to assign; an empty list assigns the default \"Direct visits\" source."},"labels":{"type":"array","items":{"type":"integer"},"description":"IDs of labels to assign."}}},"SiteCreate":{"description":"Fields for creating a site. Only `url` is required; all others default to a new-site configuration. When `template_id` is given, that template's settings seed the defaults and any field set explicitly here overrides them (shallow, per top-level key).\n","allOf":[{"$ref":"#/components/schemas/SiteUpdate"},{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"The website's URL."},"template_id":{"type":"integer","format":"int64","description":"A site template to apply as the base configuration. Must be one of your own templates; an unknown id returns 422. The template's settings become the defaults; any field you also send in this request overrides the template's value for that whole field (shallow merge, per top-level field).\n"}}}]},"SiteTemplate":{"type":"object","description":"A reusable bundle of site settings. Mirrors a site's configuration minus the site-only fields (url, paused, blocked, labels, visit history), plus a `name`.\n","properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier."},"name":{"type":"string","nullable":true,"description":"Template name."},"devices":{"type":"object","description":"Device targeting split, as percentages summing to 100.","properties":{"desktop":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to desktop devices."},"mobile":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to mobile devices."},"tablet":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to tablet devices."}}},"visit_duration":{"type":"object","description":"Visit duration range, in seconds.","properties":{"min_seconds":{"type":"integer","minimum":10,"maximum":600,"description":"Shortest visit duration, in seconds."},"max_seconds":{"type":"integer","minimum":10,"maximum":600,"description":"Longest visit duration, in seconds."}}},"visit_distribution":{"type":"string","enum":["even","asap"],"description":"How visits are paced — \"even\" spreads them across the day, \"asap\" delivers as fast as possible."},"limits":{"type":"object","description":"Visit caps; null means no limit.","properties":{"hourly":{"type":"integer","minimum":1,"maximum":500,"description":"Max visits per hour — always at least 1 (an hourly cap is mandatory)."},"daily":{"type":"integer","nullable":true,"minimum":1,"maximum":10000,"description":"Max visits per day; null means no limit."},"total":{"type":"integer","nullable":true,"minimum":1,"maximum":500000,"description":"Max visits in total (lifetime); null means no limit."}}},"schedule":{"type":"object","description":"Active-hours scheduling.","properties":{"active_hours_mask":{"type":"string","nullable":true,"description":"A weekly on/off schedule: a 168-character string, one character per hour (24 hours × 7 days). Each is `1` (active that hour) or `0` (paused). The first character is Monday 00:00 and each next is the following hour, ending Sunday 23:00, in `time_zone`. `null` or all-`1`s means always on; at least one hour must be active, so an all-`0`s mask is rejected.\n"},"time_zone":{"type":"string","nullable":true,"description":"IANA time zone for the active-hours schedule (e.g. \"Europe/Paris\"); null uses your account timezone."}}},"traffic_quality":{"type":"object","description":"Traffic quality controls.","properties":{"low_quality_ips":{"type":"boolean","description":"Whether low-quality IPs (VPN/proxy/datacenter) may deliver visits to this site."},"ipv6":{"type":"boolean","description":"Whether IPv6 visitors may deliver visits to this site."}}},"browsing":{"type":"object","description":"Browsing options.","properties":{"max_sub_windows":{"type":"integer","minimum":0,"maximum":1,"description":"How many pop-up windows the visited site may open during a visit."}}},"location_targeting":{"type":"object","description":"Country/continent targeting.","properties":{"mode":{"type":"string","enum":["include","exclude"],"description":"Whether the locations list is an allow-list (include) or a block-list (exclude)."},"locations":{"type":"array","description":"The countries/continents to include or exclude.","items":{"type":"object","properties":{"type":{"type":"string","enum":["country","continent"],"description":"Whether code is a country or a continent."},"code":{"type":"string","description":"ISO country code (alpha-2) or continent code, per type."}}}}}},"blocked_domains":{"type":"object","description":"Domains blocked during visits.","properties":{"rate":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits to which domain blocking applies."},"domains":{"type":"array","items":{"type":"string"},"description":"Hostnames to block (e.g. `ads.example.com`; no scheme or path), one per entry. Up to 10."}}},"actions":{"type":"object","description":"Auto-actions performed during visits.","properties":{"window":{"type":"string","enum":["latest","first"],"description":"Which opened browser window the actions target — the most recent (latest) or the first (first)."},"items":{"type":"array","description":"The actions to perform.","items":{"$ref":"#/components/schemas/SiteAction"}}}},"cookies":{"type":"array","maxItems":10,"description":"Cookies set before each visit.","items":{"$ref":"#/components/schemas/SiteCookie"}},"traffic_sources":{"type":"array","items":{"type":"integer"},"description":"IDs of assigned traffic sources (resolve via GET /v1/traffic-sources)."},"created_at":{"type":"string","format":"date-time","nullable":true,"description":"When it was created (UTC), or null."}},"example":{"id":2644,"name":"Default desktop","devices":{"desktop":100,"mobile":0,"tablet":0},"visit_duration":{"min_seconds":10,"max_seconds":10},"visit_distribution":"even","limits":{"hourly":50,"daily":null,"total":null},"schedule":{"active_hours_mask":null,"time_zone":null},"traffic_quality":{"low_quality_ips":true,"ipv6":true},"browsing":{"max_sub_windows":0},"location_targeting":{"mode":"include","locations":[]},"blocked_domains":{"rate":100,"domains":[]},"actions":{"window":"latest","items":[]},"cookies":[],"traffic_sources":[],"created_at":"2026-06-25T07:58:02Z"}},"SiteTemplateUpdate":{"type":"object","description":"Writable fields of a site template. For PATCH, provide at least one; only the fields present are changed.\n","properties":{"name":{"type":"string","nullable":true,"description":"Template name."},"devices":{"type":"object","description":"Device targeting split, as percentages summing to 100.","properties":{"desktop":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to desktop devices."},"mobile":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to mobile devices."},"tablet":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits sent to tablet devices."}}},"visit_duration":{"type":"object","description":"Visit duration range, in seconds.","properties":{"min_seconds":{"type":"integer","minimum":10,"maximum":600,"description":"Shortest visit duration, in seconds."},"max_seconds":{"type":"integer","minimum":10,"maximum":600,"description":"Longest visit duration, in seconds."}}},"visit_distribution":{"type":"string","enum":["even","asap"],"description":"How visits are paced — \"even\" spreads them across the day, \"asap\" delivers as fast as possible."},"limits":{"type":"object","description":"Visit caps; null means no limit.","properties":{"hourly":{"type":"integer","minimum":1,"maximum":500,"description":"Max visits per hour — always at least 1 (an hourly cap is mandatory)."},"daily":{"type":"integer","nullable":true,"minimum":1,"maximum":10000,"description":"Max visits per day; null means no limit."},"total":{"type":"integer","nullable":true,"minimum":1,"maximum":500000,"description":"Max visits in total (lifetime); null means no limit."}}},"schedule":{"type":"object","description":"Active-hours scheduling.","properties":{"active_hours_mask":{"type":"string","nullable":true,"description":"A weekly on/off schedule: a 168-character string, one character per hour (24 hours × 7 days). Each is `1` (active that hour) or `0` (paused). The first character is Monday 00:00 and each next is the following hour, ending Sunday 23:00, in `time_zone`. `null` or all-`1`s means always on; at least one hour must be active, so an all-`0`s mask is rejected.\n"},"time_zone":{"type":"string","nullable":true,"description":"IANA time zone for the active-hours schedule (e.g. \"Europe/Paris\"); null uses your account timezone."}}},"location_targeting":{"type":"object","description":"Country/continent targeting.","properties":{"mode":{"type":"string","enum":["include","exclude"],"description":"Whether the locations list is an allow-list (include) or a block-list (exclude)."},"locations":{"type":"array","description":"The countries/continents to include or exclude.","items":{"type":"object","properties":{"type":{"type":"string","enum":["country","continent"],"description":"Whether code is a country or a continent."},"code":{"type":"string","description":"ISO country code (alpha-2) or continent code, per type."}}}}}},"traffic_quality":{"type":"object","description":"Traffic quality controls.","properties":{"low_quality_ips":{"type":"boolean","description":"Whether low-quality IPs (VPN/proxy/datacenter) may deliver visits to this site."},"ipv6":{"type":"boolean","description":"Whether IPv6 visitors may deliver visits to this site."}}},"browsing":{"type":"object","description":"Browsing options.","properties":{"max_sub_windows":{"type":"integer","minimum":0,"maximum":1,"description":"How many pop-up windows the visited site may open during a visit."}}},"blocked_domains":{"type":"object","description":"Domains blocked during visits.","properties":{"rate":{"type":"integer","minimum":0,"maximum":100,"description":"Percentage of visits to which domain blocking applies."},"domains":{"type":"array","items":{"type":"string"},"description":"Hostnames to block (e.g. `ads.example.com`; no scheme or path), one per entry. Up to 10."}}},"actions":{"type":"object","description":"Auto-actions performed during visits.","properties":{"window":{"type":"string","enum":["latest","first"],"description":"Which opened browser window the actions target — the most recent (latest) or the first (first)."},"items":{"type":"array","description":"The actions to perform.","items":{"$ref":"#/components/schemas/SiteActionInput"}}}},"cookies":{"type":"array","maxItems":10,"description":"Cookies to set before each visit; replaces the stored list.","items":{"$ref":"#/components/schemas/SiteCookie"}},"traffic_sources":{"type":"array","items":{"type":"integer"},"description":"IDs of traffic sources to assign."}}},"SiteTemplateCreate":{"description":"Fields for creating a site template. Only `name` is required; all others default to a new-template configuration.\n","allOf":[{"$ref":"#/components/schemas/SiteTemplateUpdate"},{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Template name."}}}]},"TrafficSource":{"type":"object","description":"A traffic source (custom referer) a site can report visits as coming from.","properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier."},"name":{"type":"string","nullable":true,"description":"Name of the traffic source; a default is shown when unset."},"referer":{"type":"string","description":"The referer reported to the target site. Only the scheme is removed, so `https://www.google.com/` is stored as `www.google.com/`."},"premium":{"type":"boolean","description":"Whether this source is gated to Premium. Premium sources (your custom ones, plus some shared ones) are unassigned from your sites if your subscription lapses."},"custom":{"type":"boolean","description":"True for your own sources, false for shared/built-in ones. Independent of `premium`, since some shared sources are Premium-gated too."},"created_at":{"type":"string","format":"date-time","nullable":true,"description":"When it was created (UTC), or null."}},"example":{"id":166445,"name":"Newsletter","referer":"www.google.com/","premium":true,"custom":true,"created_at":"2026-06-25T07:58:02Z"}},"Label":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier."},"name":{"type":"string","description":"Label name."},"color":{"type":"string","enum":["#6610f2","#6f42c1","#d63384","#dc3545","#fd7e14","#ffc107","#198754","#20c997","#0dcaf0","#6c757d","#343a40"],"description":"Label color."},"description":{"type":"string","nullable":true,"description":"Optional notes."}},"example":{"id":1744,"name":"Campaign A","color":"#0dcaf0","description":"Q1 landing pages"}},"ScreenshotImage":{"type":"object","properties":{"url":{"type":"string","description":"URL to fetch the screenshot image."},"preview_url":{"type":"string","nullable":true,"description":"URL to fetch the blurred preview, or null."},"window":{"type":"integer","description":"Browser window index the image was captured from."},"width":{"type":"integer","description":"Image width in pixels."},"height":{"type":"integer","description":"Image height in pixels."},"delay_seconds":{"type":"integer","description":"Seconds into the visit when the image was captured."},"captured_at":{"type":"string","format":"date-time","description":"When the image was captured."}},"example":{"url":"https://api.feelingsurf.fr/v1/screenshots/c99c25613c8b956ba2b3b0f1f764ce83","preview_url":"https://api.feelingsurf.fr/v1/screenshots/c99c25613c8b956ba2b3b0f1f764ce83/preview","window":0,"width":1280,"height":720,"delay_seconds":5,"captured_at":"2026-06-25T09:00:00Z"}},"Screenshot":{"type":"object","description":"Screenshots captured during a single visit by the app.","properties":{"uid":{"type":"string","description":"Unique id for this visit's screenshot set."},"device":{"type":"string","enum":["desktop","mobile","tablet"],"description":"Device category the visit ran on."},"orientation":{"type":"string","enum":["landscape","portrait"],"description":"Screen orientation."},"country_code":{"type":"string","nullable":true,"description":"ISO country code of the visitor, or null."},"country_name":{"type":"string","nullable":true,"description":"Localized country name, or null."},"visit_duration_seconds":{"type":"integer","description":"How long the visit lasted, in seconds."},"images":{"type":"array","items":{"$ref":"#/components/schemas/ScreenshotImage"},"description":"The captured images, one per browser window."}},"example":{"uid":"2659c544-81ab-4841-912b-4005336a2a2e","device":"desktop","orientation":"landscape","country_code":"US","country_name":"United States","visit_duration_seconds":45,"images":[{"url":"https://api.feelingsurf.fr/v1/screenshots/c99c25613c8b956ba2b3b0f1f764ce83","preview_url":"https://api.feelingsurf.fr/v1/screenshots/c99c25613c8b956ba2b3b0f1f764ce83/preview","window":0,"width":1280,"height":720,"delay_seconds":5,"captured_at":"2026-06-25T09:00:00Z"}]}},"TrafficSourceCreate":{"type":"object","required":["name","referer"],"properties":{"name":{"type":"string","description":"Name of the traffic source; a default is shown when unset."},"referer":{"type":"string","description":"The referer to report to the target site (a domain or full URL, e.g. `https://www.google.com/`); the scheme is stripped on save."}}},"TrafficSourceUpdate":{"type":"object","description":"Provide at least one field.","properties":{"name":{"type":"string","description":"Name of the traffic source; a default is shown when unset."},"referer":{"type":"string","description":"The referer to report to the target site (a domain or full URL, e.g. `https://www.google.com/`); the scheme is stripped on save."}}},"Proxy":{"type":"object","description":"A proxy FeelingSurfViewer can surf through. Its password is never returned.","properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier."},"type":{"type":"string","enum":["http","https","socks5"],"description":"The proxy protocol."},"host":{"type":"string","description":"The proxy's public IP address."},"port":{"type":"integer","description":"The proxy's port."},"username":{"type":"string","nullable":true,"description":"The username sent to the proxy, or null."},"has_password":{"type":"boolean","description":"Whether a password is stored for the proxy."},"enabled":{"type":"boolean","description":"False when you paused the proxy, or when it was switched off after failing (see `last_error`)."},"proven":{"type":"boolean","description":"Whether a check through FeelingSurfViewer has confirmed the proxy works at least once. Sessions never go through an unproven proxy, but a proven one can still sit unused, for instance while `unanswered` is true or shortly after an error.\n"},"unanswered":{"type":"boolean","description":"The proxy failed its latest check and is set aside until a new check succeeds."},"visits":{"type":"integer","description":"Visits made through the proxy."},"credits_earnt":{"type":"number","format":"float","description":"Credits earned through the proxy."},"last_used_at":{"type":"string","format":"date-time","nullable":true,"description":"When a visit last went through the proxy (UTC), or null."},"last_error":{"type":"string","nullable":true,"description":"The latest error code for the proxy, e.g. `ERR_PROXY_CONNECTION_FAILED`, or null."},"last_error_at":{"type":"string","format":"date-time","nullable":true,"description":"When `last_error` happened (UTC), or null."},"created_at":{"type":"string","format":"date-time","description":"When the proxy was added (UTC)."}},"example":{"id":4821,"type":"http","host":"203.0.113.10","port":8080,"username":"exit-eu","has_password":true,"enabled":true,"proven":true,"unanswered":false,"visits":312,"credits_earnt":47.25,"last_used_at":"2026-09-12T09:14:03Z","last_error":null,"last_error_at":null,"created_at":"2026-09-10T18:22:47Z"}},"ProxyCreate":{"type":"object","required":["host","port"],"properties":{"type":{"type":"string","enum":["http","https","socks5"],"default":"http","description":"The proxy protocol, case-insensitive."},"host":{"type":"string","description":"The proxy's public IPv4 or IPv6 address. Hostnames and private addresses are rejected; IPv6 is stored in its compressed form."},"port":{"type":"integer","minimum":1,"maximum":65535,"description":"The proxy's port."},"username":{"type":"string","description":"Username for an `http` or `https` proxy. Not allowed with `socks5`."},"password":{"type":"string","description":"Password for an `http` or `https` proxy. Not allowed with `socks5`."}}},"ProxyUpdate":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean","description":"False pauses the proxy, true resumes it."}}},"LabelCreate":{"type":"object","required":["name","color"],"properties":{"name":{"type":"string","description":"Label name."},"color":{"type":"string","enum":["#6610f2","#6f42c1","#d63384","#dc3545","#fd7e14","#ffc107","#198754","#20c997","#0dcaf0","#6c757d","#343a40"],"description":"Label color."},"description":{"type":"string","nullable":true,"description":"Optional notes."}}},"LabelUpdate":{"type":"object","description":"Provide at least one field.","properties":{"name":{"type":"string","description":"Label name."},"color":{"type":"string","enum":["#6610f2","#6f42c1","#d63384","#dc3545","#fd7e14","#ffc107","#198754","#20c997","#0dcaf0","#6c757d","#343a40"],"description":"Label color."},"description":{"type":"string","nullable":true,"description":"Optional notes."}}}}}}