Documentation

API reference

Programmatic access to your projects, articles, keywords, and rankings. Bearer-token auth, JSON envelopes, monthly quota meters, and rate-limit guidance. Same shape your dashboard uses, exposed to your code.

01

#Quick start

Base URL

https://growganic.io/api/v1

Every request requires a Bearer API key in the Authorization header. Keys are generated in your dashboard under Settings → API Keys (Business plan). The raw key is shown once on creation and stored as a SHA-256 hash; if you lose it, revoke and re-issue.

curl https://growganic.io/api/v1/projects \
  -H "Authorization: Bearer gro_xxxxxxxxxxxxxxxxxxxx"

Rate limits

  • 100 requests per minute per API key.
  • Pipeline runs, keyword tracking, and a few other actions count against your plan's monthly quota.
  • Call GET /usage anytime to see what you have left.
  • Upgrading mid-cycle resets your monthly counters, so a 429 from the prior tier clears on your next call.

02

#Response shape

All responses return JSON. Success payloads use a data envelope. Errors use an error envelope with a machine-readable code and a human-readable message.

Success

{
  "data": { }
}

Error

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or revoked API key."
  }
}

03

#Error codes

CodeHTTPMeaning
UNAUTHORIZED401Missing, invalid, or revoked API key.
FORBIDDEN403Key is valid but lacks permission for this resource.
NOT_FOUND404Resource does not exist or belongs to another tenant.
VALIDATION_ERROR400Request failed validation.
CONFLICT409Would create a duplicate. The response carries the existing record id.
PRECONDITION_FAILED412Account state blocks the action (for example, domain not verified).
RATE_LIMIT_EXCEEDED429Per-minute or monthly quota exhausted.
TOO_MANY_REQUESTS429A recent run is still settling. Retry shortly.
INTERNAL_ERROR500Server error. Retry with backoff.

04

#Projects

GET/projects

List every project on your account. Paginated.

Query params

ParamTypeDescription
statusstringOptional. One of active, paused, archived.
limitintDefault 50, max 100.
offsetintDefault 0.

Example request

curl https://growganic.io/api/v1/projects \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "items": [
      {
        "id": "3b1b...",
        "name": "Acme Blog",
        "domain": "acme.com",
        "status": "active",
        "createdAt": "2026-04-01T10:00:00.000Z",
        "updatedAt": "2026-04-07T18:22:10.000Z"
      }
    ],
    "total": 4,
    "limit": 50,
    "offset": 0
  }
}
GET/projects/:id

Fetch a single project.

Example request

curl https://growganic.io/api/v1/projects/3b1b... \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "id": "3b1b...",
    "name": "Acme Blog",
    "domain": "acme.com",
    "status": "active",
    "createdAt": "2026-04-01T10:00:00.000Z",
    "updatedAt": "2026-04-07T18:22:10.000Z"
  }
}

Returns 404 if the project does not belong to your tenant.

05

#Articles

GET/articles

List articles. Supports filters and pagination.

Query params

ParamTypeDescription
projectIduuidOptional. Filter by project.
statusstringOptional. One of draft, optimizing, review, approved, published, refreshing.
limitintDefault 50, max 100.
offsetintDefault 0.

Example request

curl "https://growganic.io/api/v1/articles?projectId=3b1b...&limit=20" \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "items": [
      {
        "id": "a1c2...",
        "projectId": "3b1b...",
        "runId": "7f2e...",
        "title": "Best form builder for small business",
        "slug": "best-form-builder",
        "targetKeyword": "best form builder",
        "status": "published",
        "wordCount": 3102,
        "readingTimeMinutes": 14,
        "overallScore": 89,
        "seoScore": 91,
        "geoScore": 92,
        "readabilityScore": 72,
        "featuredImageUrl": "https://images.example.com/hero/a1c2.webp",
        "cmsProvider": "wordpress",
        "cmsExternalId": "482",
        "publishedUrl": "https://acme.com/blog/best-form-builder",
        "publishedAt": "2026-04-07T19:10:00.000Z",
        "createdAt": "2026-04-07T18:40:00.000Z",
        "updatedAt": "2026-04-07T19:10:00.000Z"
      }
    ],
    "total": 42,
    "limit": 20,
    "offset": 0
  }
}
GET/articles/:id

