Documentation
Learn how to use Findably to create, optimize, and publish SEO & GEO content.
Findably creates, optimizes, and publishes articles that rank in traditional search engines (SEO) and generative AI platforms (GEO).
Here is how it works:
- Set up your business profile with your business details, audience, and competitors.
- Create an article by entering a topic or keyword. The AI pipeline generates the full article.
- Review & schedule the generated content, make edits, and pick a publish date.
- Publish to WordPress, Ghost, Wix Blog, or a custom webhook. On demand or on autopilot.
- Track AI visibility to see which AI crawlers visit your content and measure AI-driven referral traffic.
Each article uses one credit from your monthly plan. New accounts get a free trial with full access to all plan credits.
Your business profile drives all AI-generated content. It includes your business name, website URL, industry, description, and key selling points.
This context feeds into the first pipeline stage so the AI knows your brand voice, products, and positioning.
Set up your profile during onboarding or update it later from Settings → Business Profile. The AI auto-fill feature can generate a profile from your website URL.
Audience segments define who your content is for. Each segment has a label, description, and pain points. Add up to 7 segments manually, or use AI autofill. It asks three questions about your business and generates segments for you.
Competitors (up to 10) give the AI context on your competitive landscape. URLs are validated to confirm they point to real, active websites. You can toggle international competitors for global markets.
Strategic partnerships (up to 10) tell the AI about your business relationships, including technology partners, channel partners, and integration partners. This helps the AI reference your ecosystem naturally in generated content.
All three are configured under Settings → Audience, Competitors & Partners.
Every article runs through a multi-stage AI pipeline. Each stage has a specific job, and its output feeds into the next. One of those stages is a humanizer that smooths out the tone.
- Researcher analyzes the topic and produces a research brief with main themes, statistics, and source material.
- SEO Checklist generates target keywords, semantic keywords, heading structure, and on-page optimization guidelines.
- Outline Builder creates a detailed outline with headings, subheadings, and talking points for each section.
- Drafter writes the full article based on the outline, research, and SEO guidelines.
- Humanizer rewrites the draft to sound more natural and conversational, reducing AI detection signals.
- Editor reviews for clarity, flow, factual accuracy, and quality. Makes structural and stylistic edits.
- GEO Optimizer adds citations, statistics, quotations, and structured data that AI systems prefer to reference.
- Internal Linker adds contextual internal links to other published articles on your site for better SEO and site structure.
- Compliance Checker verifies the article covers all outline sections and preserves SEO keyword placements, patching any gaps.
- Meta Generator creates the SEO meta title, meta description, and Open Graph tags.
Pipeline progress is streamed in real time. If a stage hits the time limit, it picks up where it left off (up to 3 continuations per stage).
The Content Planner shows your articles on a calendar, organized by scheduled publish date.
Auto-scheduling is on by default. When the pipeline finishes, the next available date is assigned (one article per day, filling gaps in the calendar). You can turn this off in publishing settings.
Manual scheduling lets you pick a date for any unscheduled article. Click the calendar icon on an unscheduled article and choose a date. Only future dates are allowed.
Weekend exclusion can be toggled in publishing settings. When enabled, auto-scheduling skips Saturdays and Sundays.
WordPress
Connects via XML-RPC. You need your WordPress site URL, username, and an application password (not your login password). Generate one from your WordPress dashboard under Users → Profile → Application Passwords.
Articles are pushed as posts. Draft or publish mode depends on your auto-publish settings. If a featured image is enabled, it gets uploaded to your WordPress Media Library and set as the post's featured image with an AI-generated caption and alt text. Unsplash attribution is appended to the caption.
Ghost
Connects with your Ghost Admin API URL and an Admin API key. Find these in your Ghost dashboard under Settings → Integrations → Custom integration.
The Admin API URL is typically https://your-site.ghost.io. The API key is a hex string in the format id:secret. Featured images are set as the post's feature image with an AI-generated caption. For Unsplash images, photographer attribution is appended to the caption.
Wix Blog
Connects with a Wix API key and your Site ID. In your Wix dashboard, go to Settings → Headless Settings → Admin API Key → Manage API Key. Make sure it has Blog and Media permissions.
Your Site ID is the UUID in your Wix dashboard URL, right after /dashboard/(e.g., manage.wix.com/dashboard/your-site-id/home). Articles are converted from Markdown to Wix's Rich Content format and published as blog posts. Featured images are uploaded and set as the hero image. Note: the Wix Blog API does not support image captions natively, so captions are not included in Wix posts.
Webhook
Sends article data to any HTTP endpoint. Configure a webhook URL and optionally add auth and custom headers.
Authentication options:
- None. No authentication header.
- Bearer token. Sends
Authorization: Bearer <token>. - API key header. Sends a custom header with your API key value.
Requirements: Webhook URLs must use HTTPS and cannot point to private or internal networks (localhost, 10.x.x.x, 172.16-31.x.x, 192.168.x.x, etc.).
Custom headers: You can add up to 10 custom headers (e.g., for API versioning or routing). Headers with reserved names like Host or Content-Type are not allowed.
Payload format: Sends a POST request with Content-Type: application/json. See the Publish Webhook API Reference below for the full payload schema and code examples.
Your endpoint should return a 2xx status code to indicate success. Non-2xx responses are treated as failures.
Test on save: When you save a webhook, a lightweight test payload is sent to verify your endpoint is reachable. See the test payload format below.
Google Search Console
Connects via OAuth to import keyword ranking data. Click “Connect GSC” and authorize with your Google account, then select which property (URL or domain) to track.
Imported data includes queries, clicks, impressions, CTR, and average position. It shows up in a sortable, searchable table under GEO Insights → Keyword Rankings.
Both standard URL properties (e.g., https://example.com) and domain properties (e.g., sc-domain:example.com) are supported.
Findably's webhook sends each finished article as a JSON payload to a URL you provide. If your blog runs on WordPress, Ghost, or Wix, use the native integrations instead — they're simpler. Pick the webhook when:
- Your CMS isn't supported natively (Shopify, Webflow, Squarespace, Framer, Notion, custom sites, etc.)
- You want to route articles through an automation tool
- You have a developer who wants full control (see the developer reference below)
The easiest path: Zapier (or Make, n8n)
These “no-code” platforms receive Findably's webhook for you and push the article straight into your CMS. You don't need to run a server or write code — you just connect two apps together.
Where does the webhook URL come from?
Not from Findably. You generate it inside the tool that's going to receive your articles. Findably just stores that URL and posts your article to it.
Step-by-step with Zapier
- Create a Zap. In Zapier, click Create Zap. For the trigger, search Webhooks by Zapier and choose Catch Hook.
- Copy the webhook URL. Zapier gives you a URL that looks like
https://hooks.zapier.com/hooks/catch/12345/abcde/. This is your webhook. - Paste it into Findably. Go to Integrations → Add integration → Webhook. Give it a name, paste the Zapier URL, leave Authentication set to None, and click Save & Test. Findably sends a test ping — Zapier confirms the connection.
- Tell Zapier what to do with the article. Back in Zapier, add an action. Search for your CMS (WordPress, Shopify, Webflow, Ghost, Notion, Contentful — Zapier supports thousands of apps). Pick an action like Create Post or Create Blog Entry.
- Map Findably's fields to your CMS's fields (see the table below), then turn the Zap on. Every article you publish now flows to your CMS automatically.
Field mapping cheat sheet
| Findably field | What it is | Map to your CMS's... |
|---|---|---|
| title | Article title | Title |
| contentHtml | Ready-to-publish HTML | Body / Content (preferred for most CMSes) |
| content | Same content as Markdown | Body (use only if your CMS expects Markdown) |
| seo.metaDescription | Meta description | SEO description |
| seo.urlSlug | URL slug | Slug / Permalink |
| featuredImage.url | Hero image URL | Featured image |
| tags | Array of tags | Tags |
Common questions
Do I have to pay for Zapier?
Zapier has a free tier that works for low volumes (e.g. a few articles per month). For higher volumes or multi-step workflows, a paid plan is usually needed. Make and n8n are alternatives with different pricing and a self-hosted option (n8n).
Is the webhook secure?
Yes — all requests are sent over HTTPS. If you run your own endpoint, you can require a Bearer token so only requests carrying the right token are accepted. For Zapier, the URL itself is random and private enough.
What if my CMS is down when an article publishes?
Findably will report the push as failed. Open the article and click Publish again to retry.
Can I test without publishing a real article?
Yes. The Save & Test button on the integration form sends a small test ping to your URL. For a full payload test, publish a draft article to the integration.
Running into errors? Jump to Webhook Troubleshooting. Building a custom endpoint? Continue to the payload reference below.
When an article is published (manually or via auto-publish), a POST request is sent to your configured webhook URL. Your endpoint must return a 2xx status code within 30 seconds.
Test payload: On save, a test request is sent first to verify your endpoint. Return a 2xx response:
{
"test": true,
"timestamp": "2026-03-04T12:30:45.123Z"
}Publish payload: The full article payload sent when an article is published:
{
"title": "How to Optimize Your SEO Strategy in 2026: A Complete Guide",
"keyword": "seo optimization",
"content": "# Full Markdown Content\n\nThe complete article in markdown...",
"contentHtml": "<h1>Full Markdown Content</h1>\n<p>The complete article in HTML...</p>",
"tags": ["seo", "content-marketing", "optimization"],
"featuredImage": {
"url": "https://images.unsplash.com/photo-example?w=1080",
"alt": "Featured image for How to Optimize Your SEO Strategy",
"caption": "A well-structured content strategy drives long-term organic growth.",
"attribution": {
"photographerName": "Jane Doe",
"photographerUrl": "https://unsplash.com/@janedoe?utm_source=seo_geo&utm_medium=referral",
"unsplashUrl": "https://unsplash.com/?utm_source=seo_geo&utm_medium=referral"
}
},
"seo": {
"seoTitle": "How to Optimize Your SEO Strategy in 2026 | Expert Guide",
"metaDescription": "Learn proven SEO optimization techniques...",
"urlSlug": "how-to-optimize-seo-strategy",
"ogTitle": "How to Optimize Your SEO Strategy",
"ogDescription": "Expert guide to SEO optimization techniques..."
},
"frontmatter": {
"status": "published",
"language": "en",
"scheduledDate": "2026-03-05T00:00:00.000Z",
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-04T12:30:00.000Z"
},
"metadata": {
"articleId": "550e8400-e29b-41d4-a716-446655440000",
"pipelineRunId": "660e8400-e29b-41d4-a716-446655440001",
"publishedAt": "2026-03-04T12:30:45.123Z",
"source": "seo-geo-platform"
}
}| Field | Type | Description |
|---|---|---|
| title | string | The blog title. An SEO-optimized headline generated by the pipeline. Use this as the published post title. |
| keyword | string | The target SEO keyword (focus keyphrase) for the article. Use this for SEO plugin configuration (e.g. Yoast focus keyword). |
| content | string | Full article body in Markdown format. |
| contentHtml | string | Full article body converted to HTML. Use this if your CMS expects HTML. |
| tags | string[] | absent | Array of tag names assigned to the article. Only present if the article has tags. |
| featuredImage | object | absent | Only present if a featured image exists. Contains url (image URL), alt (alt text), caption (a short AI-generated description suitable for display below the image), and optionally attribution (photographer name, URL, and Unsplash link). Only present for Unsplash-sourced images, not AI-generated ones. |
| seo | object | absent | SEO metadata generated by the pipeline. Contains seoTitle, metaDescription, urlSlug, ogTitle, and ogDescription. |
| frontmatter.status | string | Article status at time of push (e.g., published, scheduled, finished). |
| frontmatter.language | string | Language code (e.g., en). |
| frontmatter.scheduledDate | string | null | ISO 8601 scheduled publish date, or null if unscheduled. |
| frontmatter.createdAt | string | ISO 8601 timestamp when the article was created. |
| frontmatter.updatedAt | string | ISO 8601 timestamp of the last article update. |
| metadata.articleId | string (UUID) | Unique identifier for the article. |
| metadata.pipelineRunId | string | null | UUID of the pipeline run that generated this article, or null. |
| metadata.publishedAt | string | ISO 8601 timestamp when the webhook was fired. |
| metadata.source | string | Always seo-geo-platform. |
Headers sent with every request:
Content-Type: application/jsonis always included.Authorization: Bearer <token>is included when auth type is “Bearer token.”- Any custom headers you configured are included as-is.
Example, cURL:
curl -X POST https://your-api.com/webhook/articles \
-H "Content-Type: application/json" \
-d '{
"title": "My Article",
"keyword": "seo tips",
"content": "# Heading\n\nMarkdown body...",
"contentHtml": "<h1>Heading</h1><p>HTML body...</p>",
"tags": ["seo", "content-marketing"],
"frontmatter": {
"status": "published",
"language": "en",
"scheduledDate": null,
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-04T12:30:00.000Z"
},
"metadata": {
"articleId": "abc-123",
"pipelineRunId": "def-456",
"publishedAt": "2026-03-04T12:30:45.123Z",
"source": "seo-geo-platform"
}
}'Example, Express.js receiver:
app.post("/webhook/articles", (req, res) => {
const { test, title, contentHtml, tags, frontmatter, metadata } = req.body;
// Handle test ping
if (test) {
return res.json({ ok: true });
}
// Process the article
console.log("Received article:", title);
console.log("HTML content length:", contentHtml.length);
console.log("Tags:", tags || []);
console.log("Status:", frontmatter.status);
console.log("Published at:", metadata.publishedAt);
// Save to your database, CMS, or file system...
res.json({ ok: true });
});Timeouts & retries: The test payload times out after 10 seconds. Publish payloads time out after 30 seconds. There are no automatic retries. If your endpoint fails, re-push the article from its detail page.
Duplicate protection: The same article cannot be pushed to the same webhook more than once per minute.
Connection Timeout / Network Error
Findably cannot reach your webhook URL at all. The request never arrives at your server.
Common causes:
- Wrong URL. Check for typos, wrong TLD (e.g.
.comvs.co), or a missing path segment. - Site is down. Your hosting provider may have an outage or your deployment failed.
- DNS not configured. If you recently added a custom domain, DNS records may not point to your host yet.
- Firewall or WAF blocking. Some hosting setups block incoming POST requests or requests from unfamiliar IPs.
How to verify: Open your webhook URL in a browser. Any response (even 405 Method Not Allowed) means the server is reachable. If the page doesn't load at all, the problem is with your hosting or domain.
Webhook Returned 401 (Unauthorized)
Your server was reached, but it rejected the authentication token.
Common causes:
- The Bearer token in Findably doesn't match the secret in your server's environment variables. Even a trailing space or newline causes a mismatch.
- You updated the secret in your hosting dashboard but forgot to redeploy. Most platforms (Vercel, Netlify, etc.) need a new deployment for env var changes to take effect.
- Your code reads from one variable name (e.g.
FINDABLY_WEBHOOK_SECRET) but the value is stored under a different name in your hosting dashboard.
How to fix: Pick a secure token (e.g. openssl rand -base64 32), set it in both Findably's webhook configuration and your hosting environment variables, then redeploy your application.
Webhook Returned 400 (Bad Request)
Auth passed, but your server couldn't process the payload. This usually means your webhook code expects a different field structure than what Findably sends.
How to fix: Check your server logs for the specific validation error, then compare against the Publish Webhook API Reference above to see the exact payload format Findably sends.
Webhook Returned 500 (Internal Server Error)
Your server received the request but crashed while processing it. This is a config or code issue on your end.
Common causes:
- Expired GitHub token. If your webhook commits posts to GitHub, the Personal Access Token (PAT) may have expired. Your logs will show "Bad credentials".
- Insufficient token permissions. Fine-grained GitHub tokens need Contents: Read and write on the target repository.
- Wrong repository name, missing branch, or a missing dependency in production.
How to fix: Check your server logs first. The 500 error is generic, and your logs will have the actual error message.
- Vercel: Project dashboard → Logs tab → filter for your webhook route.
- Netlify: Functions tab → select your function → view logs.
- AWS: CloudWatch logs for your Lambda/API Gateway.
Fixing an Expired GitHub Token
If your logs show "Bad credentials", your GitHub PAT has expired or been revoked. To fix it:
- Go to GitHub → Settings → Developer settings → Personal access tokens.
- Generate a new token (or regenerate the existing one). For fine-grained tokens, select the repository and grant Contents: Read and write.
- Update the token in your hosting platform's environment variables.
- Redeploy your application.
- Test the webhook from Findably.
GitHub tokens expire. Set a calendar reminder to rotate yours before that happens.
When auto-publishing is on, a daily cron job checks for articles whose scheduled date has arrived and pushes them to your active integrations (WordPress, Ghost, Wix Blog, or webhook).
Publish modes:
- Draft. Articles are created as drafts on your CMS. You review and publish manually.
- Publish. Articles go live immediately when pushed.
Configure auto-publishing under Settings → Publishing. It works independently from auto-scheduling.
If no integrations are connected, completed articles are marked as “finished” instead of “scheduled.” You can still manually push them later from the article detail page.
AI Visibility tracks when AI crawlers (GPTBot, PerplexityBot, ClaudeBot, etc.) visit your site, and measures referral traffic from ChatGPT, Perplexity, and Gemini.
Three data collection tiers:
- GSC AI Referrals filters your Google Search Console data for AI referrer domains. Enable the toggle on your GSC integration. Requires an active GSC connection.
- Cloudflare Analytics connects to your Cloudflare zone to query bot user-agent data. Requires a Cloudflare API token with Analytics read permissions and your zone ID.
- WordPress Plugin. Install the Findably Tracker plugin on your WordPress site. It detects AI bot visits via user-agent matching and reports them to your dashboard. Events are queued locally and flushed every 5 minutes via WP-Cron.
The dashboard (under GEO Insights → AI Visibility) shows summary cards, a crawl activity chart, top crawled pages, and referral traffic breakdowns across three tabs.
Notifications: You get notified on first-time crawls, new platform detections, and other events. Check the notification bell in the header for unread alerts.
External sources (like the WordPress plugin) use this webhook to report AI bot crawl events to your dashboard.
Endpoint: POST /api/webhooks/tracker
Authentication: Authorization: Bearer sk_live_...
Content-Type: application/json
Rate Limit: 100 requests per minute per API key.
API Key: Generate one from the AI Visibility data source setup. The key is shown once and starts with sk_live_. One active key per workspace. Revoke and regenerate any time.
Request body, batch format (recommended):
{
"events": [
{
"botName": "GPTBot",
"pageUrl": "https://example.com/blog/my-article",
"timestamp": "2026-03-03T14:30:00.000Z",
"httpStatus": 200
},
{
"botName": "ClaudeBot",
"pageUrl": "https://example.com/blog/another-article",
"timestamp": "2026-03-03T14:35:00.000Z",
"httpStatus": 200
}
]
}Single event format is also accepted:
{
"botName": "PerplexityBot",
"pageUrl": "https://example.com/blog/my-article",
"timestamp": "2026-03-03T15:00:00.000Z",
"httpStatus": 200
}| Field | Type | Description |
|---|---|---|
| botName | string (enum) | One of: GPTBot, ChatGPT-User, PerplexityBot, ClaudeBot, Google-Extended, Applebot-Extended |
| pageUrl | string (URL) | Full URL of the crawled page. Max 2048 characters. |
| timestamp | string (ISO 8601) | When the crawl occurred. Must include timezone offset. |
| httpStatus | integer | HTTP status code returned to the bot (100–599). |
| test | boolean (optional) | Set to true to validate authentication without storing any events. |
Success response (200):
{
"success": true,
"received": 2,
"stored": 2,
"duplicates": 0
}Deduplication: Events are deduplicated by workspace, page URL, bot name, and timestamp (truncated to the hour). Duplicate events are silently ignored and reported in the duplicates count.
Error responses:
401. Missing, invalid, or revoked API key.400. Validation failed (invalid JSON or schema errors).429. Rate limit exceeded. Check theRetry-Afterheader.500. Internal error storing events.
Example, cURL:
curl -X POST https://your-app.vercel.app/api/webhooks/tracker \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"botName": "GPTBot",
"pageUrl": "https://example.com/blog/my-article",
"timestamp": "2026-03-03T14:30:00.000Z",
"httpStatus": 200
}'Example, Node.js:
app.post("/report-crawl", async (req, res) => {
const response = await fetch(
"https://your-app.vercel.app/api/webhooks/tracker",
{
method: "POST",
headers: {
"Authorization": "Bearer sk_live_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
events: [
{
botName: req.body.botName,
pageUrl: req.body.pageUrl,
timestamp: new Date().toISOString(),
httpStatus: 200,
},
],
}),
}
);
const data = await response.json();
res.json(data);
});