Skip to navigation

Safety Identifiers

Send a stable, opaque id for the person behind each request, never their personal data

Overview

safety_identifier is an optional field on every text-to-speech request. It names the end user of your application on whose behalf the request is made.

Send it when many of your own users share one Speechify workspace: a gateway or reseller, a consumer app, or any product where the person typing the text is not you. Without it, every request on your workspace looks the same to us, so the only lever against one abusive user is your whole workspace. With it, we can attribute, limit or block that one user and leave everyone else alone.

Omitting it changes nothing. The field follows the same convention as OpenAI’s safety_identifier, so a hash you already send there works here unchanged.

Sending it

The field is accepted in the JSON body of:

EndpointField
POST /v1/audio/speechsafety_identifier
POST /v1/audio/streamsafety_identifier
POST /v1/audio/stream/with-timestampssafety_identifier

Derive the identifier from your own internal user id with a keyed hash (HMAC-SHA256), then send it with each request:

import hashlib
import hmac
import os
from speechify import Speechify
client = Speechify(token=os.environ["SPEECHIFY_API_KEY"])
secret = os.environ["SAFETY_IDENTIFIER_SECRET"].encode()
def safety_identifier(user_id: str) -> str:
return hmac.new(secret, user_id.encode(), hashlib.sha256).hexdigest()
audio = client.audio.speech(
input="Hello from the Speechify API.",
voice_id="geffen_32",
model="simba-3.2",
audio_format="mp3",
safety_identifier=safety_identifier("user-8421"),
)

Send the same value for the same person on every request. A value that changes per session or per request cannot be attributed, limited or blocked.

Hash it, never send personal data

Never send an email address, a name, a phone number or any other personal data. The identifier should be stable for you and meaningless to anyone else, which is what the keyed hash above gives you.

A keyed hash is better than a plain one: a plain hash of an email address can be reversed by hashing a list of known addresses. Keep the secret fixed, because rotating it gives every user a new identifier.

The value must be 1 to 64 characters of letters, digits and . _ - : | + / =. That fits a hex or base64 SHA-256, a UUID or an id such as user_123, and it refuses @ and spaces, so an email address or a name is rejected with 400 validation_failed naming safety_identifier rather than stored. The error never repeats the value you sent.

What we do with it

Attribution. The identifier is recorded with the request as metadata, not content. It is kept even when your workspace has zero data retention, because it is not the text you sent; with zero data retention the text is still withheld as usual. When content screening refuses a request, the refusal is recorded against the end user, so repeat abuse by one person is visible as one person.

Per-end-user limits. A workspace can be given a rate and concurrency allowance per end user, inside the workspace’s own API limits. Both apply: a request must fit the workspace’s budget and its end user’s. They are off unless set for your workspace, and they never apply to a request without a safety_identifier. Over the limit, the request returns 429 with a Retry-After header and the same codes as the workspace limits, rate_limited or concurrency_limit_reached, and a message that names the safety_identifier. Only that end user is affected.

Your workspace’s per-end-user ceilings are on GET /v1/workspaces/current/entitlements as tts_end_user_requests_per_second and tts_end_user_concurrency; 0 means none applies.

Blocking one end user. An end user can be blocked on your workspace. Every synthesis request naming them then returns:

{
"error": {
"code": "safety_identifier_blocked",
"message": "This end user is blocked from synthesis on this workspace. Stop sending requests for this safety_identifier; other end users are unaffected. Contact support with the request_id if you believe this is a mistake.",
"fields": { "safety_identifier": "this end user is blocked" }
},
"request_id": "..."
}

with status 403. Treat it as persistent: stop serving that user rather than retrying, and do not resend their requests without the identifier. Nothing is synthesized or billed for a blocked request.

Per-end-user limits and blocks are set by Speechify for your workspace. Contact us to have limits set, or with the request_id of a refusal you believe is a mistake.

FAQ

No. It is optional, and a request without it behaves exactly as before. Send it if many of your own users share your workspace.

It is never billed. It changes no workspace limit; it only adds per-end-user limits when your workspace has them.

Only if it means nothing outside your system and carries no personal data. A keyed hash of it is safer, and is what we recommend.