service_account_model_not_allowed 403: the key's model allowlist
The 403 says the model, or the operation, is not on the service-account key's allowlist. details.model or details.operation names the value to add or avoid.

A service-account key can carry two allowlists, and each has its own 403. service_account_model_not_allowed means the model you asked for is not in the key's allowed models. service_account_operation_not_allowed means the API operation itself is not allowed. The first has details.model; the second has details.operation. Neither can be fixed from the request body, because the policy is part of the key.
How the two differ
The checks run in this order for a paid submit: key status, model, then operation. A model refusal comes first, so you may fix one allowlist and meet the other next.
| Code | details field | Example value | What to change |
|---|---|---|---|
| service_account_model_not_allowed | model | a public model id | Use an allowed model, or have the id added to the key |
| service_account_operation_not_allowed | operation | model.run:<model id>, jobs.read, video_analyze, video_segment, trending-videos.search, scrapecreators.read | Use an allowed operation, or have it added to the key |
Aliases are handled
Model ids are compared after alias resolution: a request passes if the id, or its canonical form, is on the list. An operation of the form model.run:<id> is also compared in its canonical form. So the compatibility /v1/models/sume/.../runs routes and the canonical product routes do not by themselves cause a mismatch. See the API reference for the route families.
A common surprise: reading a job
jobs.read is its own operation. A key that may submit but may not read will submit successfully and then fail on GET /v1/jobs/:id. Test the full loop (submit, status, result) with the key in staging before you rely on it. This helper turns the error into an actionable message:
import json
BODY = '{"error": {"code": "service_account_operation_not_allowed", "details": {"operation": "jobs.read"}}}'
def explain(body: dict) -> str:
e = body["error"]
d = e.get("details", {})
if e["code"] == "service_account_model_not_allowed":
return f"model {d['model']} is not on this key's allowlist"
if e["code"] == "service_account_operation_not_allowed":
return f"operation {d['operation']} is not allowed for this key"
return "other error"
print(explain(json.loads(BODY)))Find out your allowlist early
A refusal on the first production call is a poor way to learn the policy. Ask the owner of the key for its allowed models and operations when you set up the integration, write them into your config and fail fast in your own code when a feature maps to something outside the list. The public catalog at GET /v1/catalog tells you what exists; the key's policy tells you what this credential may use, and the two are not the same set.
Do not route around it
Retrying the same request cannot succeed, and swapping to a model that is not listed meets the same wall. Ask the owner of the key for the access, or use a key whose policy matches your workload. Other 403 causes, such as insufficient_scope, are covered in the related posts.
Sources
Related posts
More in Developers
- service_account_policy_unavailable 503: the message says temporary
The message says temporarily unavailable, but the envelope marks this 503 retryable false with contact_support. Retry cautiously and alert on repeats.
- service_account_rate_limited 429: read the minute and hour headers
A service-account key has an optional minute window and hour window. The 429 names the window, and x-sume-service-account headers show what is left.
- Service-account 402: daily, monthly or per-end-user spend cap hit
Three different 402 codes mean three different caps. Read details.cap_usd_micros and current_usd_micros to see which window blocked the request.
- language_code or auto-detect on Sume STT? A two-arm test on your clips
AssemblyAI reports 8.4% mean WER over 18 FLEURS languages. For your audio, run each clip twice on Sume STT, with and without language_code, and compare.
Written by Sume