Byte-exact error codes and messages surfaced by MVP RPCs. This document is the byte-exact reference implementers build against. If any row here diverges from PRD prose, the PRD prose is authoritative and the row in this table is a bug.
Consolidated from prose in PRD §4.1, §4.3, §4.6, §5.1, §5.4, §5.6 for implementer convenience.
| RPC | Condition | gRPC code | Message (byte-exact) |
|---|---|---|---|
| any admin RPC | caller's IAM permission claim lacks ADMIN:NAMESPACE:{namespace}:EXTEND:APPUI at the required action bit (per-RPC CREATE / READ / UPDATE / DELETE — declared via proto options) — PRD §6 AuthZ |
PermissionDenied |
(implementation-defined; message SHOULD reference the missing EXTEND:APPUI permission and required action, but MUST NOT echo the caller's actual permission claim) |
| any RPC | request lacks a Bearer token, or the token fails AGS IAM JWKS validation — PRD §6 Security | Unauthenticated |
(implementation-defined; standard Unauthenticated) |
CreatePlaytest |
request specifies distributionModel=AGS_CAMPAIGN in M1 — PRD §4.6 / §10 M1 |
Unimplemented |
(implementation-defined; message SHOULD reference that AGS_CAMPAIGN lands in M2) |
ApproveApplicant |
fenced finalize affects 0 rows (reservation reclaimed and re-reserved mid-approve) — §4.1 6b | Aborted |
reservation expired, please retry |
ApproveApplicant |
two admins click Approve on the same PENDING applicant simultaneously — §5.4 | FailedPrecondition |
applicant already approved |
RejectApplicant |
applicant read as PENDING but a concurrent Approve commits before RejectCAS runs (CAS-race) — §5.4 | FailedPrecondition |
applicant was approved before reject could complete |
ApproveApplicant |
pool empty at reserve time, STEAM_KEYS playtest — §5.4 | ResourceExhausted |
No codes remaining in pool. Upload more codes to continue approving. |
ApproveApplicant |
pool empty at reserve time, AGS_CAMPAIGN playtest — §5.4 | ResourceExhausted |
No codes remaining in pool. Generate more codes to continue approving. |
ApproveApplicant |
attempt to approve a REJECTED applicant — §5.4 | FailedPrecondition |
applicant is rejected and cannot be re-approved |
ApproveApplicant / RejectApplicant |
playtest status = CLOSED — §5.4 | FailedPrecondition |
playtest is closed; approve/reject is no longer allowed |
ApproveApplicant / RejectApplicant |
playtest status = DRAFT (defensive; signup is impossible in DRAFT per §5.1 visibility, so unreachable in practice) — §5.4 / Table A | FailedPrecondition |
playtest is in draft; approve/reject requires OPEN status |
CreatePlaytest |
namespace has reached the 100-playtest soft cap — PRD §6 Pagination | ResourceExhausted |
(implementation-defined; message SHOULD reference the 100-playtest cap) |
CreatePlaytest |
slug collision within namespace — §5.1 | AlreadyExists |
(implementation-defined; message SHOULD reference the offending slug) |
CreatePlaytest / EditPlaytest |
ndaRequired=true with empty ndaText — §5.3 |
InvalidArgument |
(implementation-defined; message SHOULD reference that nda_text is required when nda_required is true) |
CreatePlaytest / EditPlaytest |
both startsAt and endsAt set with endsAt <= startsAt — §5.1 "Window-driven auto-transition" |
InvalidArgument |
ends_at must be after starts_at |
CreatePlaytest (STEAM_KEYS) |
initialCodeQuantity set — STEAM_KEYS sources codes from admin CSV upload; only AGS_CAMPAIGN consumes the field (§4.6 / §5.1) |
InvalidArgument |
(implementation-defined; message SHOULD reference that initial_code_quantity is not valid for STEAM_KEYS) |
CreatePlaytest / EditPlaytest |
auto_approve=true and auto_approve_limit NULL or outside 1–100,000 — §5.1 / §5.4 "Auto-approve" |
InvalidArgument |
auto_approve_limit must be between 1 and 100000 when auto_approve is true |
EditPlaytest |
attempt to edit any immutable field (incl. distributionModel, slug, namespace, status, initialCodeQuantity, ags* IDs, timestamps) — §5.1 |
InvalidArgument |
(implementation-defined; message MUST reference the offending field name) |
TransitionPlaytestStatus |
invalid transition (e.g. DRAFT → CLOSED, or any backward transition) — §5.1 | FailedPrecondition |
(implementation-defined; message SHOULD reference the invalid transition) |
AGS-backed RPCs (CreatePlaytest AGS_CAMPAIGN, TopUpCodes, SyncFromAGS) |
upstream AGS returned HTTP 429 — §4.6 / ags-failure-modes.md |
ResourceExhausted |
(implementation-defined; message SHOULD reference upstream AGS rate limit) |
AGS-backed RPCs (CreatePlaytest AGS_CAMPAIGN, TopUpCodes, SyncFromAGS) |
upstream AGS returned HTTP 5xx / timeout after 3 retries exhausted — §4.6 / ags-failure-modes.md |
Unavailable |
(implementation-defined; message SHOULD reference upstream AGS unavailability and retry exhaustion) |
UploadCodes |
non-UTF-8 CSV file — §4.3 | InvalidArgument |
(implementation-defined; message SHOULD reference UTF-8 requirement) |
UploadCodes |
bounds / charset / duplicate violation (whole-file reject) — §4.3 | InvalidArgument |
(implementation-defined; response body lists offending line numbers / values) |
CreatePlaytest (AGS_CAMPAIGN) |
initialCodeQuantity outside 1–50,000 — §4.6 / §5.1 |
InvalidArgument |
(implementation-defined; message SHOULD reference the 1–50,000 bound) |
GetPlaytest (unauth) |
slug targets a DRAFT, CLOSED, or soft-deleted playtest — §5.1 | NotFound |
(empty / standard NotFound; indistinguishable from non-existent slug) |
GetPlaytestForPlayer |
DRAFT or soft-deleted playtest, or CLOSED playtest for a non-approved caller — §5.1 | NotFound |
(empty / standard NotFound) |
GetGrantedCode |
any soft-deleted playtest, regardless of applicant state — §5.1 | NotFound |
(empty / standard NotFound) |
SubmitSurveyResponse |
second submit by the same (playtestId, userId) — §4.7 / §5.6 |
AlreadyExists |
(empty body) |
CreateSurvey |
second Create for the same playtest (a survey already exists) — §4.7 / schema.md §"Survey entity spec" |
AlreadyExists |
(implementation-defined; message SHOULD reference using EditSurvey to update an existing survey) |
CreateSurvey / EditSurvey |
empty questions, more than 50 entries, or any question with empty/over-1,000-char prompt — schema.md §"Survey entity spec" |
InvalidArgument |
(implementation-defined; message MUST reference the offending bound) |
CreateSurvey / EditSurvey |
multi-choice question with fewer than 2 or more than 20 options, or any option.label empty / over 200 chars — schema.md §"Survey entity spec" |
InvalidArgument |
(implementation-defined; message MUST reference the offending bound) |
EditSurvey |
no survey exists yet for the playtest (call CreateSurvey first) — §4.7 |
FailedPrecondition |
(implementation-defined; message SHOULD reference creating the first version) |
EditSurvey |
a question.id (or option.id) does not match any entry on the current version, or appears twice in the request — schema.md §"Survey entity spec" |
InvalidArgument |
(implementation-defined; message MUST reference the offending id and whether it was unknown or duplicated) |
GetSurvey |
playtest has no survey configured (or Playtest.surveyId dangling) — §4.7 / schema.md |
NotFound |
(empty / standard NotFound) |
SubmitSurveyResponse |
calling player has no applicant row yet (must signup first) — §4.1 step 8 | FailedPrecondition |
(implementation-defined; message SHOULD reference signup) |
SubmitSurveyResponse |
applicant is not APPROVED — §5.6 | FailedPrecondition |
(implementation-defined; message MUST reference APPROVED) |
SubmitSurveyResponse |
NDA re-acceptance required (applicant.ndaVersionHash != playtest.currentNdaVersionHash) — §5.6 |
FailedPrecondition |
(implementation-defined; message MUST reference NDA) |
SubmitSurveyResponse |
playtest has no survey configured — §5.6 | FailedPrecondition |
(implementation-defined; message SHOULD reference no-survey) |
SubmitSurveyResponse |
submitted surveyId does not exist or does not belong to the playtest — §5.6 |
InvalidArgument |
(implementation-defined; message MUST reference the offending surveyId) |
SubmitSurveyResponse |
answer questionId does not match the survey, is repeated, has the wrong type, or violates per-type bounds (text >4,000 chars; rating outside 1–5; multi-choice unknown / repeated option_id or many entries on a single-select question) — §5.6 |
InvalidArgument |
(implementation-defined; message MUST reference the offending bound) |
ExchangeDiscordCode |
request missing or malformed code / redirect_uri — STATUS.md M1 phase 9.3 |
InvalidArgument |
(implementation-defined; message MUST reference the offending field name) |
ExchangeDiscordCode |
AGS IAM rejected with error=invalid_grant (Discord code expired / already used / redirect_uri mismatch) — STATUS.md M1 phase 9.3 |
InvalidArgument |
(implementation-defined; message SHOULD propagate the AGS error_description byte-exact) |
ExchangeDiscordCode |
AGS IAM responded HTTP 5xx with error=server_error and an error_description containing discord.com + invalid_grant (AGS wraps Discord 4xx invalid_grant as a 5xx) — STATUS.md M1 phase 9.4 |
InvalidArgument |
(implementation-defined; message SHOULD propagate the AGS error_description byte-exact so the embedded Discord marker reaches the client) |
ExchangeDiscordCode |
AGS IAM rejected with error=unauthorized_client (backend confidential client lacks the Discord-grant scope) — STATUS.md M1 phase 9.3 |
Internal |
(implementation-defined; message SHOULD reference Discord federation misconfiguration but MUST NOT leak the AGS error_description) |
ExchangeDiscordCode |
AGS IAM unreachable (network error / TCP reset / TLS failure) — STATUS.md M1 phase 9.3 | Unavailable |
(implementation-defined; message SHOULD reference AGS IAM unreachability) |
ExchangeDiscordCode |
AGS IAM responded HTTP 5xx with no discord.com + invalid_grant marker in the body (genuine AGS outage) — STATUS.md M1 phase 9.3 |
Unavailable |
(implementation-defined; message SHOULD reference upstream AGS unavailability) |
ListAuditLog |
malformed page_token (does not decode to a (createdAt, id) cursor) — STATUS.md M3 phase 6 |
InvalidArgument |
page_token is malformed |
ListAuditLog |
actor_filter is neither system nor a UUID — STATUS.md M3 phase 6 |
InvalidArgument |
(implementation-defined; message MUST reference actor_filter) |
CreatePlaytest (ADT) |
one or more of adt_namespace / adt_game_id / adt_build_id missing or empty — PRD §4.8 / §5.1 |
InvalidArgument |
(implementation-defined; message MUST reference which of the three ADT identifier fields is missing) |
CreatePlaytest (ADT) |
initial_code_quantity set — ADT has no code pool (PRD §4.8.5) |
InvalidArgument |
(implementation-defined; message SHOULD reference that initial_code_quantity is not valid for ADT) |
CreatePlaytest (non-ADT) |
any adt_namespace / adt_game_id / adt_build_id / adt_fallback_download_url set when distribution_model is not ADT — PRD §5.1 |
InvalidArgument |
(implementation-defined; message MUST reference the offending adt_* field name and the actual distribution_model) |
CreatePlaytest (ADT) |
adt_build_id does not belong to the (adt_namespace, adt_game_id) per adt.Client.ListBuilds — PRD §4.8 |
InvalidArgument |
(implementation-defined; message SHOULD reference that the build id is unknown to the linked ADT namespace) |
CreatePlaytest (ADT) |
no adt_linkage row exists for the caller's studio_namespace + the requested adt_namespace — PRD §4.8.1 |
FailedPrecondition |
(implementation-defined; message SHOULD reference linking the ADT namespace first) |
StartADTLink |
ADT_BASE_URL env var unset — PRD §5.9 / §4.8.2 |
FailedPrecondition |
(implementation-defined; message SHOULD reference the missing ADT_BASE_URL configuration) |
StartADTLink |
backend's AGS service IAM JWT carries neither union_namespace nor namespace claim — PRD §4.8.1 |
FailedPrecondition |
(implementation-defined; message SHOULD reference that the backend's service token cannot be resolved to a studio namespace) |
CompleteADTLink |
state does not match any row in adt_link_pending, or the row has expired (expires_at < now()) — PRD §4.8.2 |
InvalidArgument |
(implementation-defined; message MUST reference that the linking state is invalid or expired; message SHOULD NOT distinguish "unknown" from "expired" so probing the table is uninformative) |
CompleteADTLink |
adt_namespace query param is missing or empty on the callback — PRD §4.8.2 |
InvalidArgument |
(implementation-defined; message MUST reference adt_namespace) |
UnlinkADT |
adtLinkageId does not match any adt_linkage row (live or soft-deleted) for the caller's studio_namespace — PRD §4.8 |
NotFound |
(empty / standard NotFound; idempotent re-unlink against an already-deleted row is a no-op success, not this error) |
RecoverADTLinkage |
adtNamespace missing or empty on the request — PRD §4.8 |
InvalidArgument |
adt_namespace is required |
RecoverADTLinkage |
a live adt_linkage row for the caller's studio_namespace + the requested adt_namespace already exists — PRD §4.8 |
AlreadyExists |
adt linkage already exists for that namespace |
RecoverADTLinkage |
ADT returned errorCode=99 ("Namespace is not registered") on the linkage probe (ListGames) — adt.ErrLinkageMissing; no orphan flag exists on ADT's side for this pair — PRD §4.8 / STATUS_M5.md Bug 4 |
FailedPrecondition |
no ADT-side linkage found for that namespace; use StartADTLink to create one |
RecoverADTLinkage |
ADT returned errorCode=401 on the linkage probe — adt.ErrUnauthenticated; bearer token rejected by ADT — PRD §4.8 / STATUS_M5.md Bug 4 |
FailedPrecondition |
ADT rejected the backend service token; rotate AGS IAM client credentials and restart the backend |
RecoverADTLinkage |
ADT returned errorCode=20001 on the linkage probe — adt.ErrPermissionDenied; token valid but route permission absent — PRD §4.8 / STATUS_M5.md Bug 4 |
FailedPrecondition |
backend service token lacks required ADT permission scope; ask ADT-eng to grant the missing permission |
RecoverADTLinkage |
ADT returned a transient error (5xx-retry exhausted or 429) on the linkage probe — PRD §4.8 | Unavailable |
(implementation-defined; message SHOULD reference ADT being temporarily unavailable) |
ListADTBuilds / ListADTGames |
adtLinkageId does not match any live adt_linkage row for the caller's studio_namespace — PRD §4.7 / §4.8 / STATUS_M5.md B12 |
FailedPrecondition |
no ADT linkage matches this id for the caller's studio; link an ADT namespace first |
ListADTBuilds / ListADTGames |
ADT returned 401 on the proxied adt.Client.ListBuilds / ListGames call (linkage flag missing on ADT side) — PRD §4.8 / STATUS_M5.md B12 |
FailedPrecondition |
adt linkage no longer exists or service token rejected, re-link required |
ChangeADTBuild |
adt_game_id or adt_build_id missing or empty — PRD §4.8 / §5.1 |
InvalidArgument |
(implementation-defined; message MUST reference that adt_game_id and adt_build_id are required) |
ChangeADTBuild |
playtest distribution_model is not ADT (build can only be repointed on ADT playtests) — PRD §4.8 / §5.1 |
FailedPrecondition |
(implementation-defined; message SHOULD reference that the build can only be changed on ADT playtests) |
ChangeADTBuild |
adt_build_id does not belong to the (adt_namespace, adt_game_id) per adt.Client.ListBuilds (same verification CreatePlaytest runs) — PRD §4.8 |
InvalidArgument |
(implementation-defined; message SHOULD reference that the build id is not present under the adt_namespace / adt_game_id) |
ChangeADTBuild |
no adt_linkage row exists for the caller's studio_namespace + the playtest's adt_namespace — PRD §4.8.1 |
FailedPrecondition |
no ADT linkage covers this studio + adt_namespace; link the ADT namespace first |
ChangeADTBuild |
ADT returned 401 on the proxied adt.Client.ListBuilds verification (linkage flag missing on ADT side) — PRD §4.8 |
FailedPrecondition |
adt linkage no longer exists or service token rejected, re-link required |
ChangeADTBuild |
playtest_id does not match a live Playtest row (not found or soft-deleted) — PRD §4.8 |
NotFound |
playtest not found |
CheckADTBuild |
playtest distribution_model is not ADT (build health only applies to ADT playtests) — M5.C "build gone" surfacing |
FailedPrecondition |
(implementation-defined; message SHOULD reference that build health only applies to ADT playtests) |
CheckADTBuild |
playtest_id does not match a live Playtest row (not found or soft-deleted) — M5.C |
NotFound |
playtest not found |
CheckADTBuild |
ADT returned 401 on the IssueDownloadURL probe (linkage flag missing on ADT side) — M5.C |
FailedPrecondition |
adt linkage no longer exists or service token rejected, re-link required |
CheckADTBuild |
ADT returned a transient 4xx/5xx on the IssueDownloadURL probe — M5.C. NB: errorCode=1003303402 ("build not found") is NOT an error here — it is a successful response with healthy=false and persisted adt_build_status='UNAVAILABLE' |
Unavailable |
(implementation-defined; message SHOULD reference ADT unavailability) |
ApproveApplicant (ADT) |
ADT returned 401 on IssueDownloadURL (linkage flag missing on ADT side, or union_namespace claim shift) — PRD §4.8.3 |
FailedPrecondition |
adt linkage no longer exists or service token rejected, re-link required |
ApproveApplicant (ADT) / GetADTDownloadInfo |
ADT returned errorCode=1003303402 ("build not found") on IssueDownloadURL (linkage intact; the build was deleted from ADT) AND playtest has no adt_fallback_download_url set — adt.ErrBuildNotFound; PRD §4.8.3 / STATUS_M5.md |
FailedPrecondition |
ADT build no longer exists; it may have been deleted from ADT. Set a fallback download URL or re-create the playtest with a current build. |
ApproveApplicant (ADT) |
ADT returned 4xx/5xx on IssueDownloadURL (linkage row still present) AND playtest has no adt_fallback_download_url set — PRD §4.8.3 |
Unavailable |
(implementation-defined; message SHOULD reference ADT unavailability and the option to set a fallback URL) |
GetADTDownloadInfo |
playtest distribution_model is not ADT (caller should be using GetGrantedCode) — PRD §4.7 |
FailedPrecondition |
(implementation-defined; message SHOULD reference using GetGrantedCode for non-ADT playtests) |
GetADTDownloadInfo |
calling applicant is not APPROVED — PRD §4.8.4 / §6 Security |
FailedPrecondition |
(implementation-defined; message SHOULD reference APPROVED; identical shape to GetGrantedCode's applicant-not-approved error so distribution-model probing via this RPC is not informative) |
GetCodePool |
playtest distribution_model is ADT (no code pool) — PRD §5.5 / §4.8.5 |
FailedPrecondition |
(implementation-defined; message SHOULD reference that ADT playtests have no code pool to manage) |
UploadCodes / TopUpCodes / SyncFromAGS |
playtest distribution_model is ADT — PRD §5.5 / §4.8.5 |
FailedPrecondition |
(implementation-defined; message SHOULD reference that ADT playtests have no code pool to manage) |
CreatePlaytest / EditPlaytest |
banner_image_url set but not parseable as an https://... URL — PRD §5.1 |
InvalidArgument |
banner_image_url must be an https URL |
CreatePlaytest / EditPlaytest |
platforms array is empty — PRD §5.1 |
InvalidArgument |
platforms must include at least one platform |
CreatePlaytest / EditPlaytest |
platforms contains a value outside {WINDOWS, MACOS, PS5, XBOX_SERIES, LINUX} — PRD §5.1 |
InvalidArgument |
(implementation-defined; message MUST reference the offending platform value) |
CreateAnnouncement |
subject is empty (length 0) — PRD §5.4 "Bulk announcements" |
InvalidArgument |
announcement subject must not be empty |
CreateAnnouncement |
message is empty (length 0) — PRD §5.4 "Bulk announcements" |
InvalidArgument |
announcement message must not be empty |
CreateAnnouncement |
subject longer than ANNOUNCEMENT_MAX_SUBJECT_LEN (default 200) — PRD §5.4 / §5.9 |
InvalidArgument |
announcement subject must be at most 200 characters |
CreateAnnouncement |
message longer than ANNOUNCEMENT_MAX_MESSAGE_LEN (default 4000) — PRD §5.4 / §5.9 |
InvalidArgument |
announcement message must be at most 4000 characters |
CreateAnnouncement |
send_to_filter not in {ALL, APPROVED_ONLY, PENDING_ONLY} — PRD §5.4 "Bulk announcements" |
InvalidArgument |
(implementation-defined; message MUST reference the offending send_to_filter value) |
CreateAnnouncement |
playtest status = CLOSED — PRD §5.4 "Bulk announcements" |
FailedPrecondition |
playtest is closed; announcements can no longer be sent |
GetPlaytestParticipants |
status_filter neither empty nor one of {PENDING, APPROVED, REJECTED} — PRD §5.4 / §4.7 |
InvalidArgument |
(implementation-defined; message MUST reference status_filter) |
Rows marked "implementation-defined" fix the gRPC code contractually; the exact message string may be refined during implementation but MUST reference the cited condition. All other rows are byte-exact and MUST match verbatim.