Needs keys Creates a public key for browser use, such as the website chat widget or a web call button. reCAPTCHA checks need a Google reCAPTCHA v3 secret (RECAPTCHA_SECRET) on the server; without it they run in mock mode.
How public keys work#
A browser sends Authorization: Bearer public_key_.... Tried accepts it only when:
- The request's
Originhost matches an entry inallowed_domains. Without an allowed Origin the request gets401. - The route is one of
POST /v3/create-web-call,POST /create-chat,POST /create-chat-completionorPOST /end-chat/{id}. Any other route gets403withA public key can only start web calls and chats.
Then, on every request:
- Agent limits. Web calls always use the key's
agent_id; asking for another agent returns403. Chats usechat_agents(weighted A/B pick) orchat_agent_id; a chat agent outside that list returns403. Chat completions and end-chat only work on chats started with the same key. - Blocked countries. The client country comes from the
cf-ipcountry,x-vercel-ip-countryorx-country-codeheader set by your CDN or load balancer. A listed country gets403. - Rate limits. 30 requests per minute per client IP on each key (set per deployment with
PUBLIC_KEY_IP_RATE_PER_MINUTE), plus the key's ownrate_limit_per_minuteif set. Over either limit returns429. - reCAPTCHA v3. With
recaptcha_enabled, the browser sends a token in the body asrecaptcha_tokenor in thex-recaptcha-tokenheader. A missing token, a failed check or a score belowrecaptcha_thresholdreturns403. In mock mode a token ofmock:<score>scores that value, any other token scores 0.9, and no token scores 0.
Calls and chats started with a public key carry public_key_id and origin in their metadata.
Request#
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | Label, 1 to 80 characters. Default Website widget. |
allowed_domains |
string[] | Yes | 1 to 20 hosts the key works from: example.com, *.example.com (also matches example.com itself) or localhost. Add :port to match one port only. |
agent_id |
string or null | One of three | Voice agent for browser web calls. |
chat_agent_id |
string or null | One of three | Chat agent for chats started with this key. |
chat_agents |
object[] | One of three | Up to 10 { "chat_agent_id": string, "weight": number } entries for an A/B split. Weights are 0 or more and need not add up to 1; an entry with weight 0 is never picked. Takes priority over chat_agent_id. |
recaptcha_enabled |
boolean | No | Require a reCAPTCHA v3 token. Default false. |
recaptcha_threshold |
number | No | Lowest accepted score, 0 to 1. Default 0.5. |
rate_limit_per_minute |
integer or null | No | Requests per minute across all browsers using this key, 1 to 10000. null means no key-wide limit (the per-IP limit still applies). |
blocked_countries |
string[] | No | Up to 250 two-letter country codes to reject. Stored in upper case. |
You must set at least one of agent_id, chat_agent_id or chat_agents. Every agent you name must exist in the workspace.
Example request#
curl -X POST https://api.tried.com.au/public-keys \
-H "Authorization: Bearer $TRIED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Website widget",
"allowed_domains": ["example.com.au", "*.example.com.au"],
"chat_agent_id": "chat_agent_1e5b9d3f7a2c4e6b8d0f2a4c",
"agent_id": "agent_2f8d4b6a0c1e3f5a7b9d1c3e",
"recaptcha_enabled": true,
"rate_limit_per_minute": 60
}'const res = await fetch("https://api.tried.com.au/public-keys", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TRIED_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Website widget",
allowed_domains: ["example.com.au", "*.example.com.au"],
chat_agent_id: "chat_agent_1e5b9d3f7a2c4e6b8d0f2a4c",
agent_id: "agent_2f8d4b6a0c1e3f5a7b9d1c3e",
recaptcha_enabled: true,
rate_limit_per_minute: 60,
}),
});
const key = await res.json();import os, requests
res = requests.post(
"https://api.tried.com.au/public-keys",
headers={"Authorization": f"Bearer {os.environ['TRIED_API_KEY']}"},
json={
"name": "Website widget",
"allowed_domains": ["example.com.au", "*.example.com.au"],
"chat_agent_id": "chat_agent_1e5b9d3f7a2c4e6b8d0f2a4c",
"agent_id": "agent_2f8d4b6a0c1e3f5a7b9d1c3e",
"recaptcha_enabled": True,
"rate_limit_per_minute": 60,
},
)
key = res.json()Response#
201 Created
{
"public_key_id": "pk_4a7e1c9b2d0f5e83a6c1b7d2",
"name": "Website widget",
"public_key": "public_key_9c2e4f7a1b3d5e6f8a0c2e4b6d8f1a3c",
"chat_agent_id": "chat_agent_1e5b9d3f7a2c4e6b8d0f2a4c",
"allowed_domains": ["example.com.au", "*.example.com.au"],
"created_timestamp": 1760313600000,
"chat_agents": [],
"agent_id": "agent_2f8d4b6a0c1e3f5a7b9d1c3e",
"recaptcha_enabled": true,
"recaptcha_threshold": 0.5,
"rate_limit_per_minute": 60,
"blocked_countries": []
}| Field | Type | Description |
|---|---|---|
public_key_id |
string | ID used to update or delete the key. |
public_key |
string | The value browsers send as Bearer. public_key_ followed by 32 hex characters. It is not secret: it only works from allowed_domains and only on the routes listed above. |
chat_agents |
object[] | The A/B split. [] when not set. |
The create is recorded in the audit log as public_key.create.
Errors#
| Status | When |
|---|---|
400 |
Set chat_agent_id, chat_agents or agent_id, or a field fails validation (for example Use domains like example.com, *.example.com or localhost, or an empty allowed_domains). |
401 |
Missing or invalid API key. |
403 |
The key's IP allowlist or scopes reject the request. This route needs the admin scope. Dashboard members get The member role is read-only. |
404 |
A named agent does not exist, for example Chat agent chat_agent_1e5b9d3f7a2c4e6b8d0f2a4c not found or Agent agent_2f8d4b6a0c1e3f5a7b9d1c3e not found. |
429 |
Rate limit exceeded. See Rate limits. |
See Errors for the body format.