# Capture leads and subscribers (/docs/capture-leads)
Mark records a lead or a newsletter signup through two public endpoints:
`POST /leads` and `POST /subscribe`. Your site keeps its own form, design,
and copy — these endpoints are the contract your form's submit handler
posts to.
Both endpoints live on the API host (for example
`https://api.mark.lifewithdata.org`) and take a JSON body over HTTPS. Life
With Data gives you the `key` value below; it identifies your workspace and
is safe to include in client-side code.
## Before you integrate [#before-you-integrate]
* **Your public form key.** Every request body includes `key`, the value
Life With Data gave you for your workspace.
* **Your site's domain on the allowlist.** Requests from a browser must come
from an origin Life With Data has added to your workspace. An origin
that isn't allowed gets refused.
* **A consent checkbox.** Both endpoints require an explicit consent
acknowledgment before they record anything.
## POST /leads [#post-leads]
Call this when a visitor submits your lead form.
```
POST /leads
Content-Type: application/json
```
| Field | Type | Required | Notes |
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `key` | string | Yes | Your public form key. |
| `email` | string | Yes | The visitor's email. |
| `consent` | `true` | Yes | Must be exactly `true`. |
| `consentVersion` | string | Yes | The version of the consent text you showed. |
| `pageUrl` | string | Yes | The page the form was on, including any `utm_*` query parameters. |
| `renderedAt` | number | Yes | Unix time in milliseconds when the form first rendered. Used to reject a submit that happens too fast to be human. |
| `name` | string | No | |
| `phone` | string | No | |
| `message` | string | No | The visitor's message. |
| `personaId` | string | No | An id from your Mark workspace, if your form asks which persona the visitor matches. |
| `answers` | object | No | Any other named field on your form: a flat object of strings, numbers, or booleans, up to 40 keys. |
| `referrer` | string | No | `document.referrer` at submit time. |
| `distinctId` | string | No | Your analytics visitor id, if you have one. |
| `fbclid`, `gclid` | string | No | Ad click ids from the page URL. |
| `honeypot` | string | No | Leave any hidden honeypot input empty; a filled value marks the submission as a bot. |
A successful submission returns:
```json
{ "leadId": "..." }
```
Show your own thank-you message — Mark doesn't redirect anywhere. If the
same email submits again while their lead is still new, Mark attaches the
new message to that same lead instead of creating a second one.
## POST /subscribe [#post-subscribe]
Call this for a newsletter or marketing-only signup, where you don't want a
sales lead created. It takes the same fields as `POST /leads` except
`phone`, `message`, `personaId`, and `answers`, and returns:
```json
{ "contactId": "..." }
```
## Responses [#responses]
| Status | Meaning |
| ------------------------ | ---------------------------------------------------------------------------------------------------------- |
| `201` | Recorded. The body has `leadId` or `contactId`. |
| `200` with an empty body | The submission looked automated (a filled honeypot or an impossibly fast submit) and nothing was recorded. |
| `400` | The body is missing a required field, `consent` isn't `true`, or `answers` doesn't fit the shape above. |
| `403` | The request's origin isn't on your workspace's allowlist. |
| `404` | The `key` doesn't match a workspace. |
| `429` | Too many requests from the same address; try again shortly. |
## Example [#example]
```bash
curl -X POST https://api.mark.lifewithdata.org/leads \
-H "Content-Type: application/json" \
-d '{
"key": "your-public-form-key",
"email": "visitor@example.com",
"message": "Interested in a quote",
"consent": true,
"consentVersion": "1.0",
"pageUrl": "https://your-site.com/contact?utm_source=instagram",
"renderedAt": 1735000000000
}'
```
# Check site readiness (/docs/check-site-readiness)
The **Site readiness** card lists the agent-parseability issues on one
website: the things that make it harder for AI answer engines to read and
use the site. The card names the site's domain, which opens the site in a
new tab, and shows **Scanned** with the date of the last scan.
## Find the card [#find-the-card]
* **Home** shows the card for your primary website only, the one with the
**Primary** badge in **Settings** > **Websites**. Select **See more** on the
card to open that site's page.
* **Analysis** > **Website** opens your primary site's page. Select the
site's name at the top to switch to another site.
There the same issues sit inside the [site audit](/docs/site-audit) report,
under the **AI readiness** category, scored with the rest of the audit.
If you haven't added a website yet, **Home** shows "No website yet. Add one to
see how ready it is for AI answer engines." Select **Add one** to go to
**Websites**.
## Run the first scan [#run-the-first-scan]
A site with no report yet shows "No site readiness report yet for", then the
site's domain. Select **Run scan** to check it.
## Read the issues [#read-the-issues]
If the scan finds issues, the card shows a summary such as "3 issues found",
with a count for each tier that has issues:
* **essential**: an agent can't work around it. Fix these first.
* **recommended**: lower stakes than essential.
* **bonus**: lowest stakes.
Select the summary to show or hide the list. It starts hidden.
Each issue in the list shows its name, its tier as a badge, what the scan
found, and what to change. Essential issues come first.
If the scan finds nothing, the card says "No agent-parseability issues
found."
## Scan again [#scan-again]
After you change the site, check it again:
1. Go to **Home**.
2. Select **Scan again**. The button reads **Scanning…** while it works.
On a site's page, select **Check now** on the **AI readiness** tile of the
site audit report instead.
When it finishes, the card says "Updated just now." and shows the new
results. If the scan can't run, the card says "Could not check right now.
Try again in a few minutes." and keeps the last results on screen.
# Connect social accounts (/docs/connect-social-accounts)
Connect your Instagram or X account on the **Channels** page so Mark can
schedule and publish posts there once they're approved. Open it from the
sidebar: **Content** > **Channels**.
The page has four groups: **Social**, **Website**, **Email**, and **Ads**.
Each channel gets a card with its connection status and its publishing
rules.
## Connect Instagram or X [#connect-instagram-or-x]
1. In **Social**, find the **Instagram** or **X** card and select
**Connect**.
2. Sign in to the platform and authorize Mark. You leave Mark for this step.
3. The platform sends you back to **Channels**, which shows "Instagram is
connected." or "X is connected."
The connected card shows the account handle, like `@verdella`. If
authorization doesn't finish, **Channels** shows an error instead and the
card stays **Not connected**. Select **Connect** to try again.
To switch accounts or repair a connection, select **Reconnect** on a
connected card. It runs the same authorization again.
## What each card's status means [#what-each-cards-status-means]
* **A handle, like `@verdella`.** The account is connected and Mark can
publish to it after approval.
* **Not connected**, with a **Connect** button. Select **Connect** to
connect the account.
* **Connecting is not set up in this environment.** This copy of Mark can't
connect social accounts. Ask Life With Data.
* **Coming soon** badge. This channel isn't self-serve yet. Only Instagram
and X connect from **Channels** today.
## Set publishing rules [#set-publishing-rules]
Open **Publishing rules** on a channel card to set how posts on that channel
get approved and how often they go out. Select **Save** after a change. Each
channel has its own rules.
* **Active**: whether Mark writes for this channel.
* **Approval policy**: **Required** sends every post to a reviewer before it
can be scheduled. **Auto** approves a post the moment it's ready, as long
as it has no claim flags, so no reviewer sees it first.
* **Approver role**: which role should approve posts on this channel,
**Member** or **Approver**. It's a note for your team and for Mark. It
doesn't limit who can approve.
* **Cadence count** and **Cadence period**: how many posts you want per week
or per month. **Calendar** shows progress against it, like "2 of 3 this
week". Leave the period on **No cadence** to skip it.
* **ICP fit**: whether this channel reaches your ideal customer,
**Confirmed** or **Uncertain**.
* **Notes**: anything Mark should keep in mind for this channel.
Social cards also have a **Bio destination**, the page your profile link
points to. Once it's set, the card shows a **Profile short URL** to paste
into the profile.
For why review comes first and who approves, see
[Review and approval](/docs/concepts/review-and-approval).
## Website rules [#website-rules]
The **Website** card covers the blog posts, FAQs, landing pages, and
competitor pages Mark writes for your sites. Open **Rules by content type**
to set the same rules for each content type separately. Select **Manage
websites** to go to **Websites**, where you add and manage the sites
themselves.
## Other connections [#other-connections]
Channels only connects social accounts. Google, Drive, and other services
connect under **Settings** > **Connections**.
# Create and submit a post (/docs/create-and-submit-a-post)
Write a post in the post editor, then submit it for review. For what each
status means, see
[Review and approval](/docs/concepts/review-and-approval).
Open your posts from the sidebar: **Content** > **Posts**. The list shows
every post in your workspace, newest change first, with
its **Channel**, **Status**, and **Last updated** time. If there are none
yet, it says "No posts yet. Start one with New post."
## Start a draft [#start-a-draft]
1. Select **New post**.
2. Pick a **Channel**.
3. Select **Create draft**. The post opens in the editor.
To switch channels later, change **Channel** in the editor. Fields that the
new channel doesn't use are cleared.
## Write the post [#write-the-post]
The editor shows the post's status at the top, next to **Channel**.
* **Body**: write the post. The counter shows its length against the
channel's limit, like 180 / 2200 on Instagram. Over the limit, the counter
turns red and the editor says "The body is over the 2200 character limit
for instagram." **Save draft** and **Submit for review** can't be selected
until you shorten it.
* **Angle**: pick an angle for the post, or leave it on **No angle**. Angles
come from **Strategy** > **Angles**. For setting them up, see **Create and
assign angles**.
* **Scheduled time**: pick the date and time the post should go out, in your
timezone. Leave it empty if you don't know yet.
* **Media**: select **Attach media**. In the dialog, drag files in to upload
them, or select files this workspace already uploaded. Select **Done**.
To take a file off the post, select the remove button (**×**) in the corner
of its thumbnail. If uploading isn't available in your workspace, the
dialog says so. Files that are already uploaded are still there to pick.
* **Details**: fields for the channel you picked, so they change when the
channel does. Fields marked with **\*** are required. List fields read "One
value per line". Put each value on its own line.
The preview beside the editor updates as you type. Instagram and X show the
post as it will look on the channel. Other channels say there's no preview.
## Save or submit [#save-or-submit]
* Select **Save draft** to keep your work. **Saved** appears next to the
buttons. The post stays a draft and stays editable.
* Select **Submit for review** when it's ready. Mark saves the post first,
then sends it to review. The status changes to in review. If the channel's
**Approval policy** is **Auto**, Mark can approve it right away instead.
See [Connect social accounts](/docs/connect-social-accounts).
If something needs fixing, the editor says so above the buttons. "Some fields
need attention." means a **Details** field has a problem. The message shows
under that field.
Once you submit, the post is locked. Trying to save it shows "This post is
locked for review. It can be edited again after a reviewer requests changes."
To see what happens next, read
[Review and approve posts](/docs/review-and-approve-posts).
## Edit a post later [#edit-a-post-later]
Select a post in the **Posts** list to open it in the editor. Drafts and
posts with changes requested stay editable. Other posts are locked. After a
reviewer requests changes, fix the post and select **Submit for review**
again.
# Read the Home Google cards (/docs/home-google-cards)
Home shows two Google cards side by side. **Organic search** comes from
Google Search Console. **Website activity** comes from Google Analytics
(GA4). Each card covers the last 28 settled days and compares them with the
28 days before.
The cards appear only when the workspace has a website. With no website,
Home says **No website yet. Add one** instead.
## Read the numbers [#read-the-numbers]
**Organic search** shows:
* **Clicks**: how many times someone clicked through to your site from
Google Search.
* **Impressions**: how many times your site appeared in Google Search
results.
* **Average position**: where your site ranked on average. Lower is better.
* **Top searches**: the searches that brought the most clicks.
**Website activity** shows:
* **Sessions**: visits to your site.
* **Users**: people who visited.
* **Conversions**: key events counted in Analytics. This figure shows only
once the property has key events to count.
* **Top landing pages**: the pages where the most sessions started.
Under each figure is its change against the previous 28 days. Counts show a
percent change. **Average position** shows what it was before. Green is good
news and red is bad news, so a lower average position shows green. The
change is blank when there is no earlier figure to compare against, or the
earlier count was zero.
The card header shows the date range. Hover the info icon next to it to see
the range it's compared with and which Google product the numbers come from.
## Why the newest days are missing [#why-the-newest-days-are-missing]
Google finishes counting a day a few days after it ends. Search Console data
arrives about 3 days late and Analytics data about 2 days late. The cards
leave those days out, so a day Google hasn't finished counting doesn't look
like a drop.
Mark also keeps the numbers for up to an hour, so a card can be up to an hour
behind Google.
## When a card shows no numbers [#when-a-card-shows-no-numbers]
You manage the Google connection in **Settings** > **Connections** >
**Google**. Most fixes happen there.
If Google isn't connected, the card says **Not connected yet.** Select
**Connect Google**. One sign-in covers Analytics and Search Console.
Otherwise the card shows one sentence instead of numbers:
| The card says | What to do |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Choose the Search Console property Mark should read. | On the Google connection, choose the Search Console property and select **Save**. |
| Choose the Google Analytics property Mark should read. | On the Google connection, choose the Analytics property and select **Save**. |
| Connect Google to see numbers here. | Connect Google from the Google connection. |
| Reconnect Google and allow this access to see numbers here. | Reconnect Google and allow the access it asks for. |
| Google access was revoked or expired. Reconnect Google. | Reconnect Google. |
| Google did not answer. Reload to try again. | Reload the page. |
When the Google connection needs something from you, the card shows its
status and a **Next:** line with the step to take. Select **View connection**
to open it.
If Home can't read your connections, it says "Search Console and Analytics
status could not load. Reload to try again." Reload the page.
## See more detail [#see-more-detail]
On a connected **Organic search** card, select **More details** to open
**Performance** at the **Search** section. **Website activity** has no detail
link.
# Overview (/docs)
Mark is Life With Data's internal platform for running GTM and ops/automation
work for clients. Anthony and Hannah operate it; it is not a public,
self-serve product yet.
The near-term shape is a product-backed service: Life With Data runs Mark and
delivers the work — content, social, demand capture, and eventually ads.
Clients get a hosted workspace with a database, scheduled workflows, and a
human in the loop, not a pile of disconnected automations.
## Where to go next [#where-to-go-next]
* [Concepts](/docs/concepts) — the mental model: tenants, drafts, and the
review loop.
* [Install the site tag](/docs/install-the-site-tag) — the one script that
wires a client's existing site to Mark for attribution.
* [Connect social accounts](/docs/connect-social-accounts) — connect
Instagram or X from Channels and set each channel's publishing rules.
* [Check site readiness](/docs/check-site-readiness) — see what stops AI
answer engines from reading a website, and scan it again.
* [Site audit](/docs/site-audit) — crawl up to 50 pages of a site, read its
health score by category, and work through its issues.
* [Manage assets](/docs/manage-assets) — upload images and video, find
any file in your workspace, and rename or delete uploads.
* [Read Performance](/docs/read-performance) — Google Search clicks and
queries, and how each pillar's social posts did.
* [Track AI assistant traffic](/docs/track-ai-assistant-traffic) — install
the tracking file on a Next.js site and see which AI assistants fetch your
pages.
* [Capture leads and subscribers](/docs/capture-leads) — the API contract
your site's form posts to.
* [MCP server](/docs/mcp) — query these docs from Claude, ChatGPT, or another
MCP client.
More documentation lands here as it is written.
# Inspect a URL (/docs/inspect-a-url)
**Inspect a URL** asks Google Search Console about one page and shows whether
Google has indexed it, and why or why not. It shows Google's last recorded
view of the page. It doesn't run a fresh test.
## Find the section [#find-the-section]
1. In the sidebar, select **Analysis** > **Website**. It opens your primary
site. To switch sites, select the site's name at the top of the page.
2. **Inspect a URL** is the first section, above **Site audit**.
**Performance** also links here. When Google is connected, select "inspect a
URL on" and your primary site's name at the bottom of **Search**. See
[Read Performance](/docs/read-performance).
## Before you start [#before-you-start]
* Google must be connected with Search Console access.
* A Search Console property must be chosen on the Google connection.
When both are in place, the section header shows "Search Console property:",
then the property. Every inspection uses that one property, whichever site's
page you're on. The URL you enter must be a page in that property.
## Inspect a page [#inspect-a-page]
1. Enter the full page URL, including https\://.
2. Select **Inspect**. The button reads **Inspecting…** while it runs.
The result appears below the form, under the URL you checked.
## Read the result [#read-the-result]
Three values sum it up:
* **Verdict**: **Indexed**, **Not indexed**, or **Unknown**.
* **Coverage**: Google's own wording for the page's index status.
* **Last crawl**: when Google last crawled the page, in your workspace
timezone. **Never** means Google hasn't crawled it.
The details below show why:
* **Crawl allowed**: **Yes**, or "No: blocked by robots.txt".
* **Page fetch**: whether Google could load the page. For example,
**Successful** or "Failed: not found (404)".
* **Indexing allowed**: **Yes**, or a reason such as "No: noindex in robots
meta tag".
* **User-declared canonical**: the canonical URL the page names, or **None**.
* **Google-selected canonical**: the URL Google chose as the canonical.
* **Sitemaps**: sitemaps where Google found the page.
* **Referring pages**: pages Google saw linking to it.
A "—" means Google returned no value for that field.
**Sitemaps** and **Referring pages** list at most 5 URLs. If Google knows of
more, the list ends with "Showing 5 of", then the total.
If a value starts with "No:" or "Failed:", fix that on your site, then inspect
the page again after Google recrawls it.
When Google returns a link to its full report, **Open in Search Console**
shows below the details. It opens in a new tab.
## If something goes wrong [#if-something-goes-wrong]
If the URL doesn't start with http\:// or https\://, the form says "Enter a full
page URL, like `https://example.com/page`."
If Google can't inspect the page, the form says "Search Console could not
inspect that URL. It must be a page on", then your property, then "Try
again." Check that the URL belongs to that property.
If the Google connection isn't set up, the section shows one of these
messages. Select **Open the Google connection** to fix it in **Settings** >
**Connections** > **Google**:
* Not connected: "Connect Google to see search clicks and impressions."
* No Search Console access: "Your Google connection does not include Search
Console. Reconnect Google and allow Search Console access."
* No property chosen: "Choose the Search Console property Mark should read on
the Google connection."
* Access revoked or expired: "Google access was revoked or expired. Reconnect
Google to see search data."
If Search Console doesn't answer, the section says "Search Console did not
answer. Reload to try again." Reload the page.
For the rest of the site's page, see [Site audit](/docs/site-audit) and
[Check site readiness](/docs/check-site-readiness).
# Install the site tag (/docs/install-the-site-tag)
The site tag is a single script you paste on your site. It does two things:
it starts analytics for your pages through Mark's own domain, and it listens
to the lead form you already have, so the traffic source that produced an
enquiry is attributed correctly.
It does not create the lead. Your form still submits to your CRM exactly as
it does today — the tag only watches for the submission and reports it to
Mark's analytics as an attribution event. It does not change your form
either: your markup, your styling, your validation, and your thank-you page
all stay exactly as they are.
## Paste the snippet [#paste-the-snippet]
Copy the snippet from your site's row on the Websites page and put it in
the `
` of every page, in the order it is given. To find that row, see
[Manage websites](/docs/manage-websites). Mark's own line looks like this:
```html
```
If your site uses Google Tag Manager or Cookiebot and Life With Data has
your container id or Cookiebot id, the snippet also carries those blocks:
Consent Mode defaults first, then the Tag Manager loader, then Cookiebot
outside Tag Manager. Mark never writes the consent defaults itself; they are
pasted once, here.
The snippet appears only once analytics is set to run through your own
domain. Until then the Websites page says what is still needed. This
sequencing matters: the tag sends events to that domain, so a snippet pasted
earlier would sit on your pages doing nothing.
The key identifies your site and is safe in client-side code, the same way
any analytics key is.
If your site already loads PostHog, remove that snippet when you add this
one. Two copies of the analytics library on one page split a single visitor
into two people, and the lead you care about ends up attached to whichever
copy happened to answer first.
## Tell Mark which form to capture [#tell-mark-which-form-to-capture]
Life With Data configures this for you, but it is worth knowing what they
need. There are two ways the tag can hear about a submission.
### A form in your own markup [#a-form-in-your-own-markup]
Most sites. Mark needs a **CSS selector** that matches your form element, for
example `#enquiry` or `form.contact-form`. The tag attaches one listener and
reads the form's named inputs when it is submitted.
These input names become their own property on the attribution event:
| Input name | Becomes |
| ------------------------------------- | -------------------------------------------------------------------- |
| `email` | The visitor's email. Required — a submission without one is ignored. |
| `name`, or `firstName` and `lastName` | The visitor's name. A split pair is joined into one. |
| `phone` | The visitor's phone. |
| `message` | The enquiry text. |
| `personaId` | The persona, if your form asks. |
| anything else with a `name` | An answer, filed under that input's name. |
Password inputs and hidden inputs are never read. If your form has a
honeypot field, tell Life With Data its name: the tag drops a submission
with a filled one instead of reporting it.
### A HubSpot form [#a-hubspot-form]
If your enquiry form is a HubSpot embed, a plain listener cannot see it, so
the tag listens for HubSpot's own submission event instead. Life With Data
needs to know you use HubSpot, and optionally the **form GUID** if you have
more than one HubSpot form and only one of them is your lead form.
HubSpot's `email`, `firstname`, `lastname`, `phone`, and `mobilephone`
properties map onto the event the same way; every other property on the
submission becomes an answer.
This works with both HubSpot embed styles. It does **not** work with HubSpot
Forms V4, which stopped broadcasting the event the tag listens for — if your
embed code mentions V4, tell Life With Data before you install.
### No form yet [#no-form-yet]
Leave the capture setting unset. The tag still runs analytics; it simply
attaches no form listener and never reports a submission.
## Cookie banners [#cookie-banners]
Each site has a **Cookie consent** setting with two choices.
**No cookie banner, track every visit** is where every site starts until you
give Life With Data a Cookiebot id. The tag starts full analytics on page
load and does not wait for a banner to answer. If a banner does ask and the
visitor declines, the tag switches to cookieless from then on.
**Wait for the cookie banner's answer** is what the site switches to once it
has a Cookiebot id. Life With Data can also pick it for a site that runs
HubSpot's banner instead. The tag then follows the banner:
| Banner | What the tag does |
| ----------------------- | ---------------------------------------------------------------------------------------- |
| Cookiebot | Waits for statistics consent. Full analytics on accept; cookieless analytics on decline. |
| HubSpot's cookie banner | Waits for the analytics category. Full analytics when granted; cookieless when denied. |
| Anything else, or none | Starts immediately. |
That last row matters. The tag only recognises the banners listed above, so
if you use a different consent tool the tag will not see it and will begin
capturing straight away. Tell Life With Data which tool you use and support
for it can be added.
Cookieless means nothing is stored on the visitor's device: no cookie, no
local storage. Page views are still counted, keyed by a server-side hash
that rolls over daily, so a decline does not make the visit vanish from
your numbers.
Google's tags follow the same setting. The Consent Mode defaults in the
snippet start granted on a site that tracks every visit, and denied on a
site that waits. The snippet is pasted once, so if the setting changes,
paste the new snippet from the Websites page to bring Google's tags in line.
Two details a privacy review usually asks about:
* The analytics library is **fetched** before consent is answered, but it is
loaded switched off — it sends no events and sets no cookies until consent
is granted. That is the analytics vendor's own documented pattern.
* If your banner loads more slowly than the tag, a single page view can be
recorded before the banner has an answer. Declining still stops everything
from that point on, and the recorded view is discarded with the visitor's
other data.
A visitor who declines still has their submission reported if they go on to
submit your form — submitting a form is its own act of consent. The tag
keeps the click ids and UTM parameters from the page the visitor landed on
in memory and attaches them to the submission, so the source survives even
when the banner was declined or an ad blocker dropped the first page view.
## Updates [#updates]
The tag is served from Mark, not copied into your pages, so improvements
reach your site on their own within a few minutes. You never need to paste
it again. The `v1` in the URL only changes if the tag ever changes in a way
that needs your attention, and Life With Data would tell you first.
## Turning it off [#turning-it-off]
Delete the script tag from your pages and the integration stops immediately;
nothing on Mark's side depends on it still being there. If Life With Data
needs to withdraw a change to the tag itself, they revert it and every page
picks up the correction within five minutes without you doing anything.
## Checking it works [#checking-it-works]
1. Open a page with the tag on it and look at your browser's network panel.
You should see requests to the analytics domain on your own site (the
one Life With Data set up as the reverse proxy) and none at all to
`posthog.com`.
2. Submit your form with a real email address.
3. The `lead_submitted` event appears in your PostHog project, and your own
thank-you page and CRM still work exactly as before — the tag only
reports the submission for attribution, it never touches either.
A submission your own form rejects — failed validation, or a failed bot
challenge — is not reported, so your validation stays the gate on what
counts.
If the form submits but nothing reaches PostHog, the selector is the usual
culprit — check it still matches after any redesign.
To post a lead to Mark directly instead of relying on the tag — for a form
that has no `submit` event, or a vendor the tag has no adapter for — see the
[lead capture contract](/docs/capture-leads).
# Manage assets (/docs/manage-assets)
**Assets** lists every file in your workspace: your uploads, URL imports, and
files synced from Google Drive. Open it from the sidebar under **Library** >
**Assets**. The newest files are at the top.
## Upload files [#upload-files]
1. Open **Library** > **Assets**.
2. Drag images or video onto the box that says **Drop images or video here,
or click to choose files**. Or select the box to pick files from your
computer.
On a phone there's nothing to drag, so the page shows a **Choose files to
upload** button instead. Tap it to pick photos or videos from your phone.
Upload several files at once, up to 400 MB each. Each file shows a
progress bar while it uploads, and it appears at the top of the table when
it's done. The box takes images and video only. Other file types are
skipped.
## Read the table [#read-the-table]
Each row is one asset:
* A small preview. Images show a thumbnail. Video, audio, and documents show
an icon for their kind. If an image preview can't load, the tile shows a
crossed-out image icon instead of staying blank.
* **Name**: select it to open the asset's details.
* **Source**: **Google Drive**, **Upload**, or **URL import**.
* **Drive path**: the folder path in Google Drive, for synced files.
* **Added**: the date the asset came into Mark.
## Filter and page through assets [#filter-and-page-through-assets]
Two sets of filters sit above the table. Use them together to narrow the
list:
* Source: **All**, **Google Drive**, **Upload**, or **URL import**.
* Kind: **All**, **Images**, **Video**, **Audio**, or **Documents**.
When there are more assets than fit on one page, select **Older** to go
further back and **Newer** to come forward again. Paging keeps your filters.
Changing a filter takes you back to the first page.
If no assets match your filters, the page says **No assets match these
filters**. Select **Clear filters** to see everything again. That's different
from an empty library, which says **No assets yet** and points you to
**Connections** if you want to sync files from Google Drive.
## See an asset's details [#see-an-assets-details]
Select an asset's name in the table. The detail page shows a preview and the
facts Mark stores about the file. A field only appears when it applies, so an
upload has no Drive path and a document has no dimensions.
* **Kind**: image, video, audio, or document.
* **MIME type**: the file's format, for example image/png.
* **Dimensions**: width and height in pixels, for images and video.
* **Duration**: for video and audio.
* **Size**
* **Source**
* **Source URL**: where Mark got the file, for URL imports and files synced
from Google Drive.
* **Drive path** and **Drive modified**: for files synced from Google Drive.
* **Added**
Select **Open file** to play or download the file in a new tab. Some files,
such as video and large files, open through a link that only works for a short
time. If you copy that link and open it later, it can stop working. Go back
to the asset and select **Open file** again for a fresh one.
## Rename an asset [#rename-an-asset]
1. In the table, open the actions menu at the end of the asset's row.
2. Select **Rename**.
3. In **Rename asset**, type the new name and select **Save**.
Renaming changes the name in Mark only. The file itself doesn't change. If
you clear the name and save, the dialog asks you to **Enter a name.**
## Delete assets [#delete-assets]
To delete one asset:
1. Open the actions menu at the end of the asset's row.
2. Select **Delete**.
3. Confirm with **Delete**. The file is gone from Mark for good.
To delete several at once, select the checkbox on each row you want, or the
checkbox in the header to select every asset on the page. A bar shows how
many are selected. Select **Delete** in that bar, then confirm.
Mark won't delete an asset that's still in use:
* If the asset is the brand logo or is attached to posts, the dialog says
it's in use and nothing is deleted. It lists the brand logo and up to three
posts that use it, with each post's channel and status, and a count of
any more.
* When you delete one asset and only posts in draft or changes requested
use it, the dialog offers a button with the post count, such as **Remove
from 2 posts and delete** or **Remove from 1 post and delete**. Select it to
take the asset off those posts and delete it in one step.
* If it's the brand logo, or a post in any other status uses it, that button
doesn't show. Other statuses include in review, approved, scheduled, and
published. Select the brand logo or each listed post to open it, remove
the asset there, then try again.
* If anything using the asset changed after you opened the dialog, it says
**Something changed since you opened this. Nothing was removed or
deleted.** Try again.
* In a bulk delete, assets in use and files synced from Google Drive are
skipped, and the rest are deleted. The dialog then shows how many were
deleted and why each skipped asset stayed.
## Files synced from Google Drive [#files-synced-from-google-drive]
Mark can't rename or delete a synced file. Its actions menu says **Synced
from Google Drive. Rename or remove it in Drive.** Make the change in Google
Drive instead.
# Manage websites (/docs/manage-websites)
**Settings** > **Websites** shows one site at a time. The site's name at the
top is a menu: select it to switch to another site, or to add one. The page
opens your primary site.
The first site is **primary**. It's the site **Home** shows. Each site has its
own snippet and its own readiness score, and every site reports into one
analytics project.
Until you add a site, the page says "No websites yet. Add one to get its tag,
a site audit, and an AI readiness score." Select **Add website** there to add
the first one.
## Switch sites [#switch-sites]
Select the site's name at the top of the page. The menu lists every site,
primary first, with a **Primary** badge on the primary one. Under each name
it shows the site's tag status and its readiness score once it has one.
Select a site to open it.
The tag status says when a page on that site last loaded its tag:
* **Tag seen** with how long ago, like "Tag seen 3 hours ago".
* **Tag not seen yet**. The snippet is ready, but no page on the site has
loaded it. It updates within an hour of the first visit after you paste it.
* **Tag not seen in N days**. The tag loaded before, but not in the last
week. The snippet may have been removed from the site.
* **No snippet yet**. See [When there's no snippet yet](#when-theres-no-snippet-yet).
When another site's tag is not seen yet, or not seen in a week, a link next
to the site's name says so, like "1 other site needs setup". Select it to
open that site. Only pages on the site's own domain, or a subdomain like
`www.`, count. A site that tells browsers to send no referrer
(`Referrer-Policy: no-referrer`) always reads **Tag not seen yet**, even when
its tag works.
## Add a website [#add-a-website]
1. Select the site's name at the top, then **Add website** at the bottom of
the menu.
2. Enter the **Hostname**, like `verdella.com`. Use the bare hostname, with no
`https://`, port, or path.
3. Optionally enter a **Name (optional)**, like "Main site". The menu shows
the name, or the hostname if there's no name.
4. Select **Add website**. The button reads **Adding…** while it runs.
The new site opens with its snippet at the top, ready to install. If the
hostname isn't valid or the site is already listed, the dialog says so and
nothing is added.
## Edit a site [#edit-a-site]
Under **Site details**, change the **Hostname** or **Name (optional)**, then
select **Save**.
## Make a site primary [#make-a-site-primary]
1. Open the site you want **Home** to show.
2. Select **More actions** (**⋯**) next to **Site audit**, then **Make primary**.
The primary site has no **Make primary** option, and it shows a **Primary**
badge next to its name.
## Remove a site [#remove-a-site]
1. Open the site, select **More actions** (**⋯**), then **Remove website**.
2. A dialog asks to confirm, like "Remove verdella.com?"
3. Select **Remove website** to confirm, or **Cancel** to keep the site.
Removing a site stops its tag working and deletes its readiness, audit, and
ranking history. The page then opens your primary site.
## Copy a site's snippet [#copy-a-sites-snippet]
Each site has its own snippet under **Tag**. Check the name at the top before
you copy, so you install the right site's snippet.
1. Select **Copy**. It always copies the whole snippet. If the snippet is
long, select **Show all N lines** to read it, and **Show less** to fold it
again.
2. Paste it in the head of every page of that site, in the order given.
Mark's tag updates itself. You paste again only after a change to the
snippet, covered under form capture below. For the paste steps a site
owner follows, and what the capture and consent settings do on the site, see
[Install the site tag](/docs/install-the-site-tag).
### When there's no snippet yet [#when-theres-no-snippet-yet]
The snippet sends analytics through your own domain, so **Tag** shows no
snippet until that's set up. In its place, it shows a notice that says what's
missing:
* **No snippet yet: PostHog is not attached**. Mark has no analytics project
for your workspace yet.
* **No snippet yet: reverse proxy not checked**. The project is attached, but
Mark hasn't checked whether analytics runs through your domain. Select
**Check again**.
* **Reverse proxy not enabled** or **Reverse proxy is not live**. Analytics
doesn't run through your domain yet. Once it does, select **Check again**.
The other notices link to the PostHog connection, where Life With Data
finishes the setup. Every site's snippet appears once analytics runs through
your domain.
## Set up form capture [#set-up-form-capture]
**Form capture** shows what that site's tag reports when a
visitor submits a form: the capture method, honeypot field, Google Tag
Manager, Cookiebot, and cookie consent. Each site has its own settings,
because the form on your main site usually isn't the form on a landing page.
1. Select **Edit capture**.
2. Pick a **Capture method**:
* **No form capture**: the tag runs analytics only.
* **A form in the site's own markup**: enter the **CSS selector** that
matches the form, like `#enquiry` or `form.contact-form`.
* **A HubSpot form**: optionally enter the **HubSpot form GUID
(optional)**. Leave it blank to capture every HubSpot form on the page.
HubSpot Forms V4 isn't supported.
3. Fill in any optional fields the site needs:
* **Honeypot input name (optional)**: the name of the site's bot-trap
input. A submission with it filled in is dropped.
* **Google Tag Manager container id (optional)**: adds Tag Manager to this
site's snippet.
* **Cookiebot id (optional)**: adds Cookiebot to this site's snippet.
4. Pick a **Cookie consent** setting: **No cookie banner, track every visit**
or **Wait for the cookie banner's answer**. Adding a Cookiebot id switches
it to wait. You can change it back.
5. Select **Save**. The section shows "Capture settings saved."
Tag Manager, Cookiebot, and consent settings change the snippet itself. After
you change one, copy the site's snippet again and replace the old one on the
site. For what each setting does on the site, see
[Install the site tag](/docs/install-the-site-tag).
## Check a site's health [#check-a-sites-health]
Select **Site audit** next to the site's name to open that site under
**Analysis** > **Website**. The badge next to the name shows the site's latest
readiness score. For what that page shows, see
[Check site readiness](/docs/check-site-readiness) and
[Site audit](/docs/site-audit).
# MCP server (/docs/mcp)
This site runs a public [Model Context Protocol](https://modelcontextprotocol.io)
server at `/api/mcp` — no auth, since everything it serves is already public
on this site. Point an MCP client at `/api/mcp` to use
it.
## Tools [#tools]
* `list_docs` — every page's title, URL, and description.
* `search_docs` — case-insensitive text search across all pages.
* `get_doc` — a page's full content by URL, as the same Markdown
`/llms-full.txt` serves.
## Plain files [#plain-files]
For a client that just wants the raw text instead of MCP tool calls:
* [`/llms.txt`](/llms.txt) — an index of every page.
* [`/llms-full.txt`](/llms-full.txt) — every page's full content.
# Read Performance (/docs/read-performance)
Performance shows how a workspace did in Google Search and on social over a
date range you pick. Open **Performance** in the sidebar. The page has a
filter bar, a bar of section links, and three sections: **Search**,
**Social**, and **AI assistant traffic**.
## Set the range and filters [#set-the-range-and-filters]
1. Pick a range. Select **Last 7 days** or **Last month**, or set the From and
To dates.
2. To see the previous period next to this one, turn on **Compare**. It
compares with the window of the same length right before your range.
3. If the workspace has more than one market, pick **All markets** or one
market. A market narrows **Social** to that market's posts, and
**Search** to that market's country when the country is a 2- or 3-letter
code. See [Read Search](#read-search).
The line under the **Performance** heading restates what you're looking at:
the range, the workspace timezone, the market and pillar if you picked one
(the pillar filter sits in **Social**), and the compare window when
**Compare** is on.
To jump to a section, select **Search**, **Social**, or **AI assistants** in
the bar under the filters. It stays at the top of the screen as you scroll.
## Read Search [#read-search]
Search shows Google Search Console numbers for the property chosen on the
Google connection. The line next to the heading names that property and the last day with data, for example
"Data through 2026-09-21, Pacific Time". Google lands data 2 to 3 days late,
so the last few days of a range can be empty.
If you picked a market, a note under the heading says how Search treats it.
Search can filter by the market's country only when that country is a 2- or
3-letter code, like DE or DEU. A name like "Germany" or "DACH" doesn't filter,
and neither does "UK". Use GB for the United Kingdom.
* If the country is a code, the tiles, **Top queries**, and **Top pages** are
for that country. **Tracked rank** isn't filtered. For a market with country
DE, the note says "Search shows searches from DE only."
* If it isn't, Search covers all markets. The note names the market. For a
market called DACH, it says "Search covers all markets. It can filter by
country only when DACH's country is a 2- or 3-letter code like DE or DEU."
Select **Edit markets** to change the country.
The four tiles show **Clicks**, **Impressions**, **CTR**, and **Average
position** for the range. With **Compare** on, each tile also shows the
previous window's figure as "vs" and the number.
### Top queries [#top-queries]
**Top queries** lists the top 100 search queries by clicks. Select **Only
positions 5 to 20 (n)** to see only queries that rank on positions 5 to 20,
sorted by impressions. The number in the label is how many there are. These
queries already rank, just not near the top.
Google hides some queries for privacy, so the rows don't add up to the totals
in the tiles.
If Mark tracks keywords for the first site listed under **Websites**, the
table has a **Tracked rank** column.
This is Mark's own Google rank check, not Search Console. It shows the latest
position and the change since the previous check. That change is not tied to
the date range you picked. The note under the table says when the check last
ran and has an **Open Rankings** link.
### Top pages [#top-pages]
**Top pages** lists the top 100 pages by clicks, with the same four numbers
per page. Google counts each page on its own, so a search that shows two of
your pages counts for both, and the rows don't add up to the totals.
To check how Google indexes one page, open the site's page under **Websites**.
Search links to it at the bottom of the section.
### When Search shows no numbers [#when-search-shows-no-numbers]
If Google isn't connected or can't be read, Search shows a message instead of
numbers. For example, "Connect Google to see search clicks and impressions."
Other messages cover a connection without Search Console access, no Search
Console property chosen, and revoked access. Fix these in **Connections** >
**Google**. The message links there.
If Search Console doesn't answer, Search says so. Reload the page to try
again.
## Read Social [#read-social]
Social covers the posts published in your range, grouped by pillar. Pillars
are the content themes set under **Strategy** > **Pillars**. Each pillar with
posts in the range has a row, plus a **No pillar** row and a **Total** row.
The columns are:
* **Posts**: posts published in the range.
* **Measured**: how many of those posts have metrics yet.
* **Impressions** and **Engagement**: from each post's latest metrics
checkpoint.
* **Clicks**: clicks on the post's links in the range, not counting bots.
The line under the heading says how fresh the metrics are, for example "Data
through" and a date, or "No checkpoints yet".
To narrow the table, use the pillar filter at the top of the section. Pick
**All pillars**, one pillar, or **No pillar**. The market filter at the top of
the page also applies here.
### See each post [#see-each-post]
1. Select the arrow next to a pillar to expand it.
2. Each post shows its channel and publish date, with its own numbers.
3. Select the post to open it in Mark. Select the external-link icon next to
it to open the live post on the channel.
### Compare with the previous window [#compare-with-the-previous-window]
With **Compare** on, the **Clicks** column shows "vs" and the previous
window's clicks for each pillar and the total. Only clicks are compared.
Impressions and engagement are each post's latest checkpoint, so they don't
split cleanly by window.
If no posts were published in the range, Social says "No posts published in
this range."
## Read AI assistant traffic [#read-ai-assistant-traffic]
AI assistant traffic counts fetches of your pages by assistants like ChatGPT,
Claude, and Perplexity in the range, with the top pages. It needs a tracking
file on your site. See
[Track AI assistant traffic](/docs/track-ai-assistant-traffic) to install it
and to read what each kind means.
# Research and save keywords (/docs/research-and-save-keywords)
**Keywords** is where you find search terms worth writing for and keep a list
of them. Select **Analysis** > **Research** in the sidebar. **Keywords** is the first tab. It
has two views: **Research** gets new keyword ideas, and **Saved** is your list.
Each run of keyword research is a paid lookup. Nothing is requested until you
select **Run keyword research**. Opening either view, saving, tagging,
removing, and tracking are free.
## Research keyword ideas [#research-keyword-ideas]
1. Select the **Research** view.
2. Enter one to five seed terms in **Seed terms (1–5, one per line)**. The line
under the box counts them. You'll see "Enter at least one seed term." until
you add one.
3. Choose a **Location** and a **Language**. They start at United States and
English.
4. Choose a **Result count**: 10, 25, 50, or 100 ideas.
5. Optionally select **Include SERP overview (may increase provider cost)**.
It adds a summary of each keyword's Google results, and it costs more.
6. Select **Run keyword research**. The button reads **Running keyword
research…** until the results arrive.
A line under the button says how many ideas came back and what the lookup
cost. If it says "No keyword ideas were returned for these seed terms and
settings.", try broader seed terms or another location.
## Read the results [#read-the-results]
Each idea has one row:
* **Keyword** is the search term.
* **Volume** is how many people search for it each month.
* **CPC** is the average cost per click for ads on it.
* **Competition** is how crowded the ads are for it, as a percentage.
* **Difficulty** is how hard it is to rank for it. Higher is harder.
* **Intent** is what the searcher is likely after, like informational or
commercial.
* **SERP** shows **Available** when the SERP overview came back for that
keyword.
A dash means no figure came back for that keyword.
## Save keywords [#save-keywords]
1. Select the box next to each keyword you want, or the box above the table to
select all.
2. Select **Save selected**.
A line confirms how many were newly saved, and how many were already saved
if any. Keywords save with the location and language of the run. Find them under
**Saved**.
## Find saved keywords [#find-saved-keywords]
Select **Saved**. The newest saved keywords are at the top. Until you save
one, it says "No keywords saved yet. Research keywords and save them here."
To narrow the list:
1. Fill in any of these:
* **Search**: part of the keyword.
* **Tags**: one or more tag names, separated by commas.
* **Source**: **All sources**, **Research**, **Domain**, **Agent**, or
**Search Console**. **Domain** means you saved it from a domain lookup
on the **Competitors** tab. A keyword keeps the source it was first
saved from.
2. Select **Apply filters**.
Select **Clear** to see the full list again. If nothing matches, you'll see
"No saved keywords match these filters."
Sort by **Keyword** or **Saved** by selecting the column heading. Select it
again to reverse the order. Use **Previous** and **Next** under the table to
move between pages.
## Tag saved keywords [#tag-saved-keywords]
1. Select the keywords.
2. Enter a **Tag name**.
3. Select **Add to selected** or **Remove from selected**.
To remove one tag from one keyword, select the **×** on the tag in the
**Tags** column.
## Remove saved keywords [#remove-saved-keywords]
1. Select the keywords.
2. Select **Remove selected**.
3. Confirm with **Remove**, or select **Cancel**.
Removing deletes the keywords, their tags, and any rank history from your
saved list. It does not undo.
## Track keywords for rank checks [#track-keywords-for-rank-checks]
Tracked keywords get Mark's own Google rank checks for one of your sites. The
results show on the **Rankings** tab and on **Performance** > **Search**. See
[Read performance](/docs/read-performance).
1. If you have more than one site, pick the rank tracking site from the row of
site buttons at the top of **Saved**.
2. Select the keywords.
3. Select **Track selected** or **Untrack selected**.
To track or untrack one keyword, select **Track** or **Untrack** on its row.
The **Rank tracking** column shows **Tracked** or **Not tracked** for the site
you picked.
Tracking is free. To run rank checks, see
[Check your keyword rankings](/docs/track-keyword-rankings).
# Research a competitor's domain and your backlinks (/docs/research-competitors)
**Competitors** shows what any domain ranks for in Google, and who links to
your own site. Select **Analysis** > **Research** in the sidebar, then the **Competitors**
tab. The domain lookup is at the top. **Backlinks** is below it.
Opening the tab is free. It shows results you already fetched. These buttons
fetch new data, and each press is a paid lookup:
* **Look up**
* **Look up top pages**
* **Keywords** on a row of **Top pages**
* **Refresh backlinks**
Opening an entry in **Recent lookups** is free.
## Look up a domain [#look-up-a-domain]
1. Enter a domain like `example.com` in **Domain**, or paste a URL. The line
under the box shows the domain Mark will look up.
2. Keep or change **Location** and **Language**. The form starts from the
lookup on screen. With no earlier lookup, it starts at United States and
English.
3. Select **Look up**.
**Rank by** sorts the keywords by **Traffic**, **Search volume**, or
**Position**. **Keyword contains** is optional. It keeps only keywords with
that word in them.
If you change a field after a lookup, you'll see "Settings changed — look up
again to apply." The results on screen stay as they were until you select
**Look up** again.
## Read the results [#read-the-results]
The line above the results shows when the data was fetched. The data updates
weekly.
Three cards sit at the top:
* **Estimated organic traffic**: estimated visits per month from Google.
* **Organic keywords**: how many keywords the domain ranks for, with counts of
new, lost, up, and down.
* **Position spread**: a bar chart of ranked keywords by Google position, so
it shows how many rank near the top and how many sit further down.
Below the cards, the table lists up to 100 ranked keywords:
* **Position** is where the domain ranks in Google for the keyword.
* **Volume** is how many people search for the keyword each month.
* **Difficulty** is "How hard it is to reach the top 10 for this keyword, from
0 to 100. Under 30 is easy, 70 and up is hard." Point at the score to see
**Easy**, **Medium**, or **Hard**.
* **Traffic** is "Estimated monthly visits this ranking brings the domain,
from search volume and the click rate at its position."
* **URL** is the domain's page that ranks.
## Save keywords [#save-keywords]
1. Select the box next to each keyword you want, or the box above the table
to select all.
2. Select **Save selected**.
The keywords save to the location and language shown next to the button. A
line confirms how many were newly saved and how many were already saved. Find
them under **Research** > **Keywords** > **Saved**, where new ones show
**Domain** as their source.
## Look up top pages [#look-up-top-pages]
1. Optionally enter part of a URL in **URL contains**, like `/blog`, to see
only matching pages.
2. Select **Look up top pages**. It uses the same domain, location, and
language as the form.
**Top pages** lists the domain's pages by estimated traffic per month, with
how many keywords each page ranks for.
To see one page's keywords, select **Keywords** on its row. A panel opens with
that page's ranked keywords, by traffic.
## Reopen an earlier lookup [#reopen-an-earlier-lookup]
**Recent lookups** lists your workspace's 25 most recent domain lookups, with
each one's location, language, **Rank by**, **Contains**, and date. Select one
to show its stored results. **Showing** marks the one on screen.
## Check your backlinks [#check-your-backlinks]
**Backlinks** shows who links to your own site. If you haven't added a site
yet, it says "No website yet." Add one under **Settings** > **Websites**
first. See [Manage websites](/docs/manage-websites).
1. If you have more than one site, pick it from the row of site buttons.
2. Select **Refresh backlinks**. Until the first refresh, the line next to the
button says there's no backlink snapshot yet for your site.
When the refresh finishes, you'll see "Updated just now." The line next to
the button shows when the snapshot was captured.
## Read your backlinks [#read-your-backlinks]
**Overview** shows the totals for your site: **Backlinks**, **Referring
domains**, **Referring pages**, **Rank**, **Broken backlinks**, **Broken
pages**, and **Spam score**.
Two charts sit next to it:
* **Backlink growth** plots backlinks and referring domains at each refresh.
It needs at least two refreshes to draw a line.
* **Links first seen** counts the links in this snapshot by the month they
were first picked up.
Under the charts, two tabs split the links:
* **Backlinks** lists each link: **Source**, **Target**, **Anchor**,
**Flags**, **Link rank**, **Domain rank**, **Spam**, and **First seen**.
**Flags** shows **Nofollow** when the link is nofollow. **Spam** is the
link's spam score.
* **Top pages** lists the pages on your site that get the most links, with
**Referring domains** and **Best domain rank** for each.
# Review and approve posts (/docs/review-and-approve-posts)
Posts waiting for review collect in the **Queue**. Approve several at once
from there, or open a post's review page to check it and decide on it alone.
For what each status means, see
[Review and approval](/docs/concepts/review-and-approval).
Open the Queue from the sidebar: **Content** > **Queue**. The page is headed
**Waiting for review**. If nothing is waiting, it says "Nothing is waiting
for review."
## Filter the Queue [#filter-the-queue]
1. Pick a **Channel**, or leave it on **All channels**.
2. Set **Scheduled from** and **Scheduled through** to limit the list to a
date range. Set one, both, or neither.
3. Select **Apply filters**.
Select **Clear** to remove the filters. The Queue shows up to 100 posts at a
time. If there are more, it says so. Narrow the filters to see the rest.
Each row shows the channel, the start of the post, and when it's scheduled.
Posts with no time yet show **Not scheduled**. A **Claim flagged** badge
means the post has an open claim flag.
## Approve posts in bulk [#approve-posts-in-bulk]
1. Select the box on each post you want to approve. To pick every post in
the current list, select the box at the top.
2. Select **Approve selected**. The button reads **Approving…** while it
works.
3. Read the summary, like "3 approved, 1 skipped." Under it, each post shows
whether it was approved or skipped, and why.
Approved posts leave the Queue. Mark skips a post instead of approving it
when:
* It has open claim flags. The reason lists them. Resolve them first.
* Someone already decided on it.
* It changed after you opened the Queue. Reload and check it again.
* It's no longer in the Queue.
* It couldn't be approved for another reason. Try it from its review page.
If you select **Approve selected** with no posts picked, the summary says "No
posts were selected."
## Open a post's review page [#open-a-posts-review-page]
Select a post in the Queue to open its review page. On the **Calendar**,
posts that aren't drafts or waiting on changes also open here. See
[Reschedule posts on the calendar](/docs/use-the-calendar).
The top of the page shows the channel, the status, and the revision number,
like **rev 3**.
If there's nothing to decide on, the page says **Not in review** instead.
The line under it says why, usually one of these:
* "This instagram post has no frozen revision yet, so there is nothing to
approve. It enters review when it is submitted." The post was written in
Mark but hasn't been submitted yet.
* "This instagram post was created outside Mark, so it does not go through
review here." It's an external post, brought in from the channel. External
posts never enter review, whether they're scheduled, published, or failed,
so there's nothing to approve, request changes on, or reject.
If the post is live on the channel, **View live post** shows under the
message.
## Check the post [#check-the-post]
Before you decide, check each part of the page. **Media**, **What we will
send**, **History**, and **Comments** start collapsed. Open them to see
what's inside.
* **Preview** shows the post as it will look on the channel. For channels
with a length limit, a line under it counts the caption's characters.
* **Delivery** shows the time in the workspace timezone (**Scheduled
(tenant)**) and in yours (**Scheduled (yours)**), plus the **Account** it
posts from. If no account is set up for the channel, it reads "No account
configured". Channel fields like **First comment** are listed here too.
* **Claims (N open)** lists open claim flags. "No open claim flags on this
post." means there are none.
* **Media (N)** lists each image or video with its dimensions. If the shape doesn't
fit the channel, you'll see "Ratio mismatch." or "Possible ratio
mismatch." This is a warning only. It doesn't block approval.
* **What we will send**, when it's there, shows the exact content Mark sends
to the channel.
## Decide [#decide]
The **Decision** box sits beside the post, or at the bottom of the screen on
a phone.
* If the post has open claim flags, **Approve** is turned off and the box
says "Approve is blocked by N open claim flag(s)", followed by the flags.
Resolve the flags first. Otherwise, select **Approve**.
* To send the post back, select **Request changes**. Fill in **What needs to
change?**, then select **Submit**.
* To turn the post down, select **Reject**. Fill in **Why is this
rejected?**, then select **Submit**.
The note is required for **Request changes** and **Reject**. Select
**Cancel** to go back without deciding. Once your decision is saved, the box
says "Decision recorded."
## Comment and read the history [#comment-and-read-the-history]
To leave a comment, open **Comments (N)**. Type in **Write a comment**, then
select **Post comment**. Each comment shows who wrote it, the revision, and
when.
Open **History (N revisions)** to see each revision, who edited it, and
when. Older revisions read "Differs from rev N in:" followed by the fields
that changed. If the body changed, you'll also see the lines that changed
since the previous revision. Under **Decisions**, each past decision is
listed with who made it, when, and their note.
## Edit or reschedule [#edit-or-reschedule]
The top of the review page has two more actions. Saving an edit or a new
time sends the post back to review, so someone has to approve it again.
* **Edit** opens the post in the editor.
* **Reschedule** asks for a **New time (your timezone)**. Pick the date and
time, then select **Reschedule and reopen review**.
## View the live post [#view-the-live-post]
Once a post is published, the review page shows **View live post**. It opens
the post on the channel in a new tab.
# Set KPI targets (/docs/set-kpi-targets)
A KPI target is a goal for one metric in one month. Open **Strategy** >
**Targets** in the sidebar to set them. Targets are no longer on
**Performance**, and no report compares your actual numbers against them.
## Add a target [#add-a-target]
1. Pick a **Metric**: **Posts per week**, **Impressions**, **Engagement
rate**, **Leads**, **Qualified**, **Rejection rate**, or **Cost per lead**.
2. Pick a **Month**. It defaults to the current month in your workspace's
time zone.
3. Enter a **Value**: zero or a positive number with up to 4 decimal places.
4. Select **Save target**.
The target appears in the table below the form, with columns for **Month**,
**Metric**, and **Target**. Until you save one, the page shows "No targets
yet."
## Change a target [#change-a-target]
Save the same metric and month again with a new value. It replaces the old
one, so there's no separate edit button.
## Delete a target [#delete-a-target]
1. Select **Delete** on the target's row.
2. Confirm with **Delete**. The dialog warns "This cannot be undone."
# Set up posting slots (/docs/set-up-posting-slots)
A posting slot is a weekday and a time when a channel posts. Open
**Content** > **Slots** in the sidebar to set them. The page heading is **Posting slots**,
with the line "The weekly grid of times each channel posts. Times are your
organization's wall clock."
## Add a slot [#add-a-slot]
1. Pick a **Channel**. The list starts on **Select a channel**.
2. Pick a **Weekday**. The list starts on **Select a day** and runs Monday
through Sunday.
3. Enter a **Time** in the time field.
4. Select **Add slot**.
The slot appears in the list below the form, and the form clears for the next
one. If Mark can't add it, the form shows why:
* "Pick a channel, a weekday, and a time." Mark couldn't read one of the
three. Check them and try again.
* "That channel already has a slot at this weekday and time." The slot exists
already.
* "You must be signed in to an organization." Your session ended. Sign in,
pick your organization, and try again.
## Read the list [#read-the-list]
Until you add one, the page shows "No slots yet. Add one above to say when a
channel posts."
Slots are grouped under each channel's name, with underscores shown as spaces
(so landing\_page reads as **Landing Page**). Each row reads as a weekday and a
24-hour time, like "Monday at 09:00", with a **Delete** button.
Empty slots show up on the calendar as **Missed**, **Gap**, or **Open slot**.
To read them there and move posts into them, see
[Reschedule posts on the calendar](/docs/use-the-calendar).
## Delete a slot [#delete-a-slot]
Select **Delete** on the slot's row. There's no confirm dialog, so the row is
gone as soon as the page refreshes.
# Set up your brand, personas, pillars, and markets (/docs/set-up-your-strategy)
Your posts and angles build on your brand, personas, pillars, and markets,
so set these up first. Each has its own page in the sidebar under
**Strategy**: **Brand**, **Personas**, **Pillars**, and **Markets**.
Many fields are lists that take one entry per row. Select **Add** plus the
field name (for example, **Add Tone**) for another row, and the **×** next to
a row to remove it.
Create personas and pillars before you create **Angles**, because angles
build on them. [**Targets**](/docs/set-kpi-targets), the last page in the
group, has its own guide.
## Brand [#brand]
Brand holds the facts and voice your posts should follow, plus your visual
identity. The page has two groups, **Brand and voice** and **Visual
identity**, and each one is saved and confirmed on its own.
1. Open **Strategy** > **Brand**.
2. In **Brand and voice**, fill in:
* **Positioning**, **Differentiators**, and **Doctrine**.
* Under **Voice**: **Tone**, **Principles**, **Do**, **Don't**,
**Example posts**, **Hashtags**, and **CTA defaults**.
* Under **Company facts**: **Company name**, **Website**,
**Headquarters**, **Product range summary**, **Factory locations**, and
**Languages**.
3. Select **Save and confirm** at the bottom of the group. The page shows
"Saved and confirmed."
4. In **Visual identity**, set the **Logo**, **Favicon URL**, **Colors**,
**Heading font**, and **Body font**.
5. Select **Save and confirm** at the bottom of that group.
To set the logo, select **Choose logo**, pick one image or upload one, and
select **Done**. To clear it, select the **×** next to the logo. Either way,
select **Save and confirm** for **Visual identity** to keep the change.
### Confirmed or not confirmed [#confirmed-or-not-confirmed]
Saving a group is how you confirm a person has reviewed its values. Under
each group's name, the page shows "Confirmed" with the date and time of the
last save, or "Not confirmed" if nobody has saved that group yet.
Saving one group doesn't touch the other. Saving **Brand and voice** leaves
**Visual identity** and its confirmation as they were, and the other way
around.
## Personas [#personas]
A persona is an audience your content is built to help and persuade.
1. Open **Strategy** > **Personas**.
2. Select **Add persona**.
3. Fill in:
* **Name**: what you call this audience.
* **Slug**: a short, unique label for the persona.
* **What they want**: the outcome this audience is after.
* **What convinces them**: one entry per row.
* **Objections**: one entry per row.
4. Leave **Active** checked unless you're adding it for later.
5. Select **Save persona**. The page shows "Persona saved."
To change a persona, select **Edit** on its row, update the fields, and
select **Save persona** again.
## Pillars [#pillars]
A pillar is a theme your content returns to, with the formats that make it
useful.
1. Open **Strategy** > **Pillars**.
2. Select **Add pillar**.
3. Fill in:
* **Name**: the theme.
* **Slug**: a short, unique label for the pillar. It's fixed after you
save.
* **Formats**: the kinds of posts that suit this theme, one per row.
* **Notes**: anything else the team should know.
4. Leave **Active** checked unless you're adding it for later.
5. Select **Save pillar**. The page shows "Pillar saved."
To change a pillar, select **Edit** on its row, update the fields, and
select **Save pillar** again.
Each pillar's row shows its slug and formats, or "No formats yet".
### A pillar's slug is fixed [#a-pillars-slug-is-fixed]
After you save a pillar, **Edit pillar** shows the slug as text instead of a
field. The app explains why: "Set when the pillar was created. Tracking
links already built from it carry this slug as their campaign, so changing
it would split this pillar's reporting in two."
Pick the slug with care when you create the pillar.
## Markets [#markets]
A market is a place you prioritize, with the local time zone it uses.
1. Open **Strategy** > **Markets**.
2. Select **Add market**.
3. Fill in:
* **Name**: what you call the market. Each market needs its own name.
* **Country**: where it is.
* **Time zone**: the market's local time zone, such as Europe/Berlin.
* **Priority**: a whole number. The list shows lower numbers first.
4. Leave **Active** checked unless you're adding it for later.
5. Select **Save market**. The page shows "Market saved."
To change a market, select **Edit** on its row, update the fields, and
select **Save market** again.
Each market's row reads like "Germany · Europe/Berlin · Priority 1".
## Deactivate or delete [#deactivate-or-delete]
Personas, pillars, and markets work the same way when you're done with one.
* **Deactivate** keeps the item but marks it **Inactive**. Inactive items
move to the bottom of the list. Select **Reactivate** to bring one back.
* **Delete** removes it for good. Mark asks you to confirm first, because
it can't be undone.
Mark refuses to delete an item while something still uses it, and tells you
to deactivate it instead:
* A persona or pillar can't be deleted while posts or angles still use it.
* A market can't be deleted while posts or answer-engine checks still use it.
When you see "Can't delete this persona because posts or angles still use
it. Deactivate it instead." (or the same message for a pillar or market),
select **Cancel** and use **Deactivate**.
# Site audit (/docs/site-audit)
A site audit crawls up to 50 pages of one website with JavaScript turned on,
the way a browser sees them, and scores what it finds together with the
site's latest AI readiness scan. Select **Analysis** > **Website** in the
sidebar to find the **Site audit** report.
## Run an audit [#run-an-audit]
1. Select **Analysis** > **Website**. It opens your primary site. If you have
more than one site, select the site's name at the top to switch.
2. In **Site audit**, select **Run audit**. After the first audit the button
reads **Run audit again**.
While the audit runs, the report shows **Crawling with JavaScript**, how many
of the 50 pages are done, and a progress bar. **Checked** shows how long ago
Mark last asked the crawler for progress. The page updates on its own every
few seconds while it's open. You can't start a second audit on the same site while one is
running.
The report header shows when the audit finished, how many pages it crawled,
and when AI readiness was last scanned.
## Run audits on a schedule [#run-audits-on-a-schedule]
Each audit is a paid crawl, so a site starts on **Manual** and audits only
when you select **Run audit**. To audit it once a week instead:
1. In **Site audit**, under **Schedule**, choose one:
* **Manual: only when you click Run audit**
* **Weekly: runs a paid audit once a week**
2. Select **Save schedule**.
With **Weekly**, **Next audit** shows the date and time of the next audit,
one week after you save. With **Manual**, it says **Manual**. Saving a
schedule doesn't start an audit, and selecting **Run audit** doesn't move the
next scheduled one.
A scheduled audit shows up in the report the same way as one you started.
If it fails, the next one runs a week later. It isn't retried sooner, since
every retry is another paid crawl. Select **Try again** to rerun it yourself.
## Read the health score [#read-the-health-score]
The health score runs from 0 to 100. Next to it are seven category tiles,
each with its own score and a count of issues per severity:
* **Crawlability & indexing**: broken pages, redirects, and canonicals.
* **Rendering**: whether pages show their content without JavaScript.
* **On-page content**: titles, descriptions, headings, and text.
* **Structured data**: errors in the page's schema markup.
* **Performance & page weight**: load time, page size, and compression.
* **Security**: HTTPS links.
* **AI readiness**: the issues from the site's AI readiness scan.
Each issue lowers the score by its weight: a Critical issue counts most, then
Warning, then Notice. An issue on every page costs its full weight, and an
issue on a few pages costs a share of it. A category with nothing checked
shows **n/a** and doesn't count toward the health score.
The site's page shows the health score as a badge next to the site's name.
Select a tile to show only that category's issues. Select it again, or
select **Show all**, to see every issue.
## Severity [#severity]
Every issue carries one of three words:
* **Critical**: the page or site is broken, or search engines can't read it.
For example, a page that returns an error, or a site that shows a blank
page without JavaScript.
* **Warning**: hurts how search engines read or reach the page. For example,
a missing meta description or a slow page.
* **Notice**: hygiene. For example, images without alt text.
AI readiness issues use the same words: essential is Critical, recommended is
Warning, and bonus is Notice.
## Work through the issues [#work-through-the-issues]
The list has two views.
**By issue** shows one row per check, Critical first. Within a severity,
issues that affect the whole site come first, then the ones on the most
pages. The **Pages** column says how many pages have the issue, for example
"7 of 50", or **Site-wide**. Select a row to see:
* what the audit found, in plain words;
* one sentence on how to fix it, with a **Learn how** link to a public
reference page;
* up to 10 of the affected pages, then how many more there are.
For an AI readiness row, the link opens the full AI readiness report
instead.
**By page** lists the pages that have issues, worst first. Select a page to
see its issues. Site-wide issues aren't in this view.
Both views show 10 rows at a time. Use **Previous** and **Next** below the
list to see the rest.
Under both views, the report counts the pages with no issues, for example
"12 pages had no issues".
## See what changed since the last audit [#see-what-changed-since-the-last-audit]
When you rerun an audit, the report compares it with the finished audit just
before it. Audits that are still running or that failed are skipped. Above
the health score, one line gives the result, for example "Since the audit on
Sep 17, 2026: 3 new issues · 4 fixed". If nothing changed, it reads "No new
or fixed issues since the audit on Sep 17, 2026".
* In **By page**, a page with new issues shows how many next to its issue
count, for example "2 new". Open the page and each new issue has a **New**
badge.
* **Fixed since** lists each issue that went away, as the page address and
the issue. Select it to open the list. It's hidden when nothing was fixed.
Only pages crawled by both audits are compared. The 50-page limit can reach
different pages from one run to the next, so an issue on a page that only one
audit crawled counts as neither new nor fixed. That includes a page you
published since the last audit. Site-wide issues aren't compared either.
The first audit of a site has nothing to compare with. Neither does an audit
run after the site's address changed, or one that shares no crawled page with
the audit before it. Those show no comparison. While a
new audit runs, **Last finished audit** keeps its own comparison.
## The blank page alert [#the-blank-page-alert]
If search engines that don't run JavaScript see a blank page, a red alert sits
at the top of the report. It says how many sample pages were fetched without
JavaScript and how to fix it: serve each page's own HTML through prerendering
or server rendering. Select **See the issue** to jump to its row.
This is the most damaging finding an audit can report. Until it's fixed,
those search engines see the same empty page at every address on the site.
## Check AI readiness [#check-ai-readiness]
The **AI readiness** tile has a **Check now** button. It runs a new AI
readiness scan and rescores the report. When it finishes, the tile says
"Updated just now." If the scan can't run, it says "Could not check right
now. Try again in a few minutes." and keeps the last results.
A site that has an AI readiness scan but no audit yet shows the AI readiness
tile and its issues on their own. Select **Run audit** to add the crawl.
## When an audit doesn't finish [#when-an-audit-doesnt-finish]
If the crawl fails, the report says **The audit didn't finish** and shows
why, for example when the site's robots.txt blocks the crawler, the site
can't be reached, or the crawl timed out. Fix the cause if there is one, then
select **Try again** to run a new audit.
While a new audit runs, or after one fails, the last finished audit stays
below it under **Last finished audit**, with the date it finished. Select it
to open it.
Audits that ran before site-wide checks existed show the issues they found
on each page, without site-wide rows.
# Track AI assistant traffic (/docs/track-ai-assistant-traffic)
AI assistants like ChatGPT, Claude, and Perplexity fetch web pages, but they
don't run the site tag, so the tag never sees them. A small file on your site
records each fetch in your PostHog project. **Performance** then shows the
hits in its **AI assistant traffic** section.
A hit means an assistant fetched a page. It doesn't mean the assistant cited
or recommended you.
## Before you start [#before-you-start]
* The site runs Next.js 16 or later. Other site builders aren't supported yet.
* PostHog is connected in **Connections** > **PostHog**. Without it, the
Website connection shows "Connect PostHog" instead of the file.
## Install the file [#install-the-file]
1. Open **Connections** > **Website**.
2. Under **AI assistant tracking**, pick the website if you have more than one.
3. Select **Copy** on the file.
4. Save it as `proxy.ts` at the root of the site's project and deploy. If the
site already has a `proxy.ts`, the comment at the top of the file says how
to merge the two.
The file already holds your PostHog project's public token, the same one the
site tag uses. Copy a fresh file from Mark rather than editing it by hand.
## Check the install [#check-the-install]
Select **Check AI assistant tracking**. Mark loads the site's home page as
**Mark-Probe**, then waits up to 30 seconds for that visit to reach PostHog.
Mark-Probe is Mark's own check. It never counts as an assistant.
| Result | What it means |
| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| **Receiving** | An AI assistant fetched a page in the last 7 days. |
| **Installed** | Mark's check reached PostHog. No assistant has visited in the last 7 days yet; on a small site that can take days. |
| **Not installed** | The site answered, but nothing reached PostHog. Deploy the file, or check again in a minute if you just did. |
| **Blocked by the site's firewall** | The site refused Mark's check, so Mark can't tell. Allow the Mark-Probe user agent in the site's firewall rules, then check again. |
| **Site didn't answer** or **Site couldn't be reached** | Mark couldn't load the home page. Check that the site is up. |
| **Check failed** | Mark couldn't read your PostHog project. Check again in a few minutes. |
The result shows for the website Mark last checked. Pick another website and
select the button again to check that one.
## Read AI assistant traffic [#read-ai-assistant-traffic]
Open **Performance** and pick a range. The **AI assistant traffic** section
shows:
* **Hits**: every assistant fetch in the range.
* Hits per assistant, split by kind:
* **Crawler** collects pages for training or an index (GPTBot, ClaudeBot,
PerplexityBot).
* **Search** builds the assistant's own search results (OAI-SearchBot,
Claude-SearchBot).
* **User fetch** loads a page because a person asked the assistant about it
(ChatGPT-User, Claude-User, Perplexity-User).
* **Top pages**: the 10 pages fetched most.
* A "Data through" line with the newest hit.
With no hits in the range, the section says so and links to the Website
connection.
Counts come from the user agent each assistant sends. Mark doesn't verify it,
so anyone can send a request that claims to be GPTBot.
# Check your keyword rankings (/docs/track-keyword-rankings)
**Rankings** shows where each tracked keyword ranks in Google for one of your
sites. Select **Analysis** > **Research** in the sidebar, then the **Rankings** tab.
Opening the tab is free. It shows positions from checks that already ran.
Only two things spend money: **Run now** and a **Weekly** schedule.
## Before you start [#before-you-start]
Rankings only checks keywords you track. Track them first from **Research** >
**Keywords** > **Saved**. Tracking is free. Until you track a keyword, the tab
says no keywords are tracked yet for your site and links to Saved keywords.
## Pick a site and locale [#pick-a-site-and-locale]
If you have more than one site, choose it in the site picker at the top. If a
site is tracked in more than one locale, pick the locale from the row of
buttons under it.
The box above the table shows the **Site**, **Locale**, **Last checked**, and
**Next check** for what you picked.
## Check positions now [#check-positions-now]
1. Select **Run now**. You'll see "Check started." If a check is already
running, you'll see "A check is already running. Showing its progress."
2. Watch the progress line under the button, like "Checking 12 keywords: 5
done, 1 failed." The page refreshes on its own until the check ends.
3. Read the result line when it finishes:
* "Last check finished for all 12 keywords." Every keyword got a new
result.
* "Last check finished with 2 of 12 keywords failed." The failed keywords
show **Check failed** in the table. Run the check again to retry them.
* "Last check failed. No new positions were recorded." Every keyword shows
**Check failed**. Earlier positions stay in each keyword's history. Run
the check again later.
## Set a schedule [#set-a-schedule]
1. Under **Schedule**, choose one:
* **Manual: only when you click Run now**
* **Weekly: runs a paid check once a week**
2. Select **Save schedule**.
With **Weekly**, **Next check** shows the date and time of the next check.
With **Manual**, it says **Manual**. Saving a schedule doesn't start a check.
## Read the table [#read-the-table]
Each tracked keyword has one row:
* **Position** is where your site ranked in Google on the last check, like
**#4**.
* **Change** compares the last two checks: **Up 3**, **Down 2**, or
**No change**. It shows a dash unless both checks found your site.
* **Ranking URL** is the page on your site that ranked.
* **Last checked** is the date and time of the keyword's latest check.
**Position** can also show one of these:
* **Not checked yet**: this keyword has never been checked.
* **Not in top 100**: the check ran, and your site wasn't in the first 100
Google results.
* **Check failed**: the latest check couldn't get a result for this keyword.
Sort by **Keyword** or **Position** by selecting the column heading.
## See a keyword's history [#see-a-keywords-history]
Select a keyword's name to open its history under the table. Once the
keyword has ranked in at least two checks, a chart shows its position over time, with #1
at the top. The **Checked** list shows every check with its position and
ranking URL. Select **Close** to hide it.
# Reschedule posts on the calendar (/docs/use-the-calendar)
Move a post to a different posting slot on the **Calendar** by dragging it
or by picking a slot from a list. The calendar shows one week of posting
slots and the posts in them. Open it from the sidebar: **Content** >
**Calendar**.
Use **Previous week** and **Next week** (the arrows at the top) to change
weeks, and **Today** to come back to the current one. The week's dates and
your workspace timezone sit next to the arrows.
## Read the week [#read-the-week]
The calendar has one row per channel, with a column for each day. Under each
channel name, the row shows progress against its cadence, like "2 of 3 this
week". A channel with no cadence shows a count instead, like "2 committed
posts".
Each post card shows the post's time, its status, the first line of the post,
and its media. A dashed card is an external post published outside Mark,
tagged with where it came from. A card marked **Off-slot** is a post whose
time doesn't match any of the channel's slots.
Slots with no post show one of three labels:
* **Missed**: the slot's time has passed and nothing went out.
* **Gap**: the slot is in the next 7 days and has no post yet.
* **Open slot**: the slot is more than 7 days away.
Slots come from **Content** > **Slots**.
## Move a post [#move-a-post]
A post can move to an empty future slot on its own channel, in the week
you're looking at. Published posts, external posts, and posts with changes
requested have no move controls.
To drag a post:
1. Grab the handle on the post card.
2. The slots that can take it light up and show **Move here**.
3. Drop the post on one of those slots.
To move a post without dragging:
1. Open **Move to slot…** on the post card. It lists only the slots that can
take the post.
2. Choose a slot.
3. Select **Move**.
The calendar refreshes with the post in its new slot.
## What moving does to review [#what-moving-does-to-review]
If the post is a draft, it only gets the new time. Nothing else changes.
Any other post goes back to review with the new time. That includes posts
that were already approved or scheduled, so someone has to approve it again
before it can publish. For how review works, see
[Review and approval](/docs/concepts/review-and-approval).
## When a move is refused [#when-a-move-is-refused]
If Mark can't make the move, the calendar shows why under the week:
* "Choose a slot on the same channel." A post can only move within its
own channel.
* "Choose an empty future slot." The slot's time has passed.
* "That posting slot is already occupied." Another post took the slot
first. Pick a different one.
* "Published posts are read-only." The post already went out.
* "External posts are read-only." The post was published outside Mark.
* "This post changed. Refresh the calendar and try again." Someone else
changed the post while you were moving it. Refresh the page and move it
again.
* "That post or posting slot is no longer available." The post or the slot
was deleted. Refresh the page.
# Concepts (/docs/concepts)
Mark is multi-tenant. Life With Data and each client run in separate
workspaces on the same platform.
* **Workspace** — one tenant's data: its content, its schedule, its
connections to systems like HubSpot, Google, or the
Instagram and X accounts it publishes to. Life With Data is tenant
one, so nothing waits on a client grant to ship.
* **Draft** — a piece of content (a post, an email, an ad) before it
publishes. Drafts carry claims that need a confirmed source before they
can be approved.
* **Review and approval** — see [Review and approval](/docs/concepts/review-and-approval)
for how a draft moves from written to published.
Mark maps onto a client's existing systems. It does not invent new fields,
statuses, or lists in a client's CRM.
# Review and approval (/docs/concepts/review-and-approval)
Nothing publishes without a person confirming it. That is the loop Mark is
built around: draft, in review, approved, scheduled, published — or changes
requested and back to draft.
A draft that cites an unconfirmed claim gets flagged, and the flag blocks
approval until someone confirms the source. It gets better with each review
and approval.
Who reviews depends on the workspace. In a client's tenant, that client is
the approver — the UI names them (`Waiting on Verdella`), not a generic
pending state.
## Emails approvers get [#emails-approvers-get]
Mark emails workspace members when a post in review needs attention. Each
post in an email has a **Review post** button that opens its review page.
* **Posts waiting for review** is a daily digest. It says
"Verdella has 3 posts in review." and lists each post by channel with its
scheduled time, or **No time set**. When a post is due soon, the list
starts under **Due within 24 hours**.
* **A post needs your review** covers one post that's due within 24 hours.
Some workspaces get this in place of the daily digest.
* **A post missed its scheduled time** means the post was still in review when
its time passed, so it didn't publish. Open it to decide what happens next.
The member who submitted a post doesn't get the digest or the due-soon email
about it. A missed post emails every workspace member, including the person
who submitted it.