API reference

How to call dogdog site, prompt, image, and video checks.

Last updated: 2026-10-09

Base URL

Use the host where dogdog is running. Locally that is http://localhost:3000.

Every check is stored on the account that owns the API key. One account cannot read another account's history.

Authentication

Create a key in the console at /settings/apikeys. Send it on every request:

Authorization: Bearer sk_your_key

x-api-key: sk_your_key is also accepted. The browser console uses the signed-in session and does not need this header.

Missing or invalid credentials return HTTP 401:

{ "code": -1, "message": "Unauthorized" }

Response envelope

A finished check returns HTTP 200 even when the content is blocked. Read data.allowed. Do not treat HTTP 200 as approval.

{
  "code": 0,
  "message": "ok",
  "data": {
    "id": "record id",
    "allowed": false,
    "decision": "block",
    "categories": ["explicit_sexual_content"],
    "code": "policy_violation",
    "message": "Content does not meet the configured moderation policy.",
    "summary": "One short sentence.",
    "findings": []
  }
}
  • allow / allowed: true / approved — the check finished and no category matched
  • block / allowed: false / policy_violation — at least one category matched
  • review / allowed: false / manual_review — the model was uncertain or did not finish
  • review / allowed: false / moderation_unavailable — the model timed out or the provider failed
  • error / allowed: false / invalid_input — the request body is unusable
  • error / allowed: false / invalid_provider_response — the model reply could not be parsed
  • error / allowed: false / crawl_failed — the site could not be fetched

A provider failure never becomes allow.

Prompt check

POST /api/v1/moderate/prompt

curl -X POST "$BASE/api/v1/moderate/prompt" \
  -H "Authorization: Bearer sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"A paper boat floats across a quiet pond."}'

prompt is a string, at most 20,000 characters. An empty string returns invalid_input.

There is no keyword list. Grok screens the text against this policy:

  • minor_sexual_exploitation — sexual content involving anyone under 18
  • explicit_sexual_content — explicit sexual acts, sexual nudity, or pornography
  • celebrity_sexual_likeness — sexual or nude content using a real person's likeness
  • nonconsensual_intimate — non-consensual intimate imagery
  • graphic_violence
  • self_harm_encouragement
  • hate_or_threats
  • dangerous_instructions
  • privacy_abuse

findings is always an empty array. Matched categories are in categories. A mention in news, medical, or prevention context is not a violation by itself.

Image check

POST /api/v1/moderate/image

curl -X POST "$BASE/api/v1/moderate/image" \
  -H "Authorization: Bearer sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"imageUrl":"https://example.com/photo.jpg","prompt":"optional note"}'

imageUrl must be a public http or https URL. A data: URL returns invalid_input. To upload a file instead, send multipart/form-data with a file field. JPEG, PNG, WebP, and GIF are accepted, up to 8 MB.

curl -X POST "$BASE/api/v1/moderate/image" \
  -H "Authorization: Bearer sk_your_key" \
  -F "file=@photo.jpg" \
  -F "prompt=optional note"

prompt is an optional note, at most 2,000 characters.

The policy is the same as the prompt check. checked_images must be 1 and the image must actually be readable. If the model cannot see the image, the decision is review.

Video check

POST /api/v1/moderate/video

curl -X POST "$BASE/api/v1/moderate/video" \
  -H "Authorization: Bearer sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"videoUrl":"https://example.com/clip.mp4","prompt":"optional note"}'

videoUrl must be a public http or https URL for an mp4, webm, or mov file, at most 32 MB. dogdog downloads the file and samples three frames. Those frames are screened with the same policy as an image check. Audio is not transcribed. A private address or a data: URL returns invalid_input. If the file cannot be fetched or decoded, or the frames cannot be assessed, the decision is review or error.

To upload a file instead, send multipart/form-data with a file field. MP4, WebM, and MOV are accepted, up to 32 MB.

curl -X POST "$BASE/api/v1/moderate/video" \
  -H "Authorization: Bearer sk_your_key" \
  -F "file=@clip.mp4" \
  -F "prompt=optional note"

prompt is an optional note, at most 2,000 characters.

Site scan

POST /api/v1/scans

curl -X POST "$BASE/api/v1/scans" \
  -H "Authorization: Bearer sk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","scope":"page","providers":["stripe","paypal"]}'

url is required. scope is page or site. page checks only that URL. site, the default when scope is omitted, also reads the sitemap and same-site pages. providers may be stripe, paypal, or both. Omitting it checks both.

A full-site scan can detect languages first. POST /api/v1/scans/languages with {"url":"https://example.com"} returns the languages declared on that same host, from hreflang, the page language, Content-Language, and language path prefixes, plus each language's entry URL. Pass languages to POST /api/v1/scans, for example ["en","zh-CN"]. One, several, or every detected code is valid. The crawl then keeps those language entry pages and same-language pages, still up to 8 pages. Omitting languages does not filter by language.

The crawler only fetches public http and https pages on the same host. Localhost, private networks, and redirects to another host return crawl_failed. It reads the homepage, robots.txt sitemaps, /sitemap.xml, and same-site links, up to 8 pages, and prefers pricing, checkout, terms, and privacy URLs.

Visible text is matched against a fixed keyword list, then a shortened copy of the pages is sent to the model. High-severity keywords or a model block become block. allow is returned only when the model allows the site and no keyword matched. Medium-severity keywords, an uncertain model, or a model outage become review.

Site categories are adult_content, minor_sexual_content, celebrity_sexual_likeness, gambling, illegal_drugs, weapons, counterfeit, get_rich_quick, hate_or_violence, and other_restricted.

data.pages lists the URLs that were read. data.findings lists hits:

{
  "provider": "stripe",
  "category": "gambling",
  "severity": "high",
  "url": "https://example.com/pricing",
  "evidence": "short nearby text"
}

History

GET /api/v1/records

GET /api/v1/records?kind=site

kind is site, prompt, image, or video. The list is the newest 50 rows for the key's owner.

GET /api/v1/records/{id}

Returns that row, including the stored detail. Another account's id returns HTTP 404 with { "code": -1, "message": "Not found" }.