4xx errors indicate a problem with the request; 5xx errors indicate a problem on Corti’s side. Error responses are in JSON and differ between the SDKs and the REST API. The tabs below cover each.
Some Corti products handle errors and authentication differently than what is described below:
- Admin API — Separate from the
Corti APIused for speech to text, text generation, and agentic workflows. See the Administration API reference for details. Please contact us if you have interest in this functionality or further questions. - Agents API — The JSON format of the error response is different. See the Agents API docs for more info.
- Embedded Assistant — Authentication is handled differently. See the Embedded Assistant Authentication docs for those methods.
- JavaScript SDK
- C# .NET SDK
- REST API
The SDK throws typed error classes on any non-2xx response or internal failure. Each class exposes a different set of attributes. See Error Handling for full details.
Error Classes
| Class | Thrown when | Attributes |
|---|---|---|
CortiError | API returned a non-2xx HTTP response (4xx / 5xx) | message, statusCode, body, rawResponse |
CortiSDKError | SDK infrastructure error (e.g. localStorage unavailable) | message, code, cause |
ParseError | Input validation failed (e.g. missing PKCE verifier, invalid JWT) | message, errors |
JsonError | Response body could not be parsed as JSON | message, errors |
HTTP Status Codes
400 — Bad Request
400 — Bad Request
| Error code | When it occurs | How to resolve |
|---|---|---|
bad_request | The request body or query parameters are malformed or missing required fields. | Double check the fields submitted to be sure of correct format and necessary fields. |
401 — Unauthorized
401 — Unauthorized
Token endpoint errors are returned as JSON in the response body. Corti API errors are returned in the
WWW-Authenticate response header with no body. The header is not accessible from the exception directly; use .WithRawResponse() on the request if you need it.| Error code | When it occurs | How to resolve |
|---|---|---|
invalid_client | Client authentication failed. There is a problem with the client ID. | Verify your client_id is correct. |
unauthorized_client | Client authentication failed. There is a problem with the client secret. | Verify your client_secret is correct. |
invalid_token | The bearer token is invalid or has expired. Check the WWW-Authenticate header for error_description. | Re-authenticate to obtain a fresh token and retry the request. |
403 — Forbidden
403 — Forbidden
| When it occurs | How to resolve |
|---|---|
| Accessing a resource (interaction, recording, transcript, document) that belongs to a different client. | Verify the resource ID was created by the authenticated client. |
404 — Not Found
404 — Not Found
| Error | When it occurs | How to resolve |
|---|---|---|
Invalid '{resource_name}' '{resource_id}' | The requested resource does not exist. | Verify the ID and parent resource ID is correct and was created under the authenticated tenant. |
Attempting to access a resource that belongs to a different OAuth client also returns a 404, not a 403. This is intentional. Returning a 403 would confirm that the resource exists, which is a security risk.
429 — Too Many Requests
429 — Too Many Requests
You have exceeded the rate limit for the API. The SDK automatically retries with exponential backoff. The default retry limit is 2. Override per request:If the error persists, reduce request frequency or contact support to discuss your rate limit requirements.
const response = await client.interactions.create(..., {
maxRetries: 0, // disable retries for this request
});
500 — Internal Server Error
500 — Internal Server Error
An unexpected error occurred on the Corti side. Double check the request format and retry the request. If the issue persists, contact support.
503 — Service Unavailable
503 — Service Unavailable
The service is temporarily unavailable. Retry with exponential backoff. Monitor the status page.
WebSocket APIs
WebSocket errors from/streams and /transcribe are surfaced as plain Error objects whose message is the server status code (e.g. CONFIG_DENIED, CONFIG_MISSING, CONFIG_TIMEOUT).Every
CONFIG_* server message also carries a reason field with human-readable details behind the failure. Whether you can read it depends on the mode you connected in:- Default mode (
client.stream.connect({ id, configuration })/client.transcribe.connect({ configuration })): the SDK handles the handshake-phase andCONFIG_*messages internally. It rejectsconnect()with just the bare code. Thereasonis not exposed. awaitConfiguration: falseor manual mode: the raw message, includingmsg.reason, flows throughsocket.on('message', …).
`CONFIG_DENIED`
`CONFIG_DENIED`
You sent a config message, but it’s invalid.
reason from server | Likely cause | Fix |
|---|---|---|
invalid language code: must be a valid BCP 47 language tag | transcription.primaryLanguage is not a supported code. | Use a supported language code. |
invalid output locale: language should not be empty | mode.type: "facts" without mode.outputLocale. | Add outputLocale. |
invalid output locale: invalid language code: must be a valid BCP 47 language tag | mode.type: "facts" with invalid mode.outputLocale code. | Use a supported language code |
unknown mode | mode.type is not "transcription" or "facts". | Use one of the two supported values. |
invalid participants | Participant role outside doctor / patient / multiple. | Use one of the three supported roles. |
invalid audio format: MIME type not allowed... | audioFormat MIME type is not in the supported list. | Use a supported MIME type or omit the field. |
`CONFIG_MISSING`
`CONFIG_MISSING`
You sent a non-config frame before any valid config message arrived.
Only reachable when you opt out of the automatic handshake. Default mode sends config for you.reason from server | Cause | Fix |
|---|---|---|
No valid configuration provided for interaction | A non-config frame reached the server before a config message. Typically sendAudio() / sendFlush() / sendEnd() in manual mode, or a raw socket.send() queued before OPEN in awaitConfiguration: false. | Be sure to call sendConfiguration() immediately after waitForOpen(), or pass configuration to connect() and let the SDK handle the handshake.` |
`CONFIG_TIMEOUT`
`CONFIG_TIMEOUT`
The 10-second handshake window elapsed without any config message arriving.
Only reachable in manual mode. The default handshake sends config well within the 10s budget.reason from server | Cause | Fix |
|---|---|---|
| Manual mode: no configuration provided within 10s | Server’s 10s handshake timer expired. | Call sendConfiguration() immediately after waitForOpen(). |
The SDK throws typed exceptions on any non-2xx response. Each exception extends
Each status code throws a typed subclass with a more specific
CortiClientApiException, which exposes Message, StatusCode, and Body. See Error Handling for full details.Exceptions
| Exception class | Thrown when | Attributes |
|---|---|---|
CortiClientApiException | Any non-2xx HTTP response | Message, StatusCode, Body |
Body. See the accordions below for details.HTTP Status Codes
400 — Bad Request
400 — Bad Request
| Exception class | Error code | When it occurs | How to resolve |
|---|---|---|---|
BadRequestError | bad_request | The request body or query parameters are malformed or missing required fields. | Double check the fields submitted to be sure of correct format and necessary fields. |
401 — Unauthorized
401 — Unauthorized
Token endpoint errors are returned as JSON in the response body. Corti API errors are returned in the
WWW-Authenticate response header with no body. The header is not accessible from the exception directly; use .WithRawResponse() on the request if you need it.| Exception class | Error code | When it occurs | How to resolve |
|---|---|---|---|
UnauthorizedError | invalid_client | Client authentication failed. There is a problem with the client ID. | Verify your client_id is correct. |
UnauthorizedError | unauthorized_client | Client authentication failed. There is a problem with the client secret. | Verify your client_secret is correct. |
UnauthorizedError | invalid_token | The bearer token is invalid or has expired. | Re-authenticate to obtain a fresh token and retry the request. |
403 — Forbidden
403 — Forbidden
| Exception class | When it occurs | How to resolve |
|---|---|---|
ForbiddenError | Accessing a resource (interaction, recording, transcript, document) that belongs to a different client. | Verify the resource ID was created by the authenticated client. |
404 — Not Found
404 — Not Found
| Exception class | Error code | When it occurs | How to resolve |
|---|---|---|---|
NotFoundError | Invalid '{resource_name}' '{resource_id}' | The requested resource does not exist. | Verify the ID and parent resource ID is correct and was created under the authenticated tenant. |
Attempting to access a resource that belongs to a different OAuth client also returns a 404, not a 403. This is intentional. Returning a 403 would confirm that the resource exists, which is a security risk.
422 — Unprocessable Entity
422 — Unprocessable Entity
| Exception class | When it occurs | How to resolve |
|---|---|---|
UnprocessableEntityError | Request validation failed. | Check Body for Code, Description, HowToFix, Detail, and Cause fields. |
429 — Too Many Requests
429 — Too Many Requests
| Exception class | When it occurs | How to resolve |
|---|---|---|
CortiClientApiException | Rate limit exceeded after retries exhausted (default: 2 retries with backoff). | Reduce request frequency or contact support to discuss your rate limit requirements. |
500 — Internal Server Error
500 — Internal Server Error
| Exception class | When it occurs | How to resolve |
|---|---|---|
InternalServerError | An unexpected error occurred on the Corti side. Body contains Detail, Type, Status, and RequestId. | Double check your request format and retry the request. If the issue persists, contact support. |
502 — Bad Gateway
502 — Bad Gateway
| Exception class | When it occurs | How to resolve |
|---|---|---|
BadGatewayError | A gateway error occurred. Body contains Detail, Type, Status, and RequestId. | Retry the request. If the issue persists, contact support. |
503 — Service Unavailable
503 — Service Unavailable
| Exception class | When it occurs | How to resolve |
|---|---|---|
CortiClientApiException | The service is temporarily unavailable. | Retry with exponential backoff. Monitor the status page. |
504 — Gateway Timeout
504 — Gateway Timeout
| Exception class | When it occurs | How to resolve |
|---|---|---|
GatewayTimeoutError | The gateway timed out. Body contains Detail, Type, Status, and RequestId. | Retry the request. If the issue persists, contact support. |
WebSocket APIs
WebSocket errors from/streams and /transcribe surface as exceptions thrown by ConnectAsync() (with Message set to the server status code, e.g. CONFIG_DENIED, CONFIG_MISSING, CONFIG_TIMEOUT) or as typed events on the socket.Every
CONFIG_* server message also carries a Reason field with human-readable details behind the failure. Whether you can read it depends on how you connect:- Default mode (
stream.ConnectAsync(new StreamConfig { … })/transcribe.ConnectAsync(new TranscribeConfig { … })): the SDK handles the handshake-phase andCONFIG_*messages internally. It throws fromConnectAsync()with just the bare code. TheReasonis not exposed. - Manual mode (
ConnectAsync()with no config): the raw message, includingmsg.Reason, flows through theStreamConfigStatusMessage/TranscribeConfigStatusMessageevent. Subscribe before callingConnectAsync()to catch handshake-phase events.
`CONFIG_DENIED`
`CONFIG_DENIED`
You sent a config message, but it’s invalid.
reason from server | Likely cause | Fix |
|---|---|---|
invalid language code: must be a valid BCP 47 language tag | transcription.primaryLanguage is not a syntactically valid BCP-47 tag (e.g. "english", "en_US", empty string). | Use a supported language code. |
invalid output locale: language should not be empty | mode.type: "facts" without mode.outputLocale (or with an empty value). | Set mode.outputLocale to a valid BCP-47 code whenever mode.type is facts. |
invalid output locale: invalid language code: must be a valid BCP 47 language tag | mode.type: "facts" with a syntactically invalid mode.outputLocale. | Use a supported language code for outputLocale. |
unknown mode | mode.type is not transcription or facts. | Use one of the two supported values. |
invalid participants | Participant role outside doctor / patient / multiple. | Restrict to the three supported roles. |
invalid audio format: MIME type not allowed... | audioFormat MIME type is not in the supported list. | Use a supported MIME type or omit the field. |
`CONFIG_MISSING`
`CONFIG_MISSING`
You sent a non-config frame before any valid config message arrived.
Only reachable in manual mode. Default mode sends config for you.Reason from server | Cause | Fix |
|---|---|---|
No valid configuration provided for interaction | A non-config frame reached the server before a config message. Typically a Send(byte[], …) audio call, or a Send(StreamFlushMessage, …) / Send(StreamEndMessage, …) call before any Send(StreamConfigMessage, …). | Call Send(new StreamConfigMessage { … }, …) (or TranscribeConfigMessage) as the first frame after ConnectAsync() returns, or pass the config directly to ConnectAsync(new StreamConfig { … }) and let the SDK handle the handshake. |
`CONFIG_TIMEOUT`
`CONFIG_TIMEOUT`
The 10-second handshake window elapsed without any config message arriving.
Only reachable in manual mode. The default handshake sends config well within the 10s budget.Reason from server | Cause | Fix |
|---|---|---|
no configuration provided within 10s | Server’s 10s handshake timer expired without receiving a config message. | Call Send(new StreamConfigMessage { … }, …) (or TranscribeConfigMessage) immediately after ConnectAsync() returns, or pass the config directly to ConnectAsync(new StreamConfig { … }) and let the SDK handle the handshake. |
Resource and API errors (all endpoints except the OAuth token endpoint) return JSON with Authentication errors from the OAuth token endpoint return
requestid, status, type, detail, and validationErrors fields:{
"requestid": "<string>",
"status": 400,
"type": "<string>",
"detail": "<string>",
"validationErrors": [
{}
]
}
error and error_description fields:{
"error": "invalid_client",
"error_description": "Client authentication failed."
}
HTTP Status Codes
400 — Bad Request
400 — Bad Request
| Error code | When it occurs | How to resolve |
|---|---|---|
invalid_request | The request is missing a required parameter or is otherwise malformed. | Ensure required fields (grant_type,scope) are present and correctly formatted. |
unsupported_grant_type | The grant_type value is not recognised by the server. | This API requires a grant_type of client_credentials. Corti Assistant requires alternate methods for grant_type. |
invalid_scope | The requested scope does not exist or is not permitted for this client. | Use a valid scope. This API requires openid. |
401 — Unauthorized
401 — Unauthorized
Token endpoint errors are returned as JSON in the response body. Corti API errors are returned in the
WWW-Authenticate response header with no body. Check the header directly if the response body is empty.| Error code | When it occurs | How to resolve |
|---|---|---|
invalid_client | Client authentication failed. There is a problem with the client ID. | Verify your client_id is correct. |
unauthorized_client | Client authentication failed. There is a problem with the client secret | Verify your client_secret is correct. |
invalid_token | The bearer token is invalid or has expired. Check the WWW-Authenticate header for error_description. | Re-authenticate to obtain a fresh token and retry the request. |
403 — Forbidden
403 — Forbidden
| When it occurs | How to resolve |
|---|---|
| Accessing a resource (interaction, recording, transcript, document) that belongs to a different client. | Verify the resource ID was created by the authenticated client. |
404 — Not Found
404 — Not Found
| Error | When it occurs | How to resolve |
|---|---|---|
Invalid '{resource_name}' '{resource_id}' | The requested resource does not exist. | Verify the ID and parent resource ID is correct and was created under the authenticated tenant. |
'{resource_name}' not found | The requested resource does not exist. | Verify the ID and parent resource ID is correct and was created under the authenticated tenant. |
429 — Too Many Requests
429 — Too Many Requests
You have exceeded the rate limit for the API. Retry the request after a delay, using exponential backoff. If the error persists, reduce request frequency or contact support to discuss your rate limit requirements.
500 — Internal Server Error
500 — Internal Server Error
An unexpected error occurred on the Corti side. Double check your request format and retry the request. If the issue persists, contact support.
503 — Service Unavailable
503 — Service Unavailable
The service is temporarily unavailable. Retry with exponential backoff. Monitor the status page.
WebSocket APIs
WebSocket errors from/streams and /transcribe arrive as plain JSON messages on the socket: { type: "CONFIG_DENIED", reason: "…" }, { type: "CONFIG_MISSING", reason: "…" }, etc. Without an SDK in the middle, you see every server message verbatim, including the reason field on every CONFIG_* event.The raw protocol is manual mode out of the box. There’s no SDK layer handling the handshake for you. You open the socket, send
{ type: "config", configuration: { … } } yourself, and listen for CONFIG_ACCEPTED before sending audio. See the Streams API reference for the full protocol.The server does not close the socket on
CONFIG_DENIED or CONFIG_MISSING. Both codes reject the input but keep the connection open. Each subsequent non-config frame triggers a fresh CONFIG_MISSING response, one per frame, until the client closes the socket. SDK consumers don’t see this because the SDK calls close() on rejection internally. Raw-protocol clients should close the socket themselves once they receive either rejection.`CONFIG_DENIED`
`CONFIG_DENIED`
You sent a config message, but it’s invalid.
reason from server | Likely cause | Fix |
|---|---|---|
invalid language code: must be a valid BCP 47 language tag | transcription.primaryLanguage is not a syntactically valid BCP-47 tag (e.g. "english", "en_US", empty string). | Use a supported language code. |
invalid output locale: language should not be empty | mode.type: "facts" without mode.outputLocale (or with an empty value). | Set mode.outputLocale to a valid BCP-47 code whenever mode.type is facts. |
invalid output locale: invalid language code: must be a valid BCP 47 language tag | mode.type: "facts" with a syntactically invalid mode.outputLocale. | Use a supported language code for outputLocale. |
unknown mode | mode.type is not transcription or facts. | Use one of the two supported values. |
invalid participants | Participant role outside doctor / patient / multiple. | Restrict to the three supported roles. |
invalid audio format: MIME type not allowed... | audioFormat MIME type is not in the supported list. | Use a supported MIME type or omit the field. |
`CONFIG_MISSING`
`CONFIG_MISSING`
You sent a non-config frame before any valid config message arrived.
reason from server | Cause | Fix |
|---|---|---|
No valid configuration provided for interaction | A non-config frame (binary audio, { type: "flush" }, { type: "end" }) reached the server before a { type: "config", configuration: { … } } message. | Send the config message as the first frame after the WebSocket opens. Wait for CONFIG_ACCEPTED before sending any other frame. |
`CONFIG_TIMEOUT`
`CONFIG_TIMEOUT`
The 10-second handshake window elapsed without any config message arriving.
reason from server | Cause | Fix |
|---|---|---|
no configuration provided within 10s | Server’s 10s handshake timer expired without receiving a config message. | Send { type: "config", configuration: { … } } immediately after the WebSocket opens. |