# AI Content Detector API: 1 of 233 Human Posts (https://www.socialcrawl.dev/blog/detect-ai-written-social-posts)
> An AI content detector API that flags 1 of 233 human posts and catches 77% of AI posts. Filter a feed or score one text. 91% of AI posts of 40+ words caught.
An AI content detector API flagged 1 of 233 human posts at the default `likely` cut, on a 467-post bench. The same bench caught 77% of the AI posts, and 91% of AI posts of 40 words or more.
Add `label=ai_written` on a post list that accepts `label=`, or send one passage to `GET /v1/utility/ai-detection`. Both calls share one judgment and return the same `p`, `level`, and `model`. The list can drop rows, and the utility call does not.
The bodies below are the capture from 2026-10-23. The query was `remote work`, with `sort=latest`, 20 posts, from `GET /v1/twitter/search/tweets`. That page had 0 `likely` rows. It is the page that came back, not a rate of AI writing, and not the 1 of 233 bench figure.
## What does an AI text detection API return?
On a post list, an AI text detection API returns `computed.labels.ai_written`, with `p` from 0 to 1 or null, plus `level` and `model`. When you already hold the text, `GET /v1/utility/ai-detection?text=` returns those three and also `kind` (`ai_detection`), `version`, `words`, and `characters`. The model name today is `sc-ai-written-1`. A new name means a new scale, so store that name next to any `p`.
The bands are the published cuts. They were not fitted on the page below. `likely` means `p` at or above 0.95. `possible` means `p` at or above 0.71. `too_short` means under 8 words, with `p` null. No numeric floor for `unlikely` is published. The docs do not say that `possible` stops below 0.95, and they do not say that `unlikely` is everything under 0.71.
The same text gets the same judgment on the list row and on the utility call. Only the list can drop rows. The parameter is for post lists that accept `label=`. Comment lists and review lists are outside that scope. The classifier is SocialCrawl's own text classifier, trained for this question, rather than a prompt sent to a general-purpose model. Field names are on the [AI-written detection](https://www.socialcrawl.dev/docs/labels#ai-written-detection-ai_written) page.
## How do you detect AI-written posts in a feed?
You detect AI-written posts by adding `label=ai_written` to a search you already needed. On 2026-10-23 that search was latest tweets for `remote work`.
```bash
curl "https://www.socialcrawl.dev/v1/twitter/search/tweets?query=remote%20work&sort=latest&label=ai_written" \
-H "x-api-key: $SOCIALCRAWL_API_KEY"
```
The call returned 20 posts. Request `req-Vrhq0Ujb1ICrcG47` used 2 credits, and `data.labels.extra_credits` was 1. The label block reported `rows` 20, `labelled` 20, and `cached` 0. Every row's `model` was `sc-ai-written-1`. `p` was null only on the two `too_short` rows.
The JSON keeps the label on post `2113759655029834135` and `data.labels`. The other 19 items are left out. `credits_used` and `request_id` are the values recorded for this response.
```json
{
"credits_used": 2,
"request_id": "req-Vrhq0Ujb1ICrcG47",
"data": {
"items": [
{
"computed": {
"labels": {
"ai_written": {
"p": 0.904,
"level": "possible",
"model": "sc-ai-written-1"
}
}
}
}
],
"labels": {
"presets": ["ai_written"],
"rows": 20,
"labelled": 20,
"cached": 0,
"dropped": 0,
"dropped_ids": [],
"extra_credits": 1
}
}
}
```
That post is 345 words on a whitespace split of the full text, with list `p` 0.904 and `level` `possible`. Among the 10 rows with at least 40 whitespace-separated words, it had the highest `p`, so it was the text sent to the utility call. The 240-character trim of the row is below.
> Fieldwatch 1.1.31 is out with two big additions. Fieldwatch is a free, open-source Android app that identifies the devices around you, including what might be watching or following you: trackers, cameras, drones (it even decodes their Remot…
| Level | Rows |
| --- | ---: |
| likely | 0 |
| possible | 1 |
| unlikely | 17 |
| too_short | 2 |
The four counts sum to 20. One `possible` row out of 20 is not a rate of AI writing.
This page is not a set of remote-work essays. The `possible` post is a Fieldwatch note that hits on "Remote" (Remote ID) and "works". The longest `unlikely` post is 291 words at `p` 0.619, id `2113764276959658076`, and it is about a TV remote. Several other rows are near-duplicate job ads. Relevance on that search used mode `score`. It scored 20 rows, dropped 0, and added `extra_credits` 0 on that meter, so the keyword did not narrow the page.
A second search kept the query and sent `label=ai_written` with `exclude=ai_written`. Request `req-XogFotrRhODYr5Rk` returned the same 20 ids, in the same order, with the same `p`, `level`, and `model`. `dropped` was 0 and `dropped_ids` was empty. `extra_credits` was 0 because the 20 judgments were cached (`cached` 20), and `credits_used` was 1 for the search only. The response `cached` flag was false. The `possible` row stayed. Both `too_short` rows stayed.
`exclude` removed zero rows, because none were `likely`. The default drop removes `likely` only. This page had no `likely` row, so nothing was removed.
## How do you check if one text is AI-generated?
The utility call scored the exact `post.content.text` of post `2113759655029834135`, 1956 characters, not the trim above. The full text is not reprinted here.
```bash
curl -G "https://www.socialcrawl.dev/v1/utility/ai-detection" \
--data-urlencode "text=PASTE_THE_EXACT_POST_TEXT" \
-H "x-api-key: $SOCIALCRAWL_API_KEY"
```
`PASTE_THE_EXACT_POST_TEXT` stands in for that 1956-character string. The response keeps `kind`, `version`, `model`, `p`, `level`, `words`, `characters`, `credits_used`, and `request_id`.
```json
{
"data": {
"kind": "ai_detection",
"version": 1,
"model": "sc-ai-written-1",
"p": 0.904,
"level": "possible",
"words": 345,
"characters": 1956
},
"credits_used": 1,
"request_id": "req-bM4d42vsVRyKZYmQ",
"cached": false
}
```
Request `req-bM4d42vsVRyKZYmQ` cost 1 credit. `cached` was false on this call. List row and utility body match on this text. Both `p` values are 0.904, both levels are `possible`, and both name `sc-ai-written-1`. The utility `words` field is 345, the same whitespace count as the list text. The match is only for the text that was sent.
The post is 1956 characters, and the classifier reads about the first 1,000. A matching `p` does not mean the tail was scored on its own. The 1,000-character read is the labels-page rule. The utility page does not document that cut. The single-text route is [score one text](https://www.socialcrawl.dev/docs/utility).
A second call sent the exact string `Short post`. It returned `kind` `ai_detection`, `version` 1, `model` `sc-ai-written-1`, `p` null, `level` `too_short`, `words` 2, and `characters` 10. Request `req-ffY7XACAKBhJQHvM` cost 1 credit, and `cached` was false. Under 8 words the model does not guess. The `Short post` call does not re-measure the short-post line on the bench.
## What did the 467-post bench flag at likely?
These calls did not re-measure the bench. The figures here are the published bench, in [how the labels are measured](https://www.socialcrawl.dev/docs/labels#how-we-measure).
The set is 467 posts the model had not seen. 233 were written by people, from X and Reddit through November 2022, before ChatGPT existed. 234 were written by 8 current models: GPT-5.5, GPT-5.4 mini, GPT-4o mini, gpt-oss, Claude Sonnet 4.5, Claude Haiku 4.5, Gemini 3 Flash, and Grok 4.3. A third of the AI posts were prompted to sound like a person.
At `likely`, the default drop, 1 of 233 human posts was flagged. The bench caught 77% of AI posts, and 91% of AI posts of 40 words or more. At `possible` and above, 12 of 233 human posts were flagged, 95% of AI posts were caught, and 99% of AI posts of 40 words or more were caught.
Under about 40 words, `likely` catches about 2 in 5 AI posts. Gemini 3 Flash was the hardest of the 8 models, at 28% for `likely` and 79% at `possible` or above. The 77% and 95% figures are overall rates. One model can sit far from them, as Gemini 3 Flash does. No other per-model rate is published.
This AI content detection API was measured on English. Other languages, including Korean, return a score and are unmeasured. No Korean figure exists.
The score is a probability you can filter or sort on. A person can be flagged, and an AI-written post can pass. A high `p` does not identify the author. Edited text can land anywhere on the scale, whether a person rewrote a draft or a model polished a person's post.
## How does ai_style differ from ai_written?
`label=ai_style` counts checkable writing patterns and reports a `pattern_count`. The default drop is `pattern_count` at or above 2. It does not answer authorship, so a person who writes in those patterns is flagged too. `ai_written` is the probability.
Under about eight words, `ai_written` is `too_short`, `p` is null, and the row is never dropped. `ai_style` still fills counts, and `pattern_count` can still reach 2. See the [writing-pattern label](https://www.socialcrawl.dev/docs/labels#writing-pattern-labels-ai_style). This page did not request `ai_style`.
## How do you filter AI-generated social posts?
You filter AI-generated social posts with `exclude=ai_written` on the same request as `label=ai_written`. That drops `likely` only. To also drop `possible`, set `ai_written_threshold=0.71` on that same request. This page did not set it, which is why `p` 0.904 stayed.
The utility call costs 1 credit and stores nothing. Text is capped at 8,000 characters and must be URL-encoded. A call that cannot be scored is HTTP 503 and is not charged. Those are documented rules. This harvest did not capture a 503, and it did not observe storage. `ai-detection` is the exception. The utility intro that prices every utility call at zero does not cover this one.
The endpoint will accept 8,000 characters. The classifier reads about the first 1,000, which the labels page describes as nearly always the whole social post. The utility page documents the 8,000-character cap. It does not document the 1,000-character read.
On a list, posts newly judged on that request cost 1 credit per started 25. A 20-post page adds 1. The first search did that, with `extra_credits` 1 inside `credits_used` 2. Rows already scored add nothing, and a cached page of judgments adds nothing. The exclude search is the cached case. `extra_credits` was 0, `data.labels.cached` was 20, and `credits_used` was 1. The field is `data.labels.extra_credits`. `dry_run=1` shows the ceiling first, on the same meter as the other opt-in post labels.
The four data calls cost 2 credits, then 1, then 1, then 1. That is the labelled search, the Fieldwatch utility call, `Short post`, and the exclude search.
Unscored rows and `too_short` rows are never dropped. Fail-open is the name used here for that rule. The docs page does not use the word. Both short rows stayed. It had no unscored row. The docs do not state the status code for exclude without label.
## How do you start using this?
Create a key from [create an API key](https://www.socialcrawl.dev/auth/register), then open `https://www.socialcrawl.dev/dashboard/api/keys`. Both calls take one API key in `x-api-key`. The curls above send `$SOCIALCRAWL_API_KEY` to the SocialCrawl API.
The list URL is `GET https://www.socialcrawl.dev/v1/twitter/search/tweets?query=remote%20work&sort=latest&label=ai_written`. Put `exclude=ai_written` on that same URL if you want the default drop. For one passage, URL-encode the text and call `GET https://www.socialcrawl.dev/v1/utility/ai-detection?text=`. A string under 8 words, such as `Short post`, comes back `too_short` with `p` null.
`sort=latest` means a later run will not return these same 20 posts. The bands live on [AI-written detection](https://www.socialcrawl.dev/docs/labels#ai-written-detection-ai_written), and the single-text call is [score one text](https://www.socialcrawl.dev/docs/utility). When `possible` should leave the feed too, add `ai_written_threshold=0.71` beside `label=ai_written` and `exclude=ai_written`.
## Frequently asked questions
### How much does an AI content detector API call cost?
The utility call costs 1 credit. On a post list, rows newly judged on that request cost 1 credit per started 25 posts, so a 20-post page adds 1 credit on top of the search. Request `req-Vrhq0Ujb1ICrcG47` returned `credits_used` 2 and `data.labels.extra_credits` 1. A call that cannot be scored is HTTP 503 and is not charged. Other prices are on [endpoint pricing](https://www.socialcrawl.dev/docs/endpoint-pricing) and the [pricing page](https://www.socialcrawl.dev/pricing).
### What does a post under 8 words return?
The exact string `Short post` returned `p` null, `level` `too_short`, `words` 2, and `characters` 10, on model `sc-ai-written-1`. Request `req-ffY7XACAKBhJQHvM` cost 1 credit. A `too_short` row is never dropped. That call is only the under-8-word path. On the bench, under about 40 words, `likely` catches about 2 in 5 AI posts, which this two-word string did not measure.
### Does the score cover languages other than English?
English is what the 467-post bench measured. A text in another language, including Korean, still receives a score from `sc-ai-written-1`. Those scores are unmeasured. No Korean accuracy number was published, so a Korean `likely` is not the 1-of-233 human figure and not the 77% figure for AI posts.
### Is a high score proof a model wrote the post?
The score is a probability, not proof. On the published bench, `likely` flagged 1 of 233 human posts, and `possible` and above flagged 12 of 233. The live row at `p` 0.904 was `possible` and stayed in the feed, because the default drop is `likely` only and this page did not set `ai_written_threshold=0.71`. Edited text can land anywhere on the scale, whether a person rewrote a draft or a model polished a person's post. Do not read the Fieldwatch row as an accusation against its author.