Fetch a single article with full markdown and sanitized body HTML.

Example request

curl https://growganic.io/api/v1/articles/a1c2... \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "id": "a1c2...",
    "projectId": "3b1b...",
    "runId": "7f2e...",
    "title": "Best form builder for small business",
    "slug": "best-form-builder",
    "targetKeyword": "best form builder",
    "status": "published",
    "wordCount": 3102,
    "readingTimeMinutes": 14,
    "content": "Best form builder...",
    "contentHtml": "<p>Best form builder...</p>",
    "excerpt": "Honest, tested guide to the form builders...",
    "metaTitle": "Best Form Builder for Small Business (2026)",
    "metaDescription": "Honest, tested guide to the...",
    "overallScore": 89,
    "seoScore": 91,
    "geoScore": 92,
    "readabilityScore": 72,
    "featuredImageUrl": "https://images.example.com/hero/a1c2.webp",
    "cmsProvider": "wordpress",
    "cmsExternalId": "482",
    "publishedUrl": "https://acme.com/blog/best-form-builder",
    "publishedAt": "2026-04-07T19:10:00.000Z",
    "createdAt": "2026-04-07T18:40:00.000Z",
    "updatedAt": "2026-04-07T19:10:00.000Z"
  }
}

Includes runId, scores, meta tags, schema markup, and CMS state when published. Articles are kept on GrowGanic and not deletable through the API. To take a live article down, manage it in your CMS.

POST/articles

Queue a new article. The keyword you pass is the keyword the article targets, with no system reinterpretation. Consumes one article credit from your plan's monthly quota.

Request body

{
  "projectId": "3b1b...",
  "keyword": "best form builder for small business",
  "targetWordCount": 3000
}

Example request

curl -X POST https://growganic.io/api/v1/articles \
  -H "Authorization: Bearer gro_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "3b1b...",
    "keyword": "best form builder for small business",
    "targetWordCount": 3000
  }'

Example response

{
  "data": {
    "runId": "7f2e...",
    "status": "queued"
  }
}

Generation runs in the background, typically a few minutes. Poll GET /runs/:id until articleId is non-null, then fetch GET /articles/:articleId. No CMS required: articles are fetchable here. If a CMS is connected, the article also publishes there. Calling with a keyword that already has an article in the project returns 409 with the existing article id.

06

#Runs

GET/runs

List article-generation runs you started through the API.

Query params

ParamTypeDescription
projectIduuidOptional. Filter by project.
statusstringOptional. One of pending, running, completed, failed, cancelled.
limitintDefault 50, max 100.
offsetintDefault 0.

Example request

curl "https://growganic.io/api/v1/runs?projectId=3b1b...&status=completed&limit=20" \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "items": [
      {
        "id": "7f2e...",
        "projectId": "3b1b...",
        "status": "completed",
        "startedAt": "2026-04-27T18:40:02.000Z",
        "completedAt": "2026-04-27T18:46:11.000Z",
        "durationMs": 369000,
        "error": null,
        "articleId": "a1c2...",
        "createdAt": "2026-04-27T18:40:00.000Z"
      }
    ],
    "total": 17,
    "limit": 20,
    "offset": 0
  }
}

articleId fills in mid-run as soon as the article exists. Final scores arrive when status is completed.

GET/runs/:id

Poll a single run by id. Cheaper than listing.

Example request

curl https://growganic.io/api/v1/runs/7f2e... \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "id": "7f2e...",
    "projectId": "3b1b...",
    "status": "running",
    "progress": 62,
    "startedAt": "2026-04-27T18:40:02.000Z",
    "completedAt": null,
    "durationMs": null,
    "error": null,
    "articleId": "a1c2...",
    "createdAt": "2026-04-27T18:40:00.000Z"
  }
}

progress is 0..100. status reaches completed when scores are final. Once articleId is set you can fetch the article.

07

#Keywords

GET/keywords

List tracked keywords for a project. These are the keywords that appear on the Rankings page and receive daily position checks.

Query params

ParamTypeDescription
projectIdrequireduuidProject to query.
limitintDefault 50, max 200.
offsetintDefault 0.

Example request

curl "https://growganic.io/api/v1/keywords?projectId=3b1b..." \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "items": [
      {
        "id": "kw_1",
        "keyword": "best form builder",
        "location": "US",
        "device": "desktop",
        "currentPosition": 4,
        "previousPosition": 6,
        "bestPosition": 3,
        "lastCheckedAt": "2026-04-11T03:00:00.000Z",
        "createdAt": "2026-03-20T12:00:00.000Z"
      }
    ],
    "limit": 50,
    "offset": 0
  }
}
POST/keywords/track

Add new keywords to rank tracking. Up to 100 per request. Duplicates are silently ignored.

Request body

{
  "projectId": "3b1b...",
  "keywords": [
    "best form builder",
    "free survey tool",
    "typeform alternative"
  ]
}

Example request

curl -X POST https://growganic.io/api/v1/keywords/track \
  -H "Authorization: Bearer gro_..." \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "3b1b...",
    "keywords": ["best form builder", "free survey tool"]
  }'

Example response

{
  "data": {
    "tracked": 3
  }
}

Capped at your plan's tracked-keyword limit per project (Free 50, Pro 200, Business 500). Returns 403 FORBIDDEN if the request would exceed the cap.

DELETE/keywords/:id

Untrack a keyword. The id is the kw_... value returned by GET /keywords.

Example request

curl -X DELETE https://growganic.io/api/v1/keywords/kw_1 \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "deleted": true,
    "id": "kw_1"
  }
}

Soft-delete. Rank-history rows are preserved for analytics; the keyword just stops accruing new daily checks and disappears from GET /keywords. Re-track via POST /keywords/track. Returns 404 if the id does not belong to your tenant.

08

#Rankings

GET/rankings

Historical rank check data for a project.

Query params

ParamTypeDescription
projectIdrequireduuidProject to query.
keywordstringOptional. Filter to a single keyword.
daysintDefault 30, max 365. Lookback window.
limitintDefault 200, max 500.

Example request

curl "https://growganic.io/api/v1/rankings?projectId=3b1b...&days=7" \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "items": [
      {
        "keyword": "best form builder",
        "position": 4,
        "gscPosition": 3.2,
        "url": "https://acme.com/blog/best-form-builder",
        "featuredSnippet": false,
        "aiOverviewCited": true,
        "location": "US",
        "device": "desktop",
        "checkedAt": "2026-04-11T03:00:00.000Z"
      }
    ],
    "days": 7,
    "limit": 200
  }
}

Each row reports both the organic Google position and gscPosition (Google Search Console's averaged position over the day, when GSC is connected). gscPosition is null when GSC is not connected for the project, or when the keyword had no impressions in GSC for that day.

09

#Usage

GET/usage

Current month's quota meters and active plan. Use this to budget API article generation, surface 'you have N articles left' in your own UI, or back off when you hit a 429.

Example request

curl https://growganic.io/api/v1/usage \
  -H "Authorization: Bearer gro_..."

Example response

{
  "data": {
    "plan": {
      "id": "free",
      "name": "Free",
      "status": null
    },
    "meters": [
      { "label": "Published articles", "used": 1, "total": 3 },
      { "label": "keyword searches", "used": 0, "total": 20 },
      { "label": "competitor scans", "used": 0, "total": 1 },
      { "label": "reports", "used": 0, "total": 1 }
    ],
    "daysUntilReset": 4
  }
}

meters return the same counters the rate limiter enforces, so the numbers stay consistent. Lifetime plans return status: 'lifetime'. Tracked-keyword caps are per-project (see GET /keywords) and are intentionally not part of the monthly meters.

10

#Webhooks

When an article publishes, GrowGanic POSTs a signed JSON payload to a URL you control. Use it to push articles into a custom CMS, a static-site generator, or an automation tool (Zapier, Make, n8n).

The full payload schema, header reference, signature verification example, and retry policy live on the dedicated webhook integration page:

/docs/webhooks has the full reference.

Usage tips

  • Keys are tenant-scoped. Every API call is logged to your usage feed for audit.
  • Never commit keys to source control. Rotate by revoking and re-issuing.
  • Use projectId filters aggressively to keep payloads small.
  • Content generation and rank checks are async. Poll the list endpoint to detect completion.
  • If a request fails with 429, read the message field for the reset window.