Authentication
Send Authorization: Bearer <key>. Keys are up_live_… (billed), up_test_… (for building: one daily cap per account, never billed on a paid plan) and up_pk_… (publishable: for browsers, /v1/check only, limited to the project's allowed origins). A secret key sent from a browser is refused. The Perspective endpoint also accepts the key as ?key=.
Limits
- Text up to 10,000 characters; 64 in the username and display_name contexts.
- Up to 100 items per batch.
- Bursts per key per minute by plan: free 60, hobby 120, starter 600, growth 3,000, scale 12,000, research 600, enterprise 12,000. The
RateLimit-Policyheader on every response says which applies; a refusal is a 429 withRetry-After.
Response headers
Request-Id: quote it when writing to support.Unprofane-Engine: the engine version that answered.Unprofane-Route:rules,modelorcache.Server-Timing: where the milliseconds went.
POST /v1/check
Check one text.
curl -X POST https://api.unprofane.com/v1/check \
-H "Authorization: Bearer up_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"xXsh1tXx","context":"username","policy":"teen"}'const res = await fetch('https://api.unprofane.com/v1/check', {
method: 'POST',
headers: { Authorization: 'Bearer up_live_YOUR_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({
"text": "xXsh1tXx",
"context": "username",
"policy": "teen"
}),
});
const result = await res.json();import httpx
r = httpx.request(
"POST",
"https://api.unprofane.com/v1/check",
headers={"Authorization": "Bearer up_live_YOUR_KEY"},
json={
"text": "xXsh1tXx",
"context": "username",
"policy": "teen",
},
timeout=5.0,
)
result = r.json()req, _ := http.NewRequest("POST", "https://api.unprofane.com/v1/check",
strings.NewReader(`{"text":"xXsh1tXx","context":"username","policy":"teen"}`))
req.Header.Set("Authorization", "Bearer up_live_YOUR_KEY")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)$res = file_get_contents('https://api.unprofane.com/v1/check', false, stream_context_create(['http' => [
'method' => 'POST',
'header' => "Authorization: Bearer up_live_YOUR_KEY\r\nContent-Type: application/json\r\n",
'content' => '{"text":"xXsh1tXx","context":"username","policy":"teen"}',
]]));
$result = json_decode($res, true);require 'net/http'; require 'json'
uri = URI('https://api.unprofane.com/v1/check')
req = Net::HTTP::Post.new(uri, 'Authorization' => 'Bearer up_live_YOUR_KEY', 'Content-Type' => 'application/json')
req.body = '{"text":"xXsh1tXx","context":"username","policy":"teen"}'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
result = JSON.parse(res.body)using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "up_live_YOUR_KEY");
var req = new HttpRequestMessage(HttpMethod.Post, "https://api.unprofane.com/v1/check");
req.Content = new StringContent("{\"text\":\"xXsh1tXx\",\"context\":\"username\",\"policy\":\"teen\"}", Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
var result = await res.Content.ReadFromJsonAsync<JsonElement>();var http := HTTPRequest.new()
add_child(http)
http.request_completed.connect(func(_r, _code, _h, body):
var result = JSON.parse_string(body.get_string_from_utf8())
print(result))
http.request("https://api.unprofane.com/v1/check",
PackedStringArray(["Authorization: Bearer up_live_YOUR_KEY", "Content-Type: application/json"]),
HTTPClient.METHOD_POST, "{\"text\":\"xXsh1tXx\",\"context\":\"username\",\"policy\":\"teen\"}")Request: CheckRequest
| Field | Type | Default | Meaning |
|---|---|---|---|
text * | string | The text to check. At most 10,000 characters; 64 in the username and display_name contexts. | |
context | "general" | "username" | "display_name" | "comment" | "chat" | "gaming" | "educational" | "creative" | "review" | "custom_text" | "general" | What the text is: general, username, display_name, comment, chat, gaming, educational, creative, review or custom_text. Usernames are split into words and checked reversed; a comment is read as running text; in gaming a taunt about play is not acted on, under every policy but kids; in educational a word quoted or named is discussed, and in creative a quotation is dialogue. |
policy | "kids" | "teen" | "standard" | "strict" | string (^pol_[A-Za-z0-9]{8,32}$) | object | "standard" | A preset (kids, teen, standard, strict), a saved policy id (pol_…) from the dashboard, or an object mapping each category to allow, review or block. |
mode | "fast" | "balanced" | "thorough" | "balanced" | fast never asks the model; balanced asks it about every text the rules do not block outright; thorough asks it about every text. |
language | "auto" | "en" | "es" | "pt" | "fr" | "de" | "it" | "nl" | "pl" | "tr" | "ru" | "ar" | "he" | "hi" | "id" | "ja" | "auto" | auto detects. Set a code to skip detection when you already know the language of the text. |
mask | "asterisk" | "first_letter" | "grawlix" | "remove" | "asterisk" | How the censored string is written: asterisk (s***), first_letter (s***), grawlix (#@!%) or remove. |
engine | string (^\d{4}-\d{2}-\d{2}$) | A dated engine version to pin this request to, such as 2026-09-29. Defaults to the project pin, then the latest stable. | |
lists | object | Allow and block terms for this request only, added to the project's lists: up to 50 of each, 64 characters each, read through the same disguises as the text. An allowed phrase clears only the words inside it. Not accepted with a publishable key. |
Response: CheckResponse
| Field | Type | Meaning |
|---|---|---|
id | string (^chk_[A-Za-z0-9]{16,32}$) | The check id (chk_…), for a feedback call. Checks are not stored; this is a correlation handle. |
allowed | boolean | True when the action is allow. |
action | "allow" | "review" | "block" | allow, review or block, from the policy applied to the categories. |
score | number | The highest category score, 0 to 1. |
severity | "none" | "mild" | "moderate" | "strong" | "extreme" | none, mild, moderate, strong or extreme: the strongest matched term, or the model’s answer. |
categories | object | A score from 0 to 1 per policy category: mild_profanity, profanity, sexual, insult, slur, threat, rude, sexual_minors and self_harm (the last three the model's alone) and reserved. |
obfuscated | boolean | True when any match was reached through a named evasion transform. |
evasion | "homoglyph" | "zero_width" | "separator" | "leetspeak" | "repeated_chars" | "diacritics" | "mixed_script" | "fullwidth" | "combining_marks" | "phonetic" | "reversed" | "concatenation" | "case_mixing" | "self_censored"[] | Every evasion tag that fired anywhere in the text, deduplicated. |
matches | object[] | Each matched term with its UTF-16 span in the original, the text as typed, the normalized form, the term id, its category, severity and the evasion tags on that span. |
censored | string | The original text with every match masked as `mask` asks. |
reason | string | Why the action is what it is, in one sentence written from the matches and the policy, for a moderator or a log: the words read, their disguise, and what the model read where it was asked. Decide on `action`; the wording may change. |
language | object | The detected language code and the detector’s confidence. |
engine | object | Which version answered, which route (rules, model or cache), and whether the answer is degraded because the model was unavailable. |
timing_ms | number | Time inside the engine, in milliseconds. |
POST /v1/check/batch
Check up to 100 texts.
curl -X POST https://api.unprofane.com/v1/check/batch \
-H "Authorization: Bearer up_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"items":[{"id":"a","text":"Have a lovely day"},{"id":"b","text":"f.u.c.k off"}],"context":"comment","policy":"standard"}'const res = await fetch('https://api.unprofane.com/v1/check/batch', {
method: 'POST',
headers: { Authorization: 'Bearer up_live_YOUR_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({
"items": [
{
"id": "a",
"text": "Have a lovely day"
},
{
"id": "b",
"text": "f.u.c.k off"
}
],
"context": "comment",
"policy": "standard"
}),
});
const result = await res.json();import httpx
r = httpx.request(
"POST",
"https://api.unprofane.com/v1/check/batch",
headers={"Authorization": "Bearer up_live_YOUR_KEY"},
json={
"items": [{
"id": "a",
"text": "Have a lovely day",
}, {
"id": "b",
"text": "f.u.c.k off",
}],
"context": "comment",
"policy": "standard",
},
timeout=5.0,
)
result = r.json()req, _ := http.NewRequest("POST", "https://api.unprofane.com/v1/check/batch",
strings.NewReader(`{"items":[{"id":"a","text":"Have a lovely day"},{"id":"b","text":"f.u.c.k off"}],"context":"comment","policy":"standard"}`))
req.Header.Set("Authorization", "Bearer up_live_YOUR_KEY")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)$res = file_get_contents('https://api.unprofane.com/v1/check/batch', false, stream_context_create(['http' => [
'method' => 'POST',
'header' => "Authorization: Bearer up_live_YOUR_KEY\r\nContent-Type: application/json\r\n",
'content' => '{"items":[{"id":"a","text":"Have a lovely day"},{"id":"b","text":"f.u.c.k off"}],"context":"comment","policy":"standard"}',
]]));
$result = json_decode($res, true);require 'net/http'; require 'json'
uri = URI('https://api.unprofane.com/v1/check/batch')
req = Net::HTTP::Post.new(uri, 'Authorization' => 'Bearer up_live_YOUR_KEY', 'Content-Type' => 'application/json')
req.body = '{"items":[{"id":"a","text":"Have a lovely day"},{"id":"b","text":"f.u.c.k off"}],"context":"comment","policy":"standard"}'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
result = JSON.parse(res.body)using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "up_live_YOUR_KEY");
var req = new HttpRequestMessage(HttpMethod.Post, "https://api.unprofane.com/v1/check/batch");
req.Content = new StringContent("{\"items\":[{\"id\":\"a\",\"text\":\"Have a lovely day\"},{\"id\":\"b\",\"text\":\"f.u.c.k off\"}],\"context\":\"comment\",\"policy\":\"standard\"}", Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
var result = await res.Content.ReadFromJsonAsync<JsonElement>();var http := HTTPRequest.new()
add_child(http)
http.request_completed.connect(func(_r, _code, _h, body):
var result = JSON.parse_string(body.get_string_from_utf8())
print(result))
http.request("https://api.unprofane.com/v1/check/batch",
PackedStringArray(["Authorization: Bearer up_live_YOUR_KEY", "Content-Type: application/json"]),
HTTPClient.METHOD_POST, "{\"items\":[{\"id\":\"a\",\"text\":\"Have a lovely day\"},{\"id\":\"b\",\"text\":\"f.u.c.k off\"}],\"context\":\"comment\",\"policy\":\"standard\"}")Request: BatchRequest
| Field | Type | Default | Meaning |
|---|---|---|---|
items * | object[] | Up to 100 items, each with an id you choose (returned in order) and a text; an item may override context and policy. | |
context | "general" | "username" | "display_name" | "comment" | "chat" | "gaming" | "educational" | "creative" | "review" | "custom_text" | "general" | The context for every item that does not set its own. |
policy | "kids" | "teen" | "standard" | "strict" | string (^pol_[A-Za-z0-9]{8,32}$) | object | "standard" | The policy for every item that does not set its own. |
mode | "fast" | "balanced" | "thorough" | "balanced" | The mode for the whole batch. Four model calls run at a time. |
mask | "asterisk" | "first_letter" | "grawlix" | "remove" | "asterisk" | The mask for the whole batch. |
engine | string (^\d{4}-\d{2}-\d{2}$) | A dated engine version for the whole batch. | |
lists | object | Allow and block terms for every item, added to the project's lists. |
Response: BatchResponse
| Field | Type | Meaning |
|---|---|---|
results | object | object[] | One entry per item, in order: { id, result } on success or { id, error } when that item alone failed. One bad item never fails the batch. |
counted | integer | How many items counted against the allowance. |
POST /v1/feedback
Send a correction.
curl -X POST https://api.unprofane.com/v1/feedback \
-H "Authorization: Bearer up_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"check_id":"chk_0123456789abcdefGHIJ","verdict":"false_positive","expected_action":"allow"}'const res = await fetch('https://api.unprofane.com/v1/feedback', {
method: 'POST',
headers: { Authorization: 'Bearer up_live_YOUR_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({
"check_id": "chk_0123456789abcdefGHIJ",
"verdict": "false_positive",
"expected_action": "allow"
}),
});
const result = await res.json();import httpx
r = httpx.request(
"POST",
"https://api.unprofane.com/v1/feedback",
headers={"Authorization": "Bearer up_live_YOUR_KEY"},
json={
"check_id": "chk_0123456789abcdefGHIJ",
"verdict": "false_positive",
"expected_action": "allow",
},
timeout=5.0,
)
result = r.json()req, _ := http.NewRequest("POST", "https://api.unprofane.com/v1/feedback",
strings.NewReader(`{"check_id":"chk_0123456789abcdefGHIJ","verdict":"false_positive","expected_action":"allow"}`))
req.Header.Set("Authorization", "Bearer up_live_YOUR_KEY")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)$res = file_get_contents('https://api.unprofane.com/v1/feedback', false, stream_context_create(['http' => [
'method' => 'POST',
'header' => "Authorization: Bearer up_live_YOUR_KEY\r\nContent-Type: application/json\r\n",
'content' => '{"check_id":"chk_0123456789abcdefGHIJ","verdict":"false_positive","expected_action":"allow"}',
]]));
$result = json_decode($res, true);require 'net/http'; require 'json'
uri = URI('https://api.unprofane.com/v1/feedback')
req = Net::HTTP::Post.new(uri, 'Authorization' => 'Bearer up_live_YOUR_KEY', 'Content-Type' => 'application/json')
req.body = '{"check_id":"chk_0123456789abcdefGHIJ","verdict":"false_positive","expected_action":"allow"}'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
result = JSON.parse(res.body)using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "up_live_YOUR_KEY");
var req = new HttpRequestMessage(HttpMethod.Post, "https://api.unprofane.com/v1/feedback");
req.Content = new StringContent("{\"check_id\":\"chk_0123456789abcdefGHIJ\",\"verdict\":\"false_positive\",\"expected_action\":\"allow\"}", Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
var result = await res.Content.ReadFromJsonAsync<JsonElement>();var http := HTTPRequest.new()
add_child(http)
http.request_completed.connect(func(_r, _code, _h, body):
var result = JSON.parse_string(body.get_string_from_utf8())
print(result))
http.request("https://api.unprofane.com/v1/feedback",
PackedStringArray(["Authorization: Bearer up_live_YOUR_KEY", "Content-Type: application/json"]),
HTTPClient.METHOD_POST, "{\"check_id\":\"chk_0123456789abcdefGHIJ\",\"verdict\":\"false_positive\",\"expected_action\":\"allow\"}")Request: FeedbackRequest
| Field | Type | Default | Meaning |
|---|---|---|---|
check_id * | string (^chk_[A-Za-z0-9]{16,32}$) | The id from the response you are correcting. | |
verdict * | "false_positive" | "false_negative" | "correct" | false_positive, false_negative or correct. | |
expected_action | "allow" | "review" | "block" | What the action should have been, when you know. | |
note | string | Up to 500 characters for a person to read. | |
text | string | The text itself, only if you want us to have it. This is the one field that is stored; it leaves with your account. |
Response: FeedbackResponse
| Field | Type | Meaning |
|---|---|---|
id | string | The feedback id (fb_…). |
received | boolean | Always true on a 202. |
GET /v1/usage
Read this period’s usage.
curl -X GET https://api.unprofane.com/v1/usage \
-H "Authorization: Bearer up_live_YOUR_KEY"const res = await fetch('https://api.unprofane.com/v1/usage', {
method: 'GET',
headers: { Authorization: 'Bearer up_live_YOUR_KEY' },
});
const result = await res.json();import httpx
r = httpx.request(
"GET",
"https://api.unprofane.com/v1/usage",
headers={"Authorization": "Bearer up_live_YOUR_KEY"},
timeout=5.0,
)
result = r.json()req, _ := http.NewRequest("GET", "https://api.unprofane.com/v1/usage",
nil)
req.Header.Set("Authorization", "Bearer up_live_YOUR_KEY")
res, err := http.DefaultClient.Do(req)$res = file_get_contents('https://api.unprofane.com/v1/usage', false, stream_context_create(['http' => [
'method' => 'GET',
'header' => "Authorization: Bearer up_live_YOUR_KEY\r\n",
]]));
$result = json_decode($res, true);require 'net/http'; require 'json'
uri = URI('https://api.unprofane.com/v1/usage')
req = Net::HTTP::Get.new(uri, 'Authorization' => 'Bearer up_live_YOUR_KEY')
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
result = JSON.parse(res.body)using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "up_live_YOUR_KEY");
var req = new HttpRequestMessage(HttpMethod.Get, "https://api.unprofane.com/v1/usage");
var res = await http.SendAsync(req);
var result = await res.Content.ReadFromJsonAsync<JsonElement>();var http := HTTPRequest.new()
add_child(http)
http.request_completed.connect(func(_r, _code, _h, body):
var result = JSON.parse_string(body.get_string_from_utf8())
print(result))
http.request("https://api.unprofane.com/v1/usage",
PackedStringArray(["Authorization: Bearer up_live_YOUR_KEY"]),
HTTPClient.METHOD_GET)Response: UsageResponse
| Field | Type | Meaning |
|---|---|---|
period | object | The current billing period’s start and end, ISO 8601. |
plan | string | The account’s plan. |
checks | integer | Billable checks so far this period, from closed hours; the current hour is not yet in. |
included | integer | null | The plan’s allowance, or null for a custom plan. |
overage | integer | Checks past the allowance so far. |
routes | object | The share of checks answered by the rules, the model and the cache. |
degraded | number | The share of checks answered by the rules because the model was unavailable. |
GET /v1/projects/prj_YOUR_PROJECT_ID/lists/allow
Read a project list.
curl -X GET https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow \
-H "Authorization: Bearer up_live_YOUR_KEY"const res = await fetch('https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow', {
method: 'GET',
headers: { Authorization: 'Bearer up_live_YOUR_KEY' },
});
const result = await res.json();import httpx
r = httpx.request(
"GET",
"https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow",
headers={"Authorization": "Bearer up_live_YOUR_KEY"},
timeout=5.0,
)
result = r.json()req, _ := http.NewRequest("GET", "https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow",
nil)
req.Header.Set("Authorization", "Bearer up_live_YOUR_KEY")
res, err := http.DefaultClient.Do(req)$res = file_get_contents('https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow', false, stream_context_create(['http' => [
'method' => 'GET',
'header' => "Authorization: Bearer up_live_YOUR_KEY\r\n",
]]));
$result = json_decode($res, true);require 'net/http'; require 'json'
uri = URI('https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow')
req = Net::HTTP::Get.new(uri, 'Authorization' => 'Bearer up_live_YOUR_KEY')
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
result = JSON.parse(res.body)using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "up_live_YOUR_KEY");
var req = new HttpRequestMessage(HttpMethod.Get, "https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow");
var res = await http.SendAsync(req);
var result = await res.Content.ReadFromJsonAsync<JsonElement>();var http := HTTPRequest.new()
add_child(http)
http.request_completed.connect(func(_r, _code, _h, body):
var result = JSON.parse_string(body.get_string_from_utf8())
print(result))
http.request("https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow",
PackedStringArray(["Authorization: Bearer up_live_YOUR_KEY"]),
HTTPClient.METHOD_GET)Response: List
| Field | Type | Meaning |
|---|---|---|
kind | "allow" | "block" | "reserved" | allow, block or reserved. |
terms | string[] | The terms as they were sent, sorted. |
version | integer | Bumped on every change; keys pick the new list up within a minute. |
PUT /v1/projects/prj_YOUR_PROJECT_ID/lists/allow
Replace a project list.
curl -X PUT https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow \
-H "Authorization: Bearer up_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"terms":["Scunthorpe","Dickinson","cockatoo"]}'const res = await fetch('https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow', {
method: 'PUT',
headers: { Authorization: 'Bearer up_live_YOUR_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({
"terms": [
"Scunthorpe",
"Dickinson",
"cockatoo"
]
}),
});
const result = await res.json();import httpx
r = httpx.request(
"PUT",
"https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow",
headers={"Authorization": "Bearer up_live_YOUR_KEY"},
json={
"terms": ["Scunthorpe", "Dickinson", "cockatoo"],
},
timeout=5.0,
)
result = r.json()req, _ := http.NewRequest("PUT", "https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow",
strings.NewReader(`{"terms":["Scunthorpe","Dickinson","cockatoo"]}`))
req.Header.Set("Authorization", "Bearer up_live_YOUR_KEY")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)$res = file_get_contents('https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow', false, stream_context_create(['http' => [
'method' => 'PUT',
'header' => "Authorization: Bearer up_live_YOUR_KEY\r\nContent-Type: application/json\r\n",
'content' => '{"terms":["Scunthorpe","Dickinson","cockatoo"]}',
]]));
$result = json_decode($res, true);require 'net/http'; require 'json'
uri = URI('https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow')
req = Net::HTTP::Put.new(uri, 'Authorization' => 'Bearer up_live_YOUR_KEY', 'Content-Type' => 'application/json')
req.body = '{"terms":["Scunthorpe","Dickinson","cockatoo"]}'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
result = JSON.parse(res.body)using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "up_live_YOUR_KEY");
var req = new HttpRequestMessage(HttpMethod.Put, "https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow");
req.Content = new StringContent("{\"terms\":[\"Scunthorpe\",\"Dickinson\",\"cockatoo\"]}", Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
var result = await res.Content.ReadFromJsonAsync<JsonElement>();var http := HTTPRequest.new()
add_child(http)
http.request_completed.connect(func(_r, _code, _h, body):
var result = JSON.parse_string(body.get_string_from_utf8())
print(result))
http.request("https://api.unprofane.com/v1/projects/prj_YOUR_PROJECT_ID/lists/allow",
PackedStringArray(["Authorization: Bearer up_live_YOUR_KEY", "Content-Type: application/json"]),
HTTPClient.METHOD_PUT, "{\"terms\":[\"Scunthorpe\",\"Dickinson\",\"cockatoo\"]}")Request: ListPut
| Field | Type | Default | Meaning |
|---|---|---|---|
terms * | string[] | Up to 5,000 terms of up to 64 characters. The list is replaced, not merged. |
Response: List
| Field | Type | Meaning |
|---|---|---|
kind | "allow" | "block" | "reserved" | allow, block or reserved. |
terms | string[] | The terms as they were sent, sorted. |
version | integer | Bumped on every change; keys pick the new list up within a minute. |
DELETE /v1/projects/{id}/lists/{kind}
Empty a list. Answers 204. Same auth as the list reads.
POST /v1alpha1/comments:analyze
Byte-compatible with Google's Perspective API: the same request body, the same response, the same error envelope, the key accepted as ?key=. comments:suggestscore is answered too. The migration hub has the guides; the attribute names and languages accepted are the production set from Google's discovery document.
GET /health
No auth. Returns { ok, engine, lexicon }.
Problem details
Every failure is application/problem+json (RFC 9457) with a stable type. The types:
| Type | Status | Title |
|---|---|---|
invalid-request | 400 | The request body is not valid. |
unauthorized | 401 | No valid API key. |
key-revoked | 401 | This API key was revoked. |
forbidden-origin | 403 | This origin is not allowed for this publishable key. |
forbidden-secret-cors | 403 | Secret keys cannot be used from a browser. |
not-found | 404 | No such resource. |
method-not-allowed | 405 | Method not allowed. |
unsupported-media-type | 415 | Send application/json. |
payload-too-large | 413 | The request body is too large. |
subscription-required | 402 | A plan is required. |
quota-exceeded | 429 | This plan’s monthly checks are used up. |
rate-limited | 429 | Too many requests. |
test-key-daily-cap | 429 | Test keys are capped per day. |
engine-unknown | 400 | No such engine version. |
misconfigured | 503 | The server is temporarily misconfigured. |