API

Push content to your gitmoreviews site from your own system. The first surface is blog ingestion: your software writes the articles — from completed jobs, seasonal campaigns, whatever you know about your customers — and delivers them here. Posts appear on your live site and in its sitemap immediately.

Authentication

Create a token in Dashboard → Settings → API tokens (account owner only). The secret starts with gmv_ and is shown exactly once — we store only a hash. Send it as a bearer token on every request:

curl https://gitmoreviews.com/api/v1/websites \
  -H "Authorization: Bearer gmv_your_token_here"

A token is scoped to your whole account: it can read and write every site the account owns. Revoke it from the same settings page — revocation is immediate.

Errors & limits

Every non-2xx response uses one JSON envelope:

{
  "error": {
    "code": "validation_failed",
    "message": "The post payload is invalid.",
    "issues": [{ "path": "paragraphs", "message": "at least one paragraph" }]
  }
}

Codes you'll meet: unauthorized (401), not_found (404), method_not_allowed (405), site_not_generated (409), validation_failed (422, with issues), and rate_limited (429). Each token may make 120 requests per minute.

List your websites

GET/api/v1/websites

Returns the account's sites. Use the id in the blog endpoints below.

{
  "websites": [
    { "id": "cmc1x…", "subdomain": "scruffy-city-plumbing", "status": "PUBLISHED" }
  ]
}

List posts

GET/api/v1/websites/:id/posts

All of a site's blog posts, newest first, in the same shape you push them.

Create or update a post

POST/api/v1/websites/:id/posts

Upserts by slug: a new slug creates the post (201), an existing slug replaces it (200) — so re-pushing an edited article just works.

curl -X POST https://gitmoreviews.com/api/v1/websites/WEBSITE_ID/posts \
  -H "Authorization: Bearer gmv_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "spring-ac-tune-up",
    "title": "Why a spring AC tune-up beats a July breakdown",
    "description": "What our techs check in a 21-point spring tune-up.",
    "publishedAt": "2026-07-11",
    "paragraphs": [
      "Every summer we meet the same emergency: a capacitor that gave out on the hottest day of the year.",
      "A spring tune-up catches it for a fraction of the cost."
    ],
    "heroImageUrl": "https://example.com/images/tune-up.jpg",
    "relatedTradeSlugs": ["hvac"],
    "relatedAreaSlugs": ["knoxville"]
  }'

Field rules:

  • slug — lowercase letters, digits, hyphens; the post's URL (/blog/<slug>).
  • title, description — required; the description doubles as the meta description.
  • publishedAt — optional ISO date; defaults to today.
  • paragraphs — at least one plain-text paragraph.
  • heroImageUrl — optional; an https URL or a site-relative path.
  • relatedTradeSlugs / relatedAreaSlugs — optional; must match the site's own trade/area slugs (unknown slugs are a 422, so typos can't silently drop your internal links).
Posts go live on the next request to a published site — blog page and sitemap both. A draft site stores them and shows them when it's published.

Delete a post

DELETE/api/v1/websites/:id/posts/:slug

curl -X DELETE https://gitmoreviews.com/api/v1/websites/WEBSITE_ID/posts/spring-ac-tune-up \
  -H "Authorization: Bearer gmv_your_token_here"

Returns { "deleted": true }; deleting a slug that doesn't exist is a 404.