diff --git a/src/libs/Speechify/Generated/Speechify.AgentClient.ListTTSModels.g.cs b/src/libs/Speechify/Generated/Speechify.AgentClient.ListTTSModels.g.cs
index 4b33ff9..bfaae85 100644
--- a/src/libs/Speechify/Generated/Speechify.AgentClient.ListTTSModels.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.AgentClient.ListTTSModels.g.cs
@@ -54,14 +54,12 @@ partial void ProcessListTTSModelsResponseContent(
/// - `english_only` - the model has no multilingual deployment, so an
/// agent that declares `additional_languages` (or whose `language` is
/// not English) cannot select it.
- /// - `requires_voice_curation` - a voice must be registered for the
- /// model before it can be selected with that voice. The voice's
- /// `models` array on GET /v1/agents/voices is the per-voice answer,
- /// and it changes as voices are curated, with no release here. It is
- /// false for a model offered across the catalog, which every Speechify
- /// voice can then select.
- /// Drive a model picker from this and the voice catalog together and it
- /// can only offer combinations POST/PATCH /v1/agents will accept.
+ /// - `requires_voice_curation` - deprecated and always false. No model
+ /// asks a voice for a registration: every training conditions on the
+ /// voice's own prompt audio, so every model is selectable on every
+ /// voice we speak ourselves, cloned voices included.
+ /// Drive a model picker from this and the agent's languages and it can
+ /// only offer combinations POST/PATCH /v1/agents will accept.
/// Returns the full set in one response: bounded by the platform's
/// curated model catalog, so it is intentionally not paginated.
///
@@ -95,14 +93,12 @@ partial void ProcessListTTSModelsResponseContent(
/// - `english_only` - the model has no multilingual deployment, so an
/// agent that declares `additional_languages` (or whose `language` is
/// not English) cannot select it.
- /// - `requires_voice_curation` - a voice must be registered for the
- /// model before it can be selected with that voice. The voice's
- /// `models` array on GET /v1/agents/voices is the per-voice answer,
- /// and it changes as voices are curated, with no release here. It is
- /// false for a model offered across the catalog, which every Speechify
- /// voice can then select.
- /// Drive a model picker from this and the voice catalog together and it
- /// can only offer combinations POST/PATCH /v1/agents will accept.
+ /// - `requires_voice_curation` - deprecated and always false. No model
+ /// asks a voice for a registration: every training conditions on the
+ /// voice's own prompt audio, so every model is selectable on every
+ /// voice we speak ourselves, cloned voices included.
+ /// Drive a model picker from this and the agent's languages and it can
+ /// only offer combinations POST/PATCH /v1/agents will accept.
/// Returns the full set in one response: bounded by the platform's
/// curated model catalog, so it is intentionally not paginated.
///
diff --git a/src/libs/Speechify/Generated/Speechify.IAgentClient.ListTTSModels.g.cs b/src/libs/Speechify/Generated/Speechify.IAgentClient.ListTTSModels.g.cs
index 1d00b62..bbb6d50 100644
--- a/src/libs/Speechify/Generated/Speechify.IAgentClient.ListTTSModels.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.IAgentClient.ListTTSModels.g.cs
@@ -17,14 +17,12 @@ public partial interface IAgentClient
/// - `english_only` - the model has no multilingual deployment, so an
/// agent that declares `additional_languages` (or whose `language` is
/// not English) cannot select it.
- /// - `requires_voice_curation` - a voice must be registered for the
- /// model before it can be selected with that voice. The voice's
- /// `models` array on GET /v1/agents/voices is the per-voice answer,
- /// and it changes as voices are curated, with no release here. It is
- /// false for a model offered across the catalog, which every Speechify
- /// voice can then select.
- /// Drive a model picker from this and the voice catalog together and it
- /// can only offer combinations POST/PATCH /v1/agents will accept.
+ /// - `requires_voice_curation` - deprecated and always false. No model
+ /// asks a voice for a registration: every training conditions on the
+ /// voice's own prompt audio, so every model is selectable on every
+ /// voice we speak ourselves, cloned voices included.
+ /// Drive a model picker from this and the agent's languages and it can
+ /// only offer combinations POST/PATCH /v1/agents will accept.
/// Returns the full set in one response: bounded by the platform's
/// curated model catalog, so it is intentionally not paginated.
///
@@ -49,14 +47,12 @@ public partial interface IAgentClient
/// - `english_only` - the model has no multilingual deployment, so an
/// agent that declares `additional_languages` (or whose `language` is
/// not English) cannot select it.
- /// - `requires_voice_curation` - a voice must be registered for the
- /// model before it can be selected with that voice. The voice's
- /// `models` array on GET /v1/agents/voices is the per-voice answer,
- /// and it changes as voices are curated, with no release here. It is
- /// false for a model offered across the catalog, which every Speechify
- /// voice can then select.
- /// Drive a model picker from this and the voice catalog together and it
- /// can only offer combinations POST/PATCH /v1/agents will accept.
+ /// - `requires_voice_curation` - deprecated and always false. No model
+ /// asks a voice for a registration: every training conditions on the
+ /// voice's own prompt audio, so every model is selectable on every
+ /// voice we speak ourselves, cloned voices included.
+ /// Drive a model picker from this and the agent's languages and it can
+ /// only offer combinations POST/PATCH /v1/agents will accept.
/// Returns the full set in one response: bounded by the platform's
/// curated model catalog, so it is intentionally not paginated.
///
diff --git a/src/libs/Speechify/Generated/Speechify.ITriggersClient.Fire.g.cs b/src/libs/Speechify/Generated/Speechify.ITriggersClient.Fire.g.cs
index adea28b..da858b7 100644
--- a/src/libs/Speechify/Generated/Speechify.ITriggersClient.Fire.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.ITriggersClient.Fire.g.cs
@@ -10,10 +10,24 @@ public partial interface ITriggersClient
/// one-time fire token as `Authorization: Bearer <secret>`. The JSON body
/// becomes the started run's payload variable. Supply an `Idempotency-Key`
/// (or `Speechify-Delivery-Id`) header to make a redelivery replay the
- /// original run instead of starting a second one.
+ /// original run instead of starting a second one.
+ /// **Reacting to something that happened, for one person.** Set the
+ /// trigger's `webhook.user_identity_source` to `fire` and send
+ /// `Speechify-User-Identity` on each POST. The run then acts for that
+ /// person - their memory, and every tool call told who it is for - so one
+ /// trigger serves every person your connector watches and you hold no
+ /// trigger id per person. Use the plain form (`user_identity_source:
+ /// trigger`) when the work is the same whoever it is for: a nightly
+ /// reconciliation, an alert fan-out, a build that finished.
+ /// **Rate.** A trigger admits 60 fires a minute, and 12 a minute for any
+ /// one person; past either the endpoint answers `429` with `Retry-After`.
+ /// A fire starts a durable run against your credit balance, so a burst is
+ /// deferred rather than fanned out. Retry the refused delivery under the
+ /// same delivery id and it runs exactly once.
///
///
///
+ ///
///
/// Per-request overrides such as headers, query parameters, timeout, retries, and response buffering.
/// The token to cancel the operation with
@@ -23,6 +37,7 @@ public partial interface ITriggersClient
object request,
string? speechifyVersion = default,
+ string? speechifyUserIdentity = default,
global::Speechify.AutoSDKRequestOptions? requestOptions = default,
global::System.Threading.CancellationToken cancellationToken = default);
///
@@ -31,10 +46,24 @@ public partial interface ITriggersClient
/// one-time fire token as `Authorization: Bearer <secret>`. The JSON body
/// becomes the started run's payload variable. Supply an `Idempotency-Key`
/// (or `Speechify-Delivery-Id`) header to make a redelivery replay the
- /// original run instead of starting a second one.
+ /// original run instead of starting a second one.
+ /// **Reacting to something that happened, for one person.** Set the
+ /// trigger's `webhook.user_identity_source` to `fire` and send
+ /// `Speechify-User-Identity` on each POST. The run then acts for that
+ /// person - their memory, and every tool call told who it is for - so one
+ /// trigger serves every person your connector watches and you hold no
+ /// trigger id per person. Use the plain form (`user_identity_source:
+ /// trigger`) when the work is the same whoever it is for: a nightly
+ /// reconciliation, an alert fan-out, a build that finished.
+ /// **Rate.** A trigger admits 60 fires a minute, and 12 a minute for any
+ /// one person; past either the endpoint answers `429` with `Retry-After`.
+ /// A fire starts a durable run against your credit balance, so a burst is
+ /// deferred rather than fanned out. Retry the refused delivery under the
+ /// same delivery id and it runs exactly once.
///
///
///
+ ///
///
/// Per-request overrides such as headers, query parameters, timeout, retries, and response buffering.
/// The token to cancel the operation with
@@ -44,6 +73,7 @@ public partial interface ITriggersClient
object request,
string? speechifyVersion = default,
+ string? speechifyUserIdentity = default,
global::Speechify.AutoSDKRequestOptions? requestOptions = default,
global::System.Threading.CancellationToken cancellationToken = default);
///
@@ -52,16 +82,31 @@ public partial interface ITriggersClient
/// one-time fire token as `Authorization: Bearer <secret>`. The JSON body
/// becomes the started run's payload variable. Supply an `Idempotency-Key`
/// (or `Speechify-Delivery-Id`) header to make a redelivery replay the
- /// original run instead of starting a second one.
+ /// original run instead of starting a second one.
+ /// **Reacting to something that happened, for one person.** Set the
+ /// trigger's `webhook.user_identity_source` to `fire` and send
+ /// `Speechify-User-Identity` on each POST. The run then acts for that
+ /// person - their memory, and every tool call told who it is for - so one
+ /// trigger serves every person your connector watches and you hold no
+ /// trigger id per person. Use the plain form (`user_identity_source:
+ /// trigger`) when the work is the same whoever it is for: a nightly
+ /// reconciliation, an alert fan-out, a build that finished.
+ /// **Rate.** A trigger admits 60 fires a minute, and 12 a minute for any
+ /// one person; past either the endpoint answers `429` with `Retry-After`.
+ /// A fire starts a durable run against your credit balance, so a burst is
+ /// deferred rather than fanned out. Retry the refused delivery under the
+ /// same delivery id and it runs exactly once.
///
///
///
+ ///
/// Per-request overrides such as headers, query parameters, timeout, retries, and response buffering.
/// The token to cancel the operation with
///
global::System.Threading.Tasks.Task FireAsync(
string triggerId,
string? speechifyVersion = default,
+ string? speechifyUserIdentity = default,
global::Speechify.AutoSDKRequestOptions? requestOptions = default,
global::System.Threading.CancellationToken cancellationToken = default);
}
diff --git a/src/libs/Speechify/Generated/Speechify.JsonConverters.TriggerWebhookConfigUserIdentitySource.g.cs b/src/libs/Speechify/Generated/Speechify.JsonConverters.TriggerWebhookConfigUserIdentitySource.g.cs
new file mode 100644
index 0000000..95bf034
--- /dev/null
+++ b/src/libs/Speechify/Generated/Speechify.JsonConverters.TriggerWebhookConfigUserIdentitySource.g.cs
@@ -0,0 +1,53 @@
+#nullable enable
+
+namespace Speechify.JsonConverters
+{
+ ///
+ public sealed class TriggerWebhookConfigUserIdentitySourceJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ ///
+ public override global::Speechify.TriggerWebhookConfigUserIdentitySource Read(
+ ref global::System.Text.Json.Utf8JsonReader reader,
+ global::System.Type typeToConvert,
+ global::System.Text.Json.JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case global::System.Text.Json.JsonTokenType.String:
+ {
+ var stringValue = reader.GetString();
+ if (stringValue != null)
+ {
+ return global::Speechify.TriggerWebhookConfigUserIdentitySourceExtensions.ToEnum(stringValue) ?? default;
+ }
+
+ break;
+ }
+ case global::System.Text.Json.JsonTokenType.Number:
+ {
+ var numValue = reader.GetInt32();
+ return (global::Speechify.TriggerWebhookConfigUserIdentitySource)numValue;
+ }
+ case global::System.Text.Json.JsonTokenType.Null:
+ {
+ return default(global::Speechify.TriggerWebhookConfigUserIdentitySource);
+ }
+ default:
+ throw new global::System.ArgumentOutOfRangeException(nameof(reader));
+ }
+
+ return default;
+ }
+
+ ///
+ public override void Write(
+ global::System.Text.Json.Utf8JsonWriter writer,
+ global::Speechify.TriggerWebhookConfigUserIdentitySource value,
+ global::System.Text.Json.JsonSerializerOptions options)
+ {
+ writer = writer ?? throw new global::System.ArgumentNullException(nameof(writer));
+
+ writer.WriteStringValue(global::Speechify.TriggerWebhookConfigUserIdentitySourceExtensions.ToValueString(value));
+ }
+ }
+}
diff --git a/src/libs/Speechify/Generated/Speechify.JsonConverters.TriggerWebhookConfigUserIdentitySourceNullable.g.cs b/src/libs/Speechify/Generated/Speechify.JsonConverters.TriggerWebhookConfigUserIdentitySourceNullable.g.cs
new file mode 100644
index 0000000..6a625cb
--- /dev/null
+++ b/src/libs/Speechify/Generated/Speechify.JsonConverters.TriggerWebhookConfigUserIdentitySourceNullable.g.cs
@@ -0,0 +1,60 @@
+#nullable enable
+
+namespace Speechify.JsonConverters
+{
+ ///
+ public sealed class TriggerWebhookConfigUserIdentitySourceNullableJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ ///
+ public override global::Speechify.TriggerWebhookConfigUserIdentitySource? Read(
+ ref global::System.Text.Json.Utf8JsonReader reader,
+ global::System.Type typeToConvert,
+ global::System.Text.Json.JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case global::System.Text.Json.JsonTokenType.String:
+ {
+ var stringValue = reader.GetString();
+ if (stringValue != null)
+ {
+ return global::Speechify.TriggerWebhookConfigUserIdentitySourceExtensions.ToEnum(stringValue);
+ }
+
+ break;
+ }
+ case global::System.Text.Json.JsonTokenType.Number:
+ {
+ var numValue = reader.GetInt32();
+ return (global::Speechify.TriggerWebhookConfigUserIdentitySource)numValue;
+ }
+ case global::System.Text.Json.JsonTokenType.Null:
+ {
+ return default(global::Speechify.TriggerWebhookConfigUserIdentitySource?);
+ }
+ default:
+ throw new global::System.ArgumentOutOfRangeException(nameof(reader));
+ }
+
+ return default;
+ }
+
+ ///
+ public override void Write(
+ global::System.Text.Json.Utf8JsonWriter writer,
+ global::Speechify.TriggerWebhookConfigUserIdentitySource? value,
+ global::System.Text.Json.JsonSerializerOptions options)
+ {
+ writer = writer ?? throw new global::System.ArgumentNullException(nameof(writer));
+
+ if (value == null)
+ {
+ writer.WriteNullValue();
+ }
+ else
+ {
+ writer.WriteStringValue(global::Speechify.TriggerWebhookConfigUserIdentitySourceExtensions.ToValueString(value.Value));
+ }
+ }
+ }
+}
diff --git a/src/libs/Speechify/Generated/Speechify.JsonSerializerContext.g.cs b/src/libs/Speechify/Generated/Speechify.JsonSerializerContext.g.cs
index 6320440..7864d6c 100644
--- a/src/libs/Speechify/Generated/Speechify.JsonSerializerContext.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.JsonSerializerContext.g.cs
@@ -581,6 +581,7 @@ internal sealed partial class SourceGenerationContextChunk0 : global::System.Tex
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.AgentTriggerType), TypeInfoPropertyName = "AgentTriggerType2")]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.TriggerRunSpec))]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.TriggerScheduleConfig))]
+ [global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.TriggerWebhookConfigUserIdentitySource), TypeInfoPropertyName = "TriggerWebhookConfigUserIdentitySource2")]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.TriggerWebhookConfig))]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.AgentTriggerLastFireStatus), TypeInfoPropertyName = "AgentTriggerLastFireStatus2")]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.AgentTrigger))]
@@ -862,6 +863,7 @@ internal sealed partial class SourceGenerationContextChunk0 : global::System.Tex
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.SIPTransport?), TypeInfoPropertyName = "NullableSIPTransport2")]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.SIPMediaEncryption?), TypeInfoPropertyName = "NullableSIPMediaEncryption2")]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.AgentTriggerType?), TypeInfoPropertyName = "NullableAgentTriggerType2")]
+ [global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.TriggerWebhookConfigUserIdentitySource?), TypeInfoPropertyName = "NullableTriggerWebhookConfigUserIdentitySource2")]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.AgentTriggerLastFireStatus?), TypeInfoPropertyName = "NullableAgentTriggerLastFireStatus2")]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.CreateAgentTriggerRequestType?), TypeInfoPropertyName = "NullableCreateAgentTriggerRequestType2")]
[global::System.Text.Json.Serialization.JsonSerializable(typeof(global::Speechify.AgentTriggerFireResponseStatus?), TypeInfoPropertyName = "NullableAgentTriggerFireResponseStatus2")]
@@ -1523,6 +1525,10 @@ public override bool CanConvert(global::System.Type typeToConvert)
|| typeToConvert == typeof(global::Speechify.AgentTriggerType?)
+ || typeToConvert == typeof(global::Speechify.TriggerWebhookConfigUserIdentitySource)
+
+ || typeToConvert == typeof(global::Speechify.TriggerWebhookConfigUserIdentitySource?)
+
|| typeToConvert == typeof(global::Speechify.AgentTriggerLastFireStatus)
|| typeToConvert == typeof(global::Speechify.AgentTriggerLastFireStatus?)
@@ -2742,6 +2748,16 @@ public override bool CanConvert(global::System.Type typeToConvert)
return new global::Speechify.JsonConverters.AgentTriggerTypeNullableJsonConverter();
}
+ if (typeToConvert == typeof(global::Speechify.TriggerWebhookConfigUserIdentitySource))
+ {
+ return new global::Speechify.JsonConverters.TriggerWebhookConfigUserIdentitySourceJsonConverter();
+ }
+
+ if (typeToConvert == typeof(global::Speechify.TriggerWebhookConfigUserIdentitySource?))
+ {
+ return new global::Speechify.JsonConverters.TriggerWebhookConfigUserIdentitySourceNullableJsonConverter();
+ }
+
if (typeToConvert == typeof(global::Speechify.AgentTriggerLastFireStatus))
{
return new global::Speechify.JsonConverters.AgentTriggerLastFireStatusJsonConverter();
diff --git a/src/libs/Speechify/Generated/Speechify.JsonSerializerContextTypes.g.cs b/src/libs/Speechify/Generated/Speechify.JsonSerializerContextTypes.g.cs
index 8f95dfb..340a62d 100644
--- a/src/libs/Speechify/Generated/Speechify.JsonSerializerContextTypes.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.JsonSerializerContextTypes.g.cs
@@ -2241,615 +2241,619 @@ public sealed partial class JsonSerializerContextTypes
///
///
///
- public global::Speechify.TriggerWebhookConfig? Type552 { get; set; }
+ public global::Speechify.TriggerWebhookConfigUserIdentitySource? Type552 { get; set; }
///
///
///
- public global::Speechify.AgentTriggerLastFireStatus? Type553 { get; set; }
+ public global::Speechify.TriggerWebhookConfig? Type553 { get; set; }
///
///
///
- public global::Speechify.AgentTrigger? Type554 { get; set; }
+ public global::Speechify.AgentTriggerLastFireStatus? Type554 { get; set; }
///
///
///
- public global::Speechify.ListAgentTriggersResponse? Type555 { get; set; }
+ public global::Speechify.AgentTrigger? Type555 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type556 { get; set; }
+ public global::Speechify.ListAgentTriggersResponse? Type556 { get; set; }
///
///
///
- public global::Speechify.CreateAgentTriggerRequestType? Type557 { get; set; }
+ public global::System.Collections.Generic.IList? Type557 { get; set; }
///
///
///
- public global::Speechify.CreateAgentTriggerRequest? Type558 { get; set; }
+ public global::Speechify.CreateAgentTriggerRequestType? Type558 { get; set; }
///
///
///
- public global::Speechify.UpdateAgentTriggerRequest? Type559 { get; set; }
+ public global::Speechify.CreateAgentTriggerRequest? Type559 { get; set; }
///
///
///
- public global::Speechify.AgentTriggerFireResponseStatus? Type560 { get; set; }
+ public global::Speechify.UpdateAgentTriggerRequest? Type560 { get; set; }
///
///
///
- public global::Speechify.AgentTriggerFireResponse? Type561 { get; set; }
+ public global::Speechify.AgentTriggerFireResponseStatus? Type561 { get; set; }
///
///
///
- public global::Speechify.Skill? Type562 { get; set; }
+ public global::Speechify.AgentTriggerFireResponse? Type562 { get; set; }
///
///
///
- public global::Speechify.ListSkillsResponse? Type563 { get; set; }
+ public global::Speechify.Skill? Type563 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type564 { get; set; }
+ public global::Speechify.ListSkillsResponse? Type564 { get; set; }
///
///
///
- public global::Speechify.CreateSkillRequest? Type565 { get; set; }
+ public global::System.Collections.Generic.IList? Type565 { get; set; }
///
///
///
- public global::Speechify.UpdateSkillRequest? Type566 { get; set; }
+ public global::Speechify.CreateSkillRequest? Type566 { get; set; }
///
///
///
- public global::Speechify.SkillVersion? Type567 { get; set; }
+ public global::Speechify.UpdateSkillRequest? Type567 { get; set; }
///
///
///
- public global::Speechify.ListSkillVersionsResponse? Type568 { get; set; }
+ public global::Speechify.SkillVersion? Type568 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type569 { get; set; }
+ public global::Speechify.ListSkillVersionsResponse? Type569 { get; set; }
///
///
///
- public global::Speechify.AgentSkill? Type570 { get; set; }
+ public global::System.Collections.Generic.IList? Type570 { get; set; }
///
///
///
- public global::Speechify.ListAgentSkillsResponse? Type571 { get; set; }
+ public global::Speechify.AgentSkill? Type571 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type572 { get; set; }
+ public global::Speechify.ListAgentSkillsResponse? Type572 { get; set; }
///
///
///
- public global::Speechify.AttachSkillRequest? Type573 { get; set; }
+ public global::System.Collections.Generic.IList? Type573 { get; set; }
///
///
///
- public global::Speechify.UpdateAgentSkillRequest? Type574 { get; set; }
+ public global::Speechify.AttachSkillRequest? Type574 { get; set; }
///
///
///
- public global::Speechify.Store? Type575 { get; set; }
+ public global::Speechify.UpdateAgentSkillRequest? Type575 { get; set; }
///
///
///
- public global::Speechify.ListStoresResponse? Type576 { get; set; }
+ public global::Speechify.Store? Type576 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type577 { get; set; }
+ public global::Speechify.ListStoresResponse? Type577 { get; set; }
///
///
///
- public global::Speechify.CreateStoreRequest? Type578 { get; set; }
+ public global::System.Collections.Generic.IList? Type578 { get; set; }
///
///
///
- public global::Speechify.UpdateStoreRequest? Type579 { get; set; }
+ public global::Speechify.CreateStoreRequest? Type579 { get; set; }
///
///
///
- public global::Speechify.StoreCollection? Type580 { get; set; }
+ public global::Speechify.UpdateStoreRequest? Type580 { get; set; }
///
///
///
- public global::Speechify.ListStoreCollectionsResponse? Type581 { get; set; }
+ public global::Speechify.StoreCollection? Type581 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type582 { get; set; }
+ public global::Speechify.ListStoreCollectionsResponse? Type582 { get; set; }
///
///
///
- public global::Speechify.StoreDocumentSource? Type583 { get; set; }
+ public global::System.Collections.Generic.IList? Type583 { get; set; }
///
///
///
- public global::Speechify.StoreDocument? Type584 { get; set; }
+ public global::Speechify.StoreDocumentSource? Type584 { get; set; }
///
///
///
- public global::Speechify.QueryStoreDocumentsResponse? Type585 { get; set; }
+ public global::Speechify.StoreDocument? Type585 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type586 { get; set; }
+ public global::Speechify.QueryStoreDocumentsResponse? Type586 { get; set; }
///
///
///
- public global::Speechify.WriteStoreDocumentRequest? Type587 { get; set; }
+ public global::System.Collections.Generic.IList? Type587 { get; set; }
///
///
///
- public global::Speechify.StoreWhereClauseOp? Type588 { get; set; }
+ public global::Speechify.WriteStoreDocumentRequest? Type588 { get; set; }
///
///
///
- public global::Speechify.StoreWhereClause? Type589 { get; set; }
+ public global::Speechify.StoreWhereClauseOp? Type589 { get; set; }
///
///
///
- public global::Speechify.StoreOrderByDirection? Type590 { get; set; }
+ public global::Speechify.StoreWhereClause? Type590 { get; set; }
///
///
///
- public global::Speechify.StoreOrderBy? Type591 { get; set; }
+ public global::Speechify.StoreOrderByDirection? Type591 { get; set; }
///
///
///
- public global::Speechify.StoreDocumentQuery? Type592 { get; set; }
+ public global::Speechify.StoreOrderBy? Type592 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type593 { get; set; }
+ public global::Speechify.StoreDocumentQuery? Type593 { get; set; }
///
///
///
- public global::Speechify.StoreBatchWriteOp? Type594 { get; set; }
+ public global::System.Collections.Generic.IList? Type594 { get; set; }
///
///
///
- public global::Speechify.StoreBatchWrite? Type595 { get; set; }
+ public global::Speechify.StoreBatchWriteOp? Type595 { get; set; }
///
///
///
- public global::Speechify.StoreBatchRequest? Type596 { get; set; }
+ public global::Speechify.StoreBatchWrite? Type596 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type597 { get; set; }
+ public global::Speechify.StoreBatchRequest? Type597 { get; set; }
///
///
///
- public global::Speechify.StoreBatchResultOp? Type598 { get; set; }
+ public global::System.Collections.Generic.IList? Type598 { get; set; }
///
///
///
- public global::Speechify.StoreBatchResult? Type599 { get; set; }
+ public global::Speechify.StoreBatchResultOp? Type599 { get; set; }
///
///
///
- public global::Speechify.StoreBatchResponse? Type600 { get; set; }
+ public global::Speechify.StoreBatchResult? Type600 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type601 { get; set; }
+ public global::Speechify.StoreBatchResponse? Type601 { get; set; }
///
///
///
- public global::Speechify.StoreAsset? Type602 { get; set; }
+ public global::System.Collections.Generic.IList? Type602 { get; set; }
///
///
///
- public global::Speechify.ListStoreAssetsResponse? Type603 { get; set; }
+ public global::Speechify.StoreAsset? Type603 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type604 { get; set; }
+ public global::Speechify.ListStoreAssetsResponse? Type604 { get; set; }
///
///
///
- public global::Speechify.ListAgentStoresResponse? Type605 { get; set; }
+ public global::System.Collections.Generic.IList? Type605 { get; set; }
///
///
///
- public global::Speechify.HostedApiAuthMode? Type606 { get; set; }
+ public global::Speechify.ListAgentStoresResponse? Type606 { get; set; }
///
///
///
- public global::Speechify.HostedAPI? Type607 { get; set; }
+ public global::Speechify.HostedApiAuthMode? Type607 { get; set; }
///
///
///
- public global::Speechify.ListHostedAPIsResponse? Type608 { get; set; }
+ public global::Speechify.HostedAPI? Type608 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type609 { get; set; }
+ public global::Speechify.ListHostedAPIsResponse? Type609 { get; set; }
///
///
///
- public global::Speechify.CreateHostedApiRequestAuthMode? Type610 { get; set; }
+ public global::System.Collections.Generic.IList? Type610 { get; set; }
///
///
///
- public global::Speechify.CreateHostedAPIRequest? Type611 { get; set; }
+ public global::Speechify.CreateHostedApiRequestAuthMode? Type611 { get; set; }
///
///
///
- public global::Speechify.UpdateHostedApiRequestAuthMode? Type612 { get; set; }
+ public global::Speechify.CreateHostedAPIRequest? Type612 { get; set; }
///
///
///
- public global::Speechify.UpdateHostedAPIRequest? Type613 { get; set; }
+ public global::Speechify.UpdateHostedApiRequestAuthMode? Type613 { get; set; }
///
///
///
- public global::Speechify.HostedApiRouteMethod? Type614 { get; set; }
+ public global::Speechify.UpdateHostedAPIRequest? Type614 { get; set; }
///
///
///
- public global::Speechify.HostedApiResolverType? Type615 { get; set; }
+ public global::Speechify.HostedApiRouteMethod? Type615 { get; set; }
///
///
///
- public global::Speechify.HostedApiResolverWhereItemsOp? Type616 { get; set; }
+ public global::Speechify.HostedApiResolverType? Type616 { get; set; }
///
///
///
- public global::Speechify.HostedApiResolverWhereItems? Type617 { get; set; }
+ public global::Speechify.HostedApiResolverWhereItemsOp? Type617 { get; set; }
///
///
///
- public global::Speechify.HostedApiResolverOrderByDirection? Type618 { get; set; }
+ public global::Speechify.HostedApiResolverWhereItems? Type618 { get; set; }
///
///
///
- public global::Speechify.HostedApiResolverOrderBy? Type619 { get; set; }
+ public global::Speechify.HostedApiResolverOrderByDirection? Type619 { get; set; }
///
///
///
- public global::Speechify.HostedAPIResolver? Type620 { get; set; }
+ public global::Speechify.HostedApiResolverOrderBy? Type620 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type621 { get; set; }
+ public global::Speechify.HostedAPIResolver? Type621 { get; set; }
///
///
///
- public global::Speechify.HostedAPIRoute? Type622 { get; set; }
+ public global::System.Collections.Generic.IList? Type622 { get; set; }
///
///
///
- public global::Speechify.ListHostedAPIRoutesResponse? Type623 { get; set; }
+ public global::Speechify.HostedAPIRoute? Type623 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type624 { get; set; }
+ public global::Speechify.ListHostedAPIRoutesResponse? Type624 { get; set; }
///
///
///
- public global::Speechify.CreateHostedApiRouteRequestMethod? Type625 { get; set; }
+ public global::System.Collections.Generic.IList? Type625 { get; set; }
///
///
///
- public global::Speechify.CreateHostedAPIRouteRequest? Type626 { get; set; }
+ public global::Speechify.CreateHostedApiRouteRequestMethod? Type626 { get; set; }
///
///
///
- public global::Speechify.UpdateHostedApiRouteRequestMethod? Type627 { get; set; }
+ public global::Speechify.CreateHostedAPIRouteRequest? Type627 { get; set; }
///
///
///
- public global::Speechify.UpdateHostedAPIRouteRequest? Type628 { get; set; }
+ public global::Speechify.UpdateHostedApiRouteRequestMethod? Type628 { get; set; }
///
///
///
- public global::Speechify.HostedAPIKey? Type629 { get; set; }
+ public global::Speechify.UpdateHostedAPIRouteRequest? Type629 { get; set; }
///
///
///
- public global::Speechify.ListHostedAPIKeysResponse? Type630 { get; set; }
+ public global::Speechify.HostedAPIKey? Type630 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type631 { get; set; }
+ public global::Speechify.ListHostedAPIKeysResponse? Type631 { get; set; }
///
///
///
- public global::Speechify.CreateHostedAPIKeyRequest? Type632 { get; set; }
+ public global::System.Collections.Generic.IList? Type632 { get; set; }
///
///
///
- public global::Speechify.ContactIdentifierKind? Type633 { get; set; }
+ public global::Speechify.CreateHostedAPIKeyRequest? Type633 { get; set; }
///
///
///
- public global::Speechify.ContactIdentifierAssertedBy? Type634 { get; set; }
+ public global::Speechify.ContactIdentifierKind? Type634 { get; set; }
///
///
///
- public global::Speechify.ContactIdentifier? Type635 { get; set; }
+ public global::Speechify.ContactIdentifierAssertedBy? Type635 { get; set; }
///
///
///
- public global::Speechify.Contact? Type636 { get; set; }
+ public global::Speechify.ContactIdentifier? Type636 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type637 { get; set; }
+ public global::Speechify.Contact? Type637 { get; set; }
///
///
///
- public global::Speechify.ListContactsResponse? Type638 { get; set; }
+ public global::System.Collections.Generic.IList? Type638 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type639 { get; set; }
+ public global::Speechify.ListContactsResponse? Type639 { get; set; }
///
///
///
- public global::Speechify.UpdateContactRequest? Type640 { get; set; }
+ public global::System.Collections.Generic.IList? Type640 { get; set; }
///
///
///
- public global::Speechify.DeleteContactResponse? Type641 { get; set; }
+ public global::Speechify.UpdateContactRequest? Type641 { get; set; }
///
///
///
- public global::Speechify.AttachContactIdentifierRequestKind? Type642 { get; set; }
+ public global::Speechify.DeleteContactResponse? Type642 { get; set; }
///
///
///
- public global::Speechify.AttachContactIdentifierRequest? Type643 { get; set; }
+ public global::Speechify.AttachContactIdentifierRequestKind? Type643 { get; set; }
///
///
///
- public global::Speechify.MergeContactsRequest? Type644 { get; set; }
+ public global::Speechify.AttachContactIdentifierRequest? Type644 { get; set; }
///
///
///
- public global::Speechify.MergeContactsResponseMerged? Type645 { get; set; }
+ public global::Speechify.MergeContactsRequest? Type645 { get; set; }
///
///
///
- public global::Speechify.MergeContactsResponse? Type646 { get; set; }
+ public global::Speechify.MergeContactsResponseMerged? Type646 { get; set; }
///
///
///
- public global::Speechify.BillingEntitlementsMaxLlmModelClass? Type647 { get; set; }
+ public global::Speechify.MergeContactsResponse? Type647 { get; set; }
///
///
///
- public global::Speechify.BillingEntitlements? Type648 { get; set; }
+ public global::Speechify.BillingEntitlementsMaxLlmModelClass? Type648 { get; set; }
///
///
///
- public global::Speechify.EntitlementsResponse? Type649 { get; set; }
+ public global::Speechify.BillingEntitlements? Type649 { get; set; }
///
///
///
- public global::System.Collections.Generic.Dictionary? Type650 { get; set; }
+ public global::Speechify.EntitlementsResponse? Type650 { get; set; }
///
///
///
- public global::Speechify.ContactResolver? Type651 { get; set; }
+ public global::System.Collections.Generic.Dictionary? Type651 { get; set; }
///
///
///
- public global::Speechify.PutContactResolverRequest? Type652 { get; set; }
+ public global::Speechify.ContactResolver? Type652 { get; set; }
///
///
///
- public global::Speechify.WebhookEndpoint? Type653 { get; set; }
+ public global::Speechify.PutContactResolverRequest? Type653 { get; set; }
///
///
///
- public global::Speechify.ListWebhookEndpointsResponse? Type654 { get; set; }
+ public global::Speechify.WebhookEndpoint? Type654 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type655 { get; set; }
+ public global::Speechify.ListWebhookEndpointsResponse? Type655 { get; set; }
///
///
///
- public global::Speechify.CreateWebhookEndpointRequest? Type656 { get; set; }
+ public global::System.Collections.Generic.IList? Type656 { get; set; }
///
///
///
- public global::Speechify.UpdateWebhookEndpointRequest? Type657 { get; set; }
+ public global::Speechify.CreateWebhookEndpointRequest? Type657 { get; set; }
///
///
///
- public global::Speechify.WebhookEndpointDelivery? Type658 { get; set; }
+ public global::Speechify.UpdateWebhookEndpointRequest? Type658 { get; set; }
///
///
///
- public global::Speechify.ListWebhookEndpointDeliveriesResponse? Type659 { get; set; }
+ public global::Speechify.WebhookEndpointDelivery? Type659 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type660 { get; set; }
+ public global::Speechify.ListWebhookEndpointDeliveriesResponse? Type660 { get; set; }
///
///
///
- public global::Speechify.Project? Type661 { get; set; }
+ public global::System.Collections.Generic.IList? Type661 { get; set; }
///
///
///
- public global::Speechify.ListProjectsResponse? Type662 { get; set; }
+ public global::Speechify.Project? Type662 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type663 { get; set; }
+ public global::Speechify.ListProjectsResponse? Type663 { get; set; }
///
///
///
- public global::Speechify.CreateProjectRequest? Type664 { get; set; }
+ public global::System.Collections.Generic.IList? Type664 { get; set; }
///
///
///
- public global::Speechify.UpdateProjectRequest? Type665 { get; set; }
+ public global::Speechify.CreateProjectRequest? Type665 { get; set; }
///
///
///
- public global::Speechify.DeleteProjectRequestMode? Type666 { get; set; }
+ public global::Speechify.UpdateProjectRequest? Type666 { get; set; }
///
///
///
- public global::Speechify.DeleteProjectRequest? Type667 { get; set; }
+ public global::Speechify.DeleteProjectRequestMode? Type667 { get; set; }
///
///
///
- public global::Speechify.ProjectRestoredCounts? Type668 { get; set; }
+ public global::Speechify.DeleteProjectRequest? Type668 { get; set; }
///
///
///
- public global::Speechify.ProjectStillRevokedCounts? Type669 { get; set; }
+ public global::Speechify.ProjectRestoredCounts? Type669 { get; set; }
///
///
///
- public global::Speechify.ProjectRestore? Type670 { get; set; }
+ public global::Speechify.ProjectStillRevokedCounts? Type670 { get; set; }
///
///
///
- public global::Speechify.ProjectTeardownBlockerKind? Type671 { get; set; }
+ public global::Speechify.ProjectRestore? Type671 { get; set; }
///
///
///
- public global::Speechify.ProjectTeardownBlockerBlocksItems? Type672 { get; set; }
+ public global::Speechify.ProjectTeardownBlockerKind? Type672 { get; set; }
///
///
///
- public global::Speechify.ProjectTeardownBlocker? Type673 { get; set; }
+ public global::Speechify.ProjectTeardownBlockerBlocksItems? Type673 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type674 { get; set; }
+ public global::Speechify.ProjectTeardownBlocker? Type674 { get; set; }
///
///
///
- public global::Speechify.ProjectTeardownGroup? Type675 { get; set; }
+ public global::System.Collections.Generic.IList? Type675 { get; set; }
///
///
///
- public global::Speechify.ProjectTeardown? Type676 { get; set; }
+ public global::Speechify.ProjectTeardownGroup? Type676 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type677 { get; set; }
+ public global::Speechify.ProjectTeardown? Type677 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type678 { get; set; }
+ public global::System.Collections.Generic.IList? Type678 { get; set; }
///
///
///
- public global::Speechify.ProjectResourceCounts? Type679 { get; set; }
+ public global::System.Collections.Generic.IList? Type679 { get; set; }
///
///
///
- public global::Speechify.ProjectStats? Type680 { get; set; }
+ public global::Speechify.ProjectResourceCounts? Type680 { get; set; }
///
///
///
- public global::Speechify.PromoteProjectRequest? Type681 { get; set; }
+ public global::Speechify.ProjectStats? Type681 { get; set; }
///
///
///
- public global::Speechify.PromoteCreated? Type682 { get; set; }
+ public global::Speechify.PromoteProjectRequest? Type682 { get; set; }
///
///
///
- public global::Speechify.PromoteReused? Type683 { get; set; }
+ public global::Speechify.PromoteCreated? Type683 { get; set; }
///
///
///
- public global::Speechify.PromoteAttentionKind? Type684 { get; set; }
+ public global::Speechify.PromoteReused? Type684 { get; set; }
///
///
///
- public global::Speechify.PromoteAttentionReason? Type685 { get; set; }
+ public global::Speechify.PromoteAttentionKind? Type685 { get; set; }
///
///
///
- public global::Speechify.PromoteAttention? Type686 { get; set; }
+ public global::Speechify.PromoteAttentionReason? Type686 { get; set; }
///
///
///
- public global::Speechify.PromoteProjectResponse? Type687 { get; set; }
+ public global::Speechify.PromoteAttention? Type687 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type688 { get; set; }
+ public global::Speechify.PromoteProjectResponse? Type688 { get; set; }
///
///
///
- public global::Speechify.ProjectAuditEventType? Type689 { get; set; }
+ public global::System.Collections.Generic.IList? Type689 { get; set; }
///
///
///
- public global::Speechify.ProjectAuditEvent? Type690 { get; set; }
+ public global::Speechify.ProjectAuditEventType? Type690 { get; set; }
///
///
///
- public global::Speechify.ProjectAuditResponse? Type691 { get; set; }
+ public global::Speechify.ProjectAuditEvent? Type691 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type692 { get; set; }
+ public global::Speechify.ProjectAuditResponse? Type692 { get; set; }
///
///
///
- public global::Speechify.ProjectMember? Type693 { get; set; }
+ public global::System.Collections.Generic.IList? Type693 { get; set; }
///
///
///
- public global::Speechify.ProjectMembersResponse? Type694 { get; set; }
+ public global::Speechify.ProjectMember? Type694 { get; set; }
///
///
///
- public global::System.Collections.Generic.IList? Type695 { get; set; }
+ public global::Speechify.ProjectMembersResponse? Type695 { get; set; }
///
///
///
- public global::Speechify.GrantProjectMemberRequest? Type696 { get; set; }
+ public global::System.Collections.Generic.IList? Type696 { get; set; }
///
///
///
- public global::Speechify.CreateRequest? Type697 { get; set; }
+ public global::Speechify.GrantProjectMemberRequest? Type697 { get; set; }
///
///
///
- public global::Speechify.DetectRequest? Type698 { get; set; }
+ public global::Speechify.CreateRequest? Type698 { get; set; }
///
///
///
- public global::Speechify.VerifyRequest? Type699 { get; set; }
+ public global::Speechify.DetectRequest? Type699 { get; set; }
///
///
///
- public global::Speechify.UploadDocumentRequest? Type700 { get; set; }
+ public global::Speechify.VerifyRequest? Type700 { get; set; }
///
///
///
- public global::Speechify.UpdateDocumentRequest? Type701 { get; set; }
+ public global::Speechify.UploadDocumentRequest? Type701 { get; set; }
///
///
///
- public global::Speechify.RunTestRequest? Type702 { get; set; }
+ public global::Speechify.UpdateDocumentRequest? Type702 { get; set; }
///
///
///
- public global::Speechify.UploadRequest? Type703 { get; set; }
+ public global::Speechify.RunTestRequest? Type703 { get; set; }
///
///
///
- public global::Speechify.UploadAssetRequest? Type704 { get; set; }
+ public global::Speechify.UploadRequest? Type704 { get; set; }
+ ///
+ ///
+ ///
+ public global::Speechify.UploadAssetRequest? Type705 { get; set; }
///
///
diff --git a/src/libs/Speechify/Generated/Speechify.Models.AgentTTSConfig.g.cs b/src/libs/Speechify/Generated/Speechify.Models.AgentTTSConfig.g.cs
index 695ffba..ed96725 100644
--- a/src/libs/Speechify/Generated/Speechify.Models.AgentTTSConfig.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.Models.AgentTTSConfig.g.cs
@@ -26,21 +26,21 @@ public sealed partial class AgentTTSConfig
///
/// Pins the Simba model this agent synthesizes on. **Null is the
/// default and the recommended setting**: the agent follows the
- /// model its voice is curated for, so a voice promoted to a newer
- /// training moves with it and no configuration goes stale.
+ /// model VMS proposes for its voice (`default_model` on
+ /// GET /v1/agents/voices), so a voice promoted to a newer training
+ /// moves with it and no configuration goes stale.
/// Set it to override that choice in either direction - onto an
/// experimental training, or back down off one. Call
- /// GET /v1/agents/tts-models for the catalog, and read the voice's
- /// `models` and `default_model` on GET /v1/agents/voices for what
- /// this particular voice can serve on.
+ /// GET /v1/agents/tts-models for the catalog. Every model there
+ /// serves every voice we speak ourselves; a partner voice takes none
+ /// of them, because its provider selects its own model.
/// A write is rejected when the agent could not actually be served
/// with the model: an English-only model on an agent that resolves
/// through the multilingual serving (it declares
- /// `additional_languages`, or its own `language` is not English),
- /// or a model the chosen voice is not curated for. The pair is
- /// re-checked whenever `voice_id`, `language`, or
- /// `additional_languages` change, so a stored pin cannot be
- /// orphaned by an edit elsewhere.
+ /// `additional_languages`, or its own `language` is not English), or
+ /// any Simba model on a partner voice. It is re-checked whenever
+ /// `language` or `additional_languages` change, so a stored pin
+ /// cannot be orphaned by an edit elsewhere.
/// Send `null` (or `""`) to clear it back to automatic.
///
[global::System.Text.Json.Serialization.JsonPropertyName("model")]
@@ -66,21 +66,21 @@ public sealed partial class AgentTTSConfig
///
/// Pins the Simba model this agent synthesizes on. **Null is the
/// default and the recommended setting**: the agent follows the
- /// model its voice is curated for, so a voice promoted to a newer
- /// training moves with it and no configuration goes stale.
+ /// model VMS proposes for its voice (`default_model` on
+ /// GET /v1/agents/voices), so a voice promoted to a newer training
+ /// moves with it and no configuration goes stale.
/// Set it to override that choice in either direction - onto an
/// experimental training, or back down off one. Call
- /// GET /v1/agents/tts-models for the catalog, and read the voice's
- /// `models` and `default_model` on GET /v1/agents/voices for what
- /// this particular voice can serve on.
+ /// GET /v1/agents/tts-models for the catalog. Every model there
+ /// serves every voice we speak ourselves; a partner voice takes none
+ /// of them, because its provider selects its own model.
/// A write is rejected when the agent could not actually be served
/// with the model: an English-only model on an agent that resolves
/// through the multilingual serving (it declares
- /// `additional_languages`, or its own `language` is not English),
- /// or a model the chosen voice is not curated for. The pair is
- /// re-checked whenever `voice_id`, `language`, or
- /// `additional_languages` change, so a stored pin cannot be
- /// orphaned by an edit elsewhere.
+ /// `additional_languages`, or its own `language` is not English), or
+ /// any Simba model on a partner voice. It is re-checked whenever
+ /// `language` or `additional_languages` change, so a stored pin
+ /// cannot be orphaned by an edit elsewhere.
/// Send `null` (or `""`) to clear it back to automatic.
///
#if NET7_0_OR_GREATER
diff --git a/src/libs/Speechify/Generated/Speechify.Models.AgentTTSModelInfo.g.cs b/src/libs/Speechify/Generated/Speechify.Models.AgentTTSModelInfo.g.cs
index d4cb2ea..1ad9909 100644
--- a/src/libs/Speechify/Generated/Speechify.Models.AgentTTSModelInfo.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.Models.AgentTTSModelInfo.g.cs
@@ -1,4 +1,6 @@
+#pragma warning disable CS0618 // Type or member is obsolete
+
#nullable enable
namespace Speechify
@@ -39,9 +41,8 @@ public sealed partial class AgentTTSModelInfo
public required bool Beta { get; set; }
///
- /// True for the model an agent lands on with no pin and an
- /// uncurated voice. The floor of the catalog, and the only entry
- /// selectable for every agent and every voice.
+ /// True for the model an agent lands on with no pin. The floor of
+ /// the catalog, and the only entry selectable in every language.
///
[global::System.Text.Json.Serialization.JsonPropertyName("default")]
[global::System.Text.Json.Serialization.JsonRequired]
@@ -56,14 +57,11 @@ public sealed partial class AgentTTSModelInfo
public required bool EnglishOnly { get; set; }
///
- /// A voice must be registered for this model before it can be
- /// selected with that voice, because the model has a voice roster
- /// built for it. False for a model offered across the catalog.
- /// Either way, the voice's `models` array on GET /v1/agents/voices is
- /// the per-voice answer and the one to drive a picker from - a voice
- /// built for a curated model is absent from the open models too. This
- /// flag explains WHY a voice is absent; it does not decide whether to
- /// check.
+ /// Deprecated and always `false`. No model requires a voice to be
+ /// registered for it: every training conditions on the voice's own
+ /// prompt audio, so every model is selectable on every voice we speak
+ /// ourselves. The field stays on the response for compatibility and
+ /// can be ignored.
///
[global::System.Text.Json.Serialization.JsonPropertyName("requires_voice_curation")]
[global::System.Text.Json.Serialization.JsonRequired]
@@ -88,23 +86,19 @@ public sealed partial class AgentTTSModelInfo
/// simply not one agents are steered onto by default.
///
///
- /// True for the model an agent lands on with no pin and an
- /// uncurated voice. The floor of the catalog, and the only entry
- /// selectable for every agent and every voice.
+ /// True for the model an agent lands on with no pin. The floor of
+ /// the catalog, and the only entry selectable in every language.
///
///
/// The model has no multilingual deployment. An agent that resolves
/// through the multilingual serving cannot select it at all.
///
///
- /// A voice must be registered for this model before it can be
- /// selected with that voice, because the model has a voice roster
- /// built for it. False for a model offered across the catalog.
- /// Either way, the voice's `models` array on GET /v1/agents/voices is
- /// the per-voice answer and the one to drive a picker from - a voice
- /// built for a curated model is absent from the open models too. This
- /// flag explains WHY a voice is absent; it does not decide whether to
- /// check.
+ /// Deprecated and always `false`. No model requires a voice to be
+ /// registered for it: every training conditions on the voice's own
+ /// prompt audio, so every model is selectable on every voice we speak
+ /// ourselves. The field stays on the response for compatibility and
+ /// can be ignored.
///
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.SetsRequiredMembers]
diff --git a/src/libs/Speechify/Generated/Speechify.Models.AgentVoiceModel.g.cs b/src/libs/Speechify/Generated/Speechify.Models.AgentVoiceModel.g.cs
index 596c984..30f8788 100644
--- a/src/libs/Speechify/Generated/Speechify.Models.AgentVoiceModel.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.Models.AgentVoiceModel.g.cs
@@ -6,17 +6,12 @@ namespace Speechify
///
/// One Simba model version the voice can be synthesised through, and
/// therefore one an agent on this voice may pin with `tts.model`.
- /// Every agent voice supports `simba-3.0`. English-only upgrade tiers
- /// (`simba-3.5-turbo`, `simba-3.2`) are listed when this voice can
- /// select them: a tier with `requires_voice_curation` only where the
- /// voice is curated for it, a tier without it on any voice we speak
- /// ourselves EXCEPT one built for a curated roster, whose speaker
- /// embedding is valid for that training alone. A partner voice lists no
+ /// Every voice we speak ourselves supports every model listed here -
+ /// `simba-3.0` plus the English-only tiers `simba-3.2` and
+ /// `simba-3.5-turbo` - because each training conditions on the voice's
+ /// own prompt audio, cloned voices included. A partner voice lists no
/// tier at all - Simba model selection does not apply to it.
- /// So read this array rather than inferring membership from
- /// `requires_voice_curation`: a voice can be absent from a model that
- /// needs no curation. It is the per-voice answer for every model, and a
- /// pin outside it is a 400. Which of these `tts.model: null`
+ /// A pin outside this array is a 400. Which of these `tts.model: null`
/// actually resolves to is `default_model`, not the order here.
/// Like `default_model`, the tiers listed here are the ENGLISH answer,
/// and they are a fact about the voice rather than about any one agent.
diff --git a/src/libs/Speechify/Generated/Speechify.Models.Model.g.cs b/src/libs/Speechify/Generated/Speechify.Models.Model.g.cs
index 34b870e..59225f8 100644
--- a/src/libs/Speechify/Generated/Speechify.Models.Model.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.Models.Model.g.cs
@@ -1,4 +1,6 @@
+#pragma warning disable CS0618 // Type or member is obsolete
+
#nullable enable
namespace Speechify
@@ -26,8 +28,8 @@ public sealed partial class Model
///
/// Whether this is the model used when a synthesis request omits
/// `model`. Exactly one model in the list is the default. Distinct
- /// from `recommended`: the default accepts every voice, while the
- /// recommended model may serve a curated or English-only set.
+ /// from `recommended`: the default accepts every voice in every
+ /// language, while the recommended model may be English-only.
///
[global::System.Text.Json.Serialization.JsonPropertyName("default")]
[global::System.Text.Json.Serialization.JsonRequired]
@@ -103,13 +105,12 @@ public sealed partial class Model
public required global::System.Collections.Generic.IList Endpoints { get; set; }
///
- /// Whether the model's stock voices are restricted to the set curated
- /// for it. When true, pick a stock voice whose `models` array in
- /// GET /v1/voices names this model; any other stock voice is rejected.
- /// When false, every stock catalogue voice works. Cloned voices are
- /// governed separately - always read each voice's own `models` array in
- /// GET /v1/voices, which reflects what your workspace may actually
- /// synthesize.
+ /// Deprecated and always `false`. No model restricts synthesis to a
+ /// registered voice set: every training conditions on the voice's own
+ /// prompt audio, so every catalogue voice and every cloned voice works
+ /// on every model, subject only to `english_voices_only`. Each voice's
+ /// `models` array in GET /v1/voices stays the per-voice answer. The
+ /// field remains on the response for compatibility.
///
[global::System.Text.Json.Serialization.JsonPropertyName("curated_voices")]
[global::System.Text.Json.Serialization.JsonRequired]
@@ -143,8 +144,8 @@ public sealed partial class Model
///
/// Whether this is the model used when a synthesis request omits
/// `model`. Exactly one model in the list is the default. Distinct
- /// from `recommended`: the default accepts every voice, while the
- /// recommended model may serve a curated or English-only set.
+ /// from `recommended`: the default accepts every voice in every
+ /// language, while the recommended model may be English-only.
///
///
/// Whether this is the model we recommend for new integrations.
@@ -172,13 +173,12 @@ public sealed partial class Model
/// response, so branch on it instead of discovering it at call time.
///
///
- /// Whether the model's stock voices are restricted to the set curated
- /// for it. When true, pick a stock voice whose `models` array in
- /// GET /v1/voices names this model; any other stock voice is rejected.
- /// When false, every stock catalogue voice works. Cloned voices are
- /// governed separately - always read each voice's own `models` array in
- /// GET /v1/voices, which reflects what your workspace may actually
- /// synthesize.
+ /// Deprecated and always `false`. No model restricts synthesis to a
+ /// registered voice set: every training conditions on the voice's own
+ /// prompt audio, so every catalogue voice and every cloned voice works
+ /// on every model, subject only to `english_voices_only`. Each voice's
+ /// `models` array in GET /v1/voices stays the per-voice answer. The
+ /// field remains on the response for compatibility.
///
///
/// Whether the model rejects a non-English voice. Independent of
diff --git a/src/libs/Speechify/Generated/Speechify.Models.TriggerRunSpec.g.cs b/src/libs/Speechify/Generated/Speechify.Models.TriggerRunSpec.g.cs
index 7be6d96..46ede35 100644
--- a/src/libs/Speechify/Generated/Speechify.Models.TriggerRunSpec.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.Models.TriggerRunSpec.g.cs
@@ -58,7 +58,9 @@ public sealed partial class TriggerRunSpec
/// `GET /v1/agents/runs?user_identity=` finds it, so you keep no
/// trigger-to-person index of your own. Validated when the trigger is
/// saved, by the run surface's rule: must not begin with `user_`,
- /// `embed_` or `anon_`.
+ /// `embed_` or `anon_`.
+ /// Leave it empty and set `webhook.user_identity_source` to `fire`
+ /// when one trigger serves many people and each delivery says which.
///
[global::System.Text.Json.Serialization.JsonPropertyName("user_identity")]
public string? UserIdentity { get; set; }
@@ -119,7 +121,9 @@ public sealed partial class TriggerRunSpec
/// `GET /v1/agents/runs?user_identity=` finds it, so you keep no
/// trigger-to-person index of your own. Validated when the trigger is
/// saved, by the run surface's rule: must not begin with `user_`,
- /// `embed_` or `anon_`.
+ /// `embed_` or `anon_`.
+ /// Leave it empty and set `webhook.user_identity_source` to `fire`
+ /// when one trigger serves many people and each delivery says which.
///
///
/// Up to 16 key/value pairs merged onto every run this trigger fires,
diff --git a/src/libs/Speechify/Generated/Speechify.Models.TriggerWebhookConfig.g.cs b/src/libs/Speechify/Generated/Speechify.Models.TriggerWebhookConfig.g.cs
index d8e1862..95e77e1 100644
--- a/src/libs/Speechify/Generated/Speechify.Models.TriggerWebhookConfig.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.Models.TriggerWebhookConfig.g.cs
@@ -14,6 +14,27 @@ public sealed partial class TriggerWebhookConfig
[global::System.Text.Json.Serialization.JsonPropertyName("payload_variable")]
public string? PayloadVariable { get; set; }
+ ///
+ /// Who names the person a fired run acts for. Defaults to `trigger`.
+ /// `trigger` uses `run.user_identity`, so every fire on
+ /// this trigger acts for the same person - or for nobody, when it is
+ /// unset.
+ /// `fire` takes the person from each delivery, as a
+ /// `Speechify-User-Identity` header on the fire request. One trigger
+ /// then serves every person your connector watches, instead of one
+ /// trigger per person. `run.user_identity` must be empty when you
+ /// choose it: a trigger that both pins a person and reads one per
+ /// delivery would run silently as the pinned person whenever your
+ /// connector forgot the header, so the combination is refused with a
+ /// `400` when the trigger is saved.
+ /// A fire that names a person the trigger does not accept, and a fire
+ /// that names nobody on a `fire`-sourced trigger, are both refused
+ /// with a `400` rather than quietly running for the wrong person.
+ ///
+ [global::System.Text.Json.Serialization.JsonPropertyName("user_identity_source")]
+ [global::System.Text.Json.Serialization.JsonConverter(typeof(global::Speechify.JsonConverters.TriggerWebhookConfigUserIdentitySourceJsonConverter))]
+ public global::Speechify.TriggerWebhookConfigUserIdentitySource? UserIdentitySource { get; set; }
+
///
/// Additional properties that are not explicitly defined in the schema
///
@@ -26,13 +47,32 @@ public sealed partial class TriggerWebhookConfig
///
/// The run-variable key the POST body is bound to. Defaults to `payload`.
///
+ ///
+ /// Who names the person a fired run acts for. Defaults to `trigger`.
+ /// `trigger` uses `run.user_identity`, so every fire on
+ /// this trigger acts for the same person - or for nobody, when it is
+ /// unset.
+ /// `fire` takes the person from each delivery, as a
+ /// `Speechify-User-Identity` header on the fire request. One trigger
+ /// then serves every person your connector watches, instead of one
+ /// trigger per person. `run.user_identity` must be empty when you
+ /// choose it: a trigger that both pins a person and reads one per
+ /// delivery would run silently as the pinned person whenever your
+ /// connector forgot the header, so the combination is refused with a
+ /// `400` when the trigger is saved.
+ /// A fire that names a person the trigger does not accept, and a fire
+ /// that names nobody on a `fire`-sourced trigger, are both refused
+ /// with a `400` rather than quietly running for the wrong person.
+ ///
#if NET7_0_OR_GREATER
[global::System.Diagnostics.CodeAnalysis.SetsRequiredMembers]
#endif
public TriggerWebhookConfig(
- string? payloadVariable)
+ string? payloadVariable,
+ global::Speechify.TriggerWebhookConfigUserIdentitySource? userIdentitySource)
{
this.PayloadVariable = payloadVariable;
+ this.UserIdentitySource = userIdentitySource;
}
///
diff --git a/src/libs/Speechify/Generated/Speechify.Models.TriggerWebhookConfigUserIdentitySource.g.cs b/src/libs/Speechify/Generated/Speechify.Models.TriggerWebhookConfigUserIdentitySource.g.cs
new file mode 100644
index 0000000..df7102f
--- /dev/null
+++ b/src/libs/Speechify/Generated/Speechify.Models.TriggerWebhookConfigUserIdentitySource.g.cs
@@ -0,0 +1,65 @@
+
+#nullable enable
+
+namespace Speechify
+{
+ ///
+ /// Who names the person a fired run acts for. Defaults to `trigger`.
+ /// `trigger` uses `run.user_identity`, so every fire on
+ /// this trigger acts for the same person - or for nobody, when it is
+ /// unset.
+ /// `fire` takes the person from each delivery, as a
+ /// `Speechify-User-Identity` header on the fire request. One trigger
+ /// then serves every person your connector watches, instead of one
+ /// trigger per person. `run.user_identity` must be empty when you
+ /// choose it: a trigger that both pins a person and reads one per
+ /// delivery would run silently as the pinned person whenever your
+ /// connector forgot the header, so the combination is refused with a
+ /// `400` when the trigger is saved.
+ /// A fire that names a person the trigger does not accept, and a fire
+ /// that names nobody on a `fire`-sourced trigger, are both refused
+ /// with a `400` rather than quietly running for the wrong person.
+ ///
+ public enum TriggerWebhookConfigUserIdentitySource
+ {
+ ///
+ ///
+ ///
+ Fire,
+ ///
+ /// a trigger that both pins a person and reads one per
+ ///
+ Trigger,
+ }
+
+ ///
+ /// Enum extensions to do fast conversions without the reflection.
+ ///
+ public static class TriggerWebhookConfigUserIdentitySourceExtensions
+ {
+ ///
+ /// Converts an enum to a string.
+ ///
+ public static string ToValueString(this TriggerWebhookConfigUserIdentitySource value)
+ {
+ return value switch
+ {
+ TriggerWebhookConfigUserIdentitySource.Fire => "fire",
+ TriggerWebhookConfigUserIdentitySource.Trigger => "trigger",
+ _ => throw new global::System.ArgumentOutOfRangeException(nameof(value), value, null),
+ };
+ }
+ ///
+ /// Converts an string to a enum.
+ ///
+ public static TriggerWebhookConfigUserIdentitySource? ToEnum(string value)
+ {
+ return value switch
+ {
+ "fire" => TriggerWebhookConfigUserIdentitySource.Fire,
+ "trigger" => TriggerWebhookConfigUserIdentitySource.Trigger,
+ _ => null,
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libs/Speechify/Generated/Speechify.TriggersClient.Fire.g.cs b/src/libs/Speechify/Generated/Speechify.TriggersClient.Fire.g.cs
index 659ba4e..d62546c 100644
--- a/src/libs/Speechify/Generated/Speechify.TriggersClient.Fire.g.cs
+++ b/src/libs/Speechify/Generated/Speechify.TriggersClient.Fire.g.cs
@@ -29,12 +29,14 @@ partial void PrepareFireArguments(
global::System.Net.Http.HttpClient httpClient,
ref string triggerId,
ref string? speechifyVersion,
+ ref string? speechifyUserIdentity,
object request);
partial void PrepareFireRequest(
global::System.Net.Http.HttpClient httpClient,
global::System.Net.Http.HttpRequestMessage httpRequestMessage,
string triggerId,
string? speechifyVersion,
+ string? speechifyUserIdentity,
object request);
partial void ProcessFireResponse(
global::System.Net.Http.HttpClient httpClient,
@@ -51,10 +53,24 @@ partial void ProcessFireResponseContent(
/// one-time fire token as `Authorization: Bearer <secret>`. The JSON body
/// becomes the started run's payload variable. Supply an `Idempotency-Key`
/// (or `Speechify-Delivery-Id`) header to make a redelivery replay the
- /// original run instead of starting a second one.
+ /// original run instead of starting a second one.
+ /// **Reacting to something that happened, for one person.** Set the
+ /// trigger's `webhook.user_identity_source` to `fire` and send
+ /// `Speechify-User-Identity` on each POST. The run then acts for that
+ /// person - their memory, and every tool call told who it is for - so one
+ /// trigger serves every person your connector watches and you hold no
+ /// trigger id per person. Use the plain form (`user_identity_source:
+ /// trigger`) when the work is the same whoever it is for: a nightly
+ /// reconciliation, an alert fan-out, a build that finished.
+ /// **Rate.** A trigger admits 60 fires a minute, and 12 a minute for any
+ /// one person; past either the endpoint answers `429` with `Retry-After`.
+ /// A fire starts a durable run against your credit balance, so a burst is
+ /// deferred rather than fanned out. Retry the refused delivery under the
+ /// same delivery id and it runs exactly once.
///
///
///
+ ///
///
/// Per-request overrides such as headers, query parameters, timeout, retries, and response buffering.
/// The token to cancel the operation with
@@ -64,6 +80,7 @@ partial void ProcessFireResponseContent(
object request,
string? speechifyVersion = default,
+ string? speechifyUserIdentity = default,
global::Speechify.AutoSDKRequestOptions? requestOptions = default,
global::System.Threading.CancellationToken cancellationToken = default)
{
@@ -72,6 +89,7 @@ partial void ProcessFireResponseContent(
request: request,
speechifyVersion: speechifyVersion,
+ speechifyUserIdentity: speechifyUserIdentity,
requestOptions: requestOptions,
cancellationToken: cancellationToken
).ConfigureAwait(false);
@@ -84,10 +102,24 @@ partial void ProcessFireResponseContent(
/// one-time fire token as `Authorization: Bearer <secret>`. The JSON body
/// becomes the started run's payload variable. Supply an `Idempotency-Key`
/// (or `Speechify-Delivery-Id`) header to make a redelivery replay the
- /// original run instead of starting a second one.
+ /// original run instead of starting a second one.
+ /// **Reacting to something that happened, for one person.** Set the
+ /// trigger's `webhook.user_identity_source` to `fire` and send
+ /// `Speechify-User-Identity` on each POST. The run then acts for that
+ /// person - their memory, and every tool call told who it is for - so one
+ /// trigger serves every person your connector watches and you hold no
+ /// trigger id per person. Use the plain form (`user_identity_source:
+ /// trigger`) when the work is the same whoever it is for: a nightly
+ /// reconciliation, an alert fan-out, a build that finished.
+ /// **Rate.** A trigger admits 60 fires a minute, and 12 a minute for any
+ /// one person; past either the endpoint answers `429` with `Retry-After`.
+ /// A fire starts a durable run against your credit balance, so a burst is
+ /// deferred rather than fanned out. Retry the refused delivery under the
+ /// same delivery id and it runs exactly once.
///
///
///
+ ///
///
/// Per-request overrides such as headers, query parameters, timeout, retries, and response buffering.
/// The token to cancel the operation with
@@ -97,6 +129,7 @@ partial void ProcessFireResponseContent(
object request,
string? speechifyVersion = default,
+ string? speechifyUserIdentity = default,
global::Speechify.AutoSDKRequestOptions? requestOptions = default,
global::System.Threading.CancellationToken cancellationToken = default)
{
@@ -108,6 +141,7 @@ partial void ProcessFireResponseContent(
httpClient: HttpClient,
triggerId: ref triggerId,
speechifyVersion: ref speechifyVersion,
+ speechifyUserIdentity: ref speechifyUserIdentity,
request: request);
@@ -170,6 +204,10 @@ partial void ProcessFireResponseContent(
{
__httpRequest.Headers.TryAddWithoutValidation("Speechify-Version", speechifyVersion.ToString());
}
+ if (speechifyUserIdentity != default)
+ {
+ __httpRequest.Headers.TryAddWithoutValidation("Speechify-User-Identity", speechifyUserIdentity.ToString());
+ }
var __httpRequestContentBody = global::System.Text.Json.JsonSerializer.Serialize(request, request.GetType(), JsonSerializerContext);
var __httpRequestContent = new global::System.Net.Http.StringContent(
@@ -190,6 +228,7 @@ partial void ProcessFireResponseContent(
httpRequestMessage: __httpRequest,
triggerId: triggerId!,
speechifyVersion: speechifyVersion,
+ speechifyUserIdentity: speechifyUserIdentity,
request: request);
return __httpRequest;
@@ -369,6 +408,43 @@ partial void ProcessFireResponseContent(
retryReason: global::System.String.Empty,
cancellationToken: __effectiveCancellationToken)).ConfigureAwait(false);
}
+ // The request was malformed or failed validation. The response body is the standard `Error` envelope; for validation failures `error.fields` enumerates the offending fields as a `path -> message` map (code = `validation_failed`).
+ if ((int)__response.StatusCode == 400)
+ {
+ string? __content_400 = null;
+ global::System.Exception? __exception_400 = null;
+ global::Speechify.Error? __value_400 = null;
+ try
+ {
+ if (__effectiveReadResponseAsString)
+ {
+ __content_400 = await __response.Content.ReadAsStringAsync(__effectiveCancellationToken).ConfigureAwait(false);
+ __value_400 = global::Speechify.Error.FromJson(__content_400, JsonSerializerContext);
+ }
+ else
+ {
+ __content_400 = await __response.Content.ReadAsStringAsync(__effectiveCancellationToken).ConfigureAwait(false);
+
+ __value_400 = global::Speechify.Error.FromJson(__content_400, JsonSerializerContext);
+ }
+ }
+ catch (global::System.Exception __ex)
+ {
+ __exception_400 = __ex;
+ }
+
+
+ throw global::Speechify.ApiException.Create(
+ statusCode: __response.StatusCode,
+ message: __content_400 ?? __response.ReasonPhrase ?? string.Empty,
+ innerException: __exception_400,
+ responseBody: __content_400,
+ responseObject: __value_400,
+ responseHeaders: global::System.Linq.Enumerable.ToDictionary(
+ __response.Headers,
+ h => h.Key,
+ h => h.Value));
+ }
// Authentication is missing or invalid. The request did not carry a recognised credential (console session token, API key, or worker JWT).
if ((int)__response.StatusCode == 401)
{
@@ -480,6 +556,43 @@ partial void ProcessFireResponseContent(
h => h.Key,
h => h.Value));
}
+ // Rate limit or concurrency limit exceeded. `error.code` distinguishes request-rate limiting (`rate_limited`) from concurrency exhaustion (`concurrency_limit_reached`). Carries `Retry-After` and the request-rate budget headers; a concurrency-exhaustion 429 also carries `RateLimit-Remaining-Calls: 0`.
+ if ((int)__response.StatusCode == 429)
+ {
+ string? __content_429 = null;
+ global::System.Exception? __exception_429 = null;
+ global::Speechify.Error? __value_429 = null;
+ try
+ {
+ if (__effectiveReadResponseAsString)
+ {
+ __content_429 = await __response.Content.ReadAsStringAsync(__effectiveCancellationToken).ConfigureAwait(false);
+ __value_429 = global::Speechify.Error.FromJson(__content_429, JsonSerializerContext);
+ }
+ else
+ {
+ __content_429 = await __response.Content.ReadAsStringAsync(__effectiveCancellationToken).ConfigureAwait(false);
+
+ __value_429 = global::Speechify.Error.FromJson(__content_429, JsonSerializerContext);
+ }
+ }
+ catch (global::System.Exception __ex)
+ {
+ __exception_429 = __ex;
+ }
+
+
+ throw global::Speechify.ApiException.Create(
+ statusCode: __response.StatusCode,
+ message: __content_429 ?? __response.ReasonPhrase ?? string.Empty,
+ innerException: __exception_429,
+ responseBody: __content_429,
+ responseObject: __value_429,
+ responseHeaders: global::System.Linq.Enumerable.ToDictionary(
+ __response.Headers,
+ h => h.Key,
+ h => h.Value));
+ }
if (__effectiveReadResponseAsString)
{
@@ -582,16 +695,31 @@ partial void ProcessFireResponseContent(
/// one-time fire token as `Authorization: Bearer <secret>`. The JSON body
/// becomes the started run's payload variable. Supply an `Idempotency-Key`
/// (or `Speechify-Delivery-Id`) header to make a redelivery replay the
- /// original run instead of starting a second one.
+ /// original run instead of starting a second one.
+ /// **Reacting to something that happened, for one person.** Set the
+ /// trigger's `webhook.user_identity_source` to `fire` and send
+ /// `Speechify-User-Identity` on each POST. The run then acts for that
+ /// person - their memory, and every tool call told who it is for - so one
+ /// trigger serves every person your connector watches and you hold no
+ /// trigger id per person. Use the plain form (`user_identity_source:
+ /// trigger`) when the work is the same whoever it is for: a nightly
+ /// reconciliation, an alert fan-out, a build that finished.
+ /// **Rate.** A trigger admits 60 fires a minute, and 12 a minute for any
+ /// one person; past either the endpoint answers `429` with `Retry-After`.
+ /// A fire starts a durable run against your credit balance, so a burst is
+ /// deferred rather than fanned out. Retry the refused delivery under the
+ /// same delivery id and it runs exactly once.
///
///
///
+ ///
/// Per-request overrides such as headers, query parameters, timeout, retries, and response buffering.
/// The token to cancel the operation with
///
public async global::System.Threading.Tasks.Task FireAsync(
string triggerId,
string? speechifyVersion = default,
+ string? speechifyUserIdentity = default,
global::Speechify.AutoSDKRequestOptions? requestOptions = default,
global::System.Threading.CancellationToken cancellationToken = default)
{
@@ -602,6 +730,7 @@ partial void ProcessFireResponseContent(
return await FireAsync(
triggerId: triggerId,
speechifyVersion: speechifyVersion,
+ speechifyUserIdentity: speechifyUserIdentity,
request: __request,
requestOptions: requestOptions,
cancellationToken: cancellationToken).ConfigureAwait(false);
diff --git a/src/libs/Speechify/openapi.yaml b/src/libs/Speechify/openapi.yaml
index 562610e..e1a80c5 100644
--- a/src/libs/Speechify/openapi.yaml
+++ b/src/libs/Speechify/openapi.yaml
@@ -1 +1 @@
-{"openapi":"3.1.0","info":{"title":"API Reference","version":"1.0.0"},"paths":{"/v1/audio/speech":{"post":{"operationId":"speech","summary":"Create Speech","description":"Synthesize speech audio from text or SSML. Returns the complete audio\nfile plus billing and speech-mark metadata in a single JSON response.\nFor low-latency playback or long-form text, use POST /v1/audio/stream.\nSet `output_format` for explicit sample-rate/bitrate control (e.g.\n`pcm_16000` or `ulaw_8000` for telephony).","tags":["audio"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Synthesized speech audio for the requested input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSpeechResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSpeechRequest"}}}}}},"/v1/audio/stream":{"post":{"operationId":"stream","summary":"Stream Speech","description":"Synthesize speech and stream the audio back as it is generated, for\nlow-latency playback. Set `output_format` in the body for explicit\ncodec/sample-rate/bitrate control (e.g. `pcm_16000` or `ulaw_8000` for\ntelephony), or fall back to the Accept header for the container; the\nresponse is raw audio bytes (HTTP chunked). For Base64-encoded audio\nwith speech-mark metadata in a single JSON response, use\nPOST /v1/audio/speech.","tags":["audio"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"Selects the audio container/codec for the streamed response when\n`output_format` is not set in the request body. The response\nContent-Type echoes this value, except `audio/pcm` returns\n`audio/L16` with rate and channels parameters (raw 16-bit linear\nPCM, 24 kHz mono, little-endian). For explicit sample-rate/bitrate\ncontrol (e.g. `pcm_16000`, `ulaw_8000`), set `output_format` in the\nbody instead; it takes precedence over this header.","required":false,"schema":{"$ref":"#/components/schemas/V1AudioStreamPostParametersAccept"}}],"responses":{"200":{"description":"Streamed audio. When `output_format` is set it selects the\ncodec/sample rate; otherwise the Accept header does. The\nContent-Type reflects the selected format: it matches the Accept\nheader, except raw PCM returns `audio/L16` (with rate and channels\nparameters) and u-law returns `audio/basic`.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetStreamRequest"}}}}}},"/v1/audio/stream/with-timestamps":{"post":{"operationId":"streamWithTimestamps","summary":"Stream Speech With Timestamps","description":"Synthesize speech and stream it back together with word-level speech\nmarks, for text highlighting, captions and audio-text synchronization\nwhile the audio is still arriving.\n\nThe response is a Server-Sent Events stream. Each `speech.chunk` event\ncarries a Base64-encoded run of audio, the speech marks that became\nfinal with it, or both - a chunk may carry only one of the two, and the\nlast chunk of a stream is often marks-only. A terminal `speech.done`\nevent ends the stream; there is no `[DONE]` sentinel. Ignore any event\ntype you do not recognize, so that new event types do not break your\nintegration.\n\nSpeech-mark times are absolute milliseconds from the start of the\nsynthesis, so concatenate the audio chunks into one stream and apply the\nmarks against that single timeline. Which chunk a mark arrives on is a\ndelivery detail and carries no meaning. Times stay correct for every\n`output_format`: changing the codec or sample rate does not change the\nduration.\n\nSpeech marks are produced by the streaming-native models. The default\n`simba-3.0` and `simba-3.2` both serve this route. The legacy\n`simba-english` and `simba-multilingual` models never could: on a\nworkspace pinned before API version `2026-09-21` they return 400\n`speech_marks_unsupported` here, and from that version on they return\n400 `model_retired` on every synthesis route. Both are switched off\nentirely on 2026-11-21.\nFor Base64-encoded audio and speech marks in one non-streamed JSON\nresponse, on any model, use POST /v1/audio/speech.","tags":["audio"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"Selects the audio container/codec carried inside the events when\n`output_format` is not set in the request body. The selected media\ntype is echoed on the `Speechify-Audio-Content-Type` response\nheader, since the response's own Content-Type is `text/event-stream`.","required":false,"schema":{"$ref":"#/components/schemas/V1AudioStreamWithTimestampsPostParametersAccept"}}],"responses":{"200":{"description":"A Server-Sent Events stream of `speech.chunk` events followed by one\nterminal `speech.done` event. A failure after the stream has started\nis delivered as a `speech.error` event carrying the standard error\nenvelope, because the status code is already committed.\n\nThe transport is `text/event-stream`: each event is an\n`event:`/`data:` pair whose `data` is one JSON payload matching the\nschema below. The payload's `type` field mirrors the `event:` name,\nso the stream is also parseable from `data:` lines alone. Ignore\nevent types you do not recognize.","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/SpeechStreamEvent"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetStreamRequest"}}}}}},"/v1/voices":{"get":{"operationId":"list","summary":"List Voices","description":"Lists the voices available to the caller - the shared voice\ncatalog plus the cloned voices they can reach, whichever member or\nservice-account key created them. A clone filed under a project is\nlisted only for a caller who can reach that project; a clone no\nproject filed is shared with the whole workspace and is listed for\neveryone in it. By default\nthe full catalogue is returned in one response. Pagination is\nopt-in: pass `limit` (and then `cursor` from the previous\nresponse) to page through the list while `has_more` is true. Max\npage size is 200. Narrow the list with the `type` and `locale`\nfilters.\n\nA page can come back with fewer than `limit` voices, and a short\npage - an empty one included - is not the end of the list. Keep\nfollowing `next_cursor` while `has_more` is true.","tags":["voices"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"type","in":"query","description":"Filter by voice type: `personal` (the workspace's cloned voices)\nor `shared` (the public catalogue). Omit to return both.","required":false,"schema":{"$ref":"#/components/schemas/V1VoicesGetParametersType"}},{"name":"locale","in":"query","description":"Filter to voices whose locale matches this BCP-47 language range,\nprefix-matched: `en` matches `en-US` and `en-GB`; `en-US` matches\nonly `en-US`. Case-insensitive. Omit to return all locales.","required":false,"schema":{"type":"string"}},{"name":"gender","in":"query","description":"Filter by voice gender. Omit to return all genders.","required":false,"schema":{"$ref":"#/components/schemas/V1VoicesGetParametersGender"}},{"name":"model","in":"query","description":"Filter to voices that support this model (as listed in each voice's\n`models[]`), e.g. `simba-3.2`. Omit to return voices for all models.","required":false,"schema":{"type":"string"}},{"name":"project_id","in":"query","description":"Filter cloned voices by workspace project: omit for every voice you\ncan reach, pass the literal `shared` for the clones no project\nfiled, or a `proj_...` id for the clones filed under that project.\nThe shared catalog carries no project and is returned either way.\n\nA clone is filed under a project when a project-pinned key created\nit. A clone with no project is shared with the whole workspace\nrather than sitting in a Default project, so the literal here is\n`shared`, never `default` - passing `default` is a 400. Returns 404\nproject_not_found for a malformed id and for any project outside\nyour reach: a project-pinned key reaches only its pinned project,\nand a member holding project grants reaches only the granted ones.\nThat 404 is the same in every case and does not reveal whether such\na project exists. `shared` is always inside your reach.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The voice catalogue (or a page of it when `limit` is set).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListVoicesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Voice","description":"Create a cloned voice for the workspace from a 10-30 second audio sample, with verified consent from the speaker.\n\nCloning requires proof that the speaker agreed to it. Create a consent challenge with `POST /v1/voices/consent-challenges`, show the returned `phrase` to the speaker, record them reading it aloud, and send that recording here as `consent_recording` together with the challenge's `consent_challenge_id`. Speechify transcribes the recording, checks it against the phrase it issued, checks that its speaker is the speaker in your `sample`, and keeps it as the consent record for the voice. The person consenting therefore has to be the person being cloned. A challenge is single use and short-lived, so record and submit in one sitting.\n\nThe clone belongs to the workspace rather than the member who created it, and access follows the caller's workspace role and API-key scopes exactly as for any other voice: voices scopes to list it, audio scopes to synthesize with it, and the content-management permission plus a write scope on the key to delete it. Cloned voices are usable self-serve on `simba-3.0` (and, on a workspace pinned before API version `2026-09-21`, on the retired `simba-english` and `simba-multilingual` until they are switched off on 2026-11-21). `simba-3.2` also serves cloned voices.\n\nCallers pinned before `Speechify-Version: 2026-09-13` use the previous flow instead: no challenge, and a `consent` form field carrying the speaker's name and email as a JSON string. That flow is deprecated and will be removed after a sunset window announced in the changelog.","tags":["voices"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"A created voice","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetVoice"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name of the personal voice"},"locale":{"type":"string","default":"en-US","description":"Native language (locale) of the personal voice (e.g. en-US, es-ES, etc.)"},"gender":{"$ref":"#/components/schemas/V1VoicesPostRequestBodyContentMultipartFormDataSchemaGender","description":"Gender marker for the personal voice\nmale GenderMale\nfemale GenderFemale\nnot_specified GenderNotSpecified"},"sample":{"type":"string","format":"binary","description":"Audio sample of the voice to clone, 10-30 seconds of clean speech."},"avatar":{"type":"string","format":"binary","description":"Avatar image file"},"consent_challenge_id":{"type":"string","description":"The `id` of the consent challenge this create consumes, from\n`POST /v1/voices/consent-challenges`. Single use: once a\ncreate has consumed it, whether or not that create\nsucceeded, it cannot be used again."},"consent_recording":{"type":"string","format":"binary","description":"Recording of the speaker reading the challenge's `phrase`\naloud. This is the consent record for the voice, not a\nsecond voice sample: it must be the same person as in\n`sample`, and it is retained as evidence. 5-30 seconds, at\nmost 25 MB, in any common audio container."}},"required":["name","gender","sample","consent_challenge_id","consent_recording"]}}}}}},"/v1/voices/consent-challenges":{"post":{"operationId":"create","summary":"Create Consent Challenge","description":"Start the consent check for a voice clone.\n\nReturns a `phrase` for the speaker to read aloud and an `id` that identifies this challenge. Show the phrase to the speaker exactly as returned, record them reading it, then send the recording and the `id` to `POST /v1/voices`, which verifies the recording against the phrase and against the voice sample being cloned, then keeps it as the consent record.\n\nA challenge is single use, is bound to the workspace that created it, and expires at `expires_at` - it is proof that a speaker was in front of a microphone just now, so create it when you are ready to record, not at the start of your flow. If it expires, create another one and record again.\n\nChallenge creation is rate limited per workspace at a few dozen per hour, far more tightly than the rest of the voice surface, because each one precedes a person recording themselves - mint it when your speaker is ready, not speculatively. Read the live ceiling off `RateLimit-*` rather than hard-coding it. **On a `429`, always honour `Retry-After` rather than a fixed backoff of your own**: the wait is measured in minutes and can run to most of an hour. `RateLimit-*` are omitted rather than reporting a bucket that is not the one refusing.","tags":["consentChallenges"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created consent challenge.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentChallenge"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConsentChallengeRequest"}}}}}},"/v1/voices/{voice_id}":{"get":{"operationId":"get","summary":"Get Voice","description":"Fetch a single voice by id - a shared catalogue voice or one of\nthe workspace's cloned voices. A cloned voice that belongs to\nanother workspace returns 404, identical to an unknown id, so\nvoice inventory is never enumerable across tenants.","tags":["voices"],"parameters":[{"name":"voice_id","in":"path","description":"The ID of the voice to fetch","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The voice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetVoice"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete","summary":"Delete Voice","description":"Delete one of the workspace's cloned voices. Requires the\n`content.manage` permission (owner, admin, or member); a\nservice-account key is authorized by its scopes instead.","tags":["voices"],"parameters":[{"name":"voice_id","in":"path","description":"The ID of the voice to delete","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Voice deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/voices/{voice_id}/sample":{"get":{"operationId":"downloadSample","summary":"Download Voice Sample","description":"Download a personal (cloned) voice sample","tags":["voices"],"parameters":[{"name":"voice_id","in":"path","description":"The ID of the voice to download sample for","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Voice sample audio file","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audio/models":{"get":{"operationId":"list","summary":"List Models","description":"List the text-to-speech models available for synthesis. Drive a model\npicker from this response, then pass a model `id` as the `model`\nparameter to POST /v1/audio/speech or /v1/audio/stream. The response\nmarks the default model (used when a request omits `model`), the\nroutes each model may be passed to, and which voices it accepts.\nMulti-speaker models arrive in a separate `dialogue_models` array\nbecause they are valid only on POST /v1/audio/dialogue. Returns\nthe full set in a single response: the model catalog is static\nplatform reference data, so it is intentionally not paginated.","tags":["models"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The available text-to-speech models.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audio/watermark/detect":{"post":{"operationId":"detect","summary":"Detect Watermark","description":"Check whether a clip carries the watermark Speechify seals into audio it\ngenerates. Upload the audio as `audio`; nothing about it is stored, and\nno voice is read or written.\n\nRead the answer carefully in one direction. A `watermarked: true` is\npositive evidence that the audio came from Speechify synthesis. A\n`watermarked: false` is NOT proof that it did not: only models\nredeployed since the watermark shipped mark their output, the detector\nneeds at least three seconds of clear speech to judge, and re-encoding\nor changing the speed of a clip degrades the mark. Treat a negative as\nthe absence of evidence rather than as evidence of absence.\n\nChecks are rate-limited well below the synthesis budget: this is a\nforensic question, not a data-plane call.","tags":["watermark"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The clip was checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatermarkDetectionResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"audio":{"type":"string","format":"binary","description":"The clip to check, at most 25MB. Give the detector at least\nthree seconds of clear speech; below that its confidence is\nnot worth acting on, and below half a second it always\nreports zero."}},"required":["audio"]}}}}}},"/v1/audio/watermark/verify":{"post":{"operationId":"verify","summary":"Verify Watermark","description":"The public AI detection tool. Ask whether a clip carries the watermark\nSpeechify seals into audio it generates, with no account, no API key and\nno credential of any kind.\n\n`verify` answers; `detect` measures. This route returns a bare yes or no,\nthe way verifying a signature does. Its sibling\n`POST /v1/audio/watermark/detect` takes an API key and returns the\ndetector's confidence alongside the verdict.\n\nThis is the programmatic half of the tool published at\n, and it exists so the tool can be invoked\nwithout visiting our website, as California's AI Transparency Act\n(BPC 22757.2) requires. Nothing about the clip is stored, and nothing\nidentifying about you is collected or retained.\n\nThe answer is a bare verdict. `watermarked: true` is positive evidence\nthat the audio came from Speechify synthesis. `watermarked: false` is\nNOT proof that it did not: only models redeployed since the watermark\nshipped mark their output, the detector needs at least three seconds of\nclear speech to judge, and re-encoding or changing the speed of a clip\ndegrades the mark. Treat a negative as the absence of evidence rather\nthan as evidence of absence.\n\nBecause the tool takes no credential, it is rate-limited per client\naddress and shares a platform-wide budget: expect a 429 under sustained\nautomated use, and retry after the interval the response advertises.\nUse `POST /v1/audio/watermark/detect` with an API key for the detector's\nconfidence score and a per-workspace allowance of its own.","tags":["watermark"],"parameters":[{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The clip was checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatermarkVerificationResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"audio":{"type":"string","format":"binary","description":"The clip to check, at most 25MB. Give the detector at least\nthree seconds of clear speech; below that its answer is not\nworth acting on."}},"required":["audio"]}}}}}},"/v1/agents":{"get":{"operationId":"list","summary":"List Agents","description":"List voice agents owned by the caller. Cursor-paginated: omit\n`cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200).","tags":["agent"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of voice agents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Agent","description":"Create a voice agent.\n\nThe agent lands in the project named by `project_id`, or in the\nimplicit Default project when none is given. Each project holds at\nmost a fixed number of live agents; a create past that answers\n`409 agent_limit_reached` naming the ceiling, and deleting an agent\nin that project - or creating this one in another - frees a slot.\n","tags":["agent"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentRequest"}}}}}},"/v1/agents/voices":{"get":{"operationId":"listAgentVoices","summary":"List Agent Voices","description":"List the voice catalogue available for voice agents: the curated\nshared catalogue plus your workspace's own cloned voices, each\nmarked by `type` (`shared` or `personal`). The same slug set is\naccepted by POST/PATCH /v1/agents, so any voice listed here can be\nassigned to an agent. Cloned voices are workspace-owned only - a\npersonal voice scoped to an individual is not selectable on an\nagent, which is a workspace-shared resource. The JSON layout\nintentionally mirrors the TTS `/v1/voices` shape so a single voice\npicker can consume both endpoints.\n\nCursor-paginated: pass `cursor` + `limit` and walk pages while\n`has_more` is true. From API version `2026-08-07` an omitted `limit`\nreturns the first page (default 50, max 200); a caller pinned before\nthat date keeps the historical whole-catalogue response until it opts\nin. The shared catalogue is served first, then your cloned voices.\n","tags":["agent"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The curated agent voice catalogue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentVoicesResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tts-models":{"get":{"operationId":"listTTSModels","summary":"List Agent TTS Models","description":"List the Simba text-to-speech models a voice agent can be pinned to\nwith `tts.model`.\n\nThe response is the whole catalog and is the same for every\nworkspace - which model serves an agent is a routing decision, not\nan entitlement, so nothing here is plan-gated. Whether a *particular*\nagent may select an entry depends on two facts this response\npublishes rather than resolving for you, because both change without\nthis list changing:\n\n- `english_only` - the model has no multilingual deployment, so an\n agent that declares `additional_languages` (or whose `language` is\n not English) cannot select it.\n- `requires_voice_curation` - a voice must be registered for the\n model before it can be selected with that voice. The voice's\n `models` array on GET /v1/agents/voices is the per-voice answer,\n and it changes as voices are curated, with no release here. It is\n false for a model offered across the catalog, which every Speechify\n voice can then select.\n\nDrive a model picker from this and the voice catalog together and it\ncan only offer combinations POST/PATCH /v1/agents will accept.\nReturns the full set in one response: bounded by the platform's\ncurated model catalog, so it is intentionally not paginated.\n","tags":["agent"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The TTS models selectable for voice agents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTTSModelsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}":{"delete":{"operationId":"delete","summary":"Delete Agent","description":"Delete an agent, and with it everything that belonged only to that\nagent.\n\n**Goes with the agent**, immediately unreadable through every\nendpoint: its runs and their step journals and approvals, its\nconversations and the transcripts, messages and recordings inside\nthem, what it learned about the people it spoke to, its triggers,\nshare links, channel bindings, team memberships and the batch\ncampaigns it dialled. A run that has not settled is asked to stop,\nincluding one parked waiting on a person or on a delegated child. These are removed for good 30 days\nlater; until then the delete can still be walked back by support.\n\n**Survives the agent**, because it belongs to the workspace rather\nthan to this agent: attached tools, knowledge bases, stores and\nskills (only the attachment is dropped), the agent's tests, phone\nnumbers, and the teams it belonged to.\n\n**Kept regardless**, because it is accounting rather than content:\nthe usage the workspace was billed for.\n\nRefused with `409 agent_in_use` while a live phone number still\nroutes to the agent, including the agent a webhook binding\nprovisioned for its number; `used_by` names the numbers. Unbind them\n(`DELETE /v1/agents/{agent_id}/phone-numbers/{phone_number_id}`,\nwhich also clears a webhook binding) and the delete proceeds.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Agent deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A live phone number still routes to the agent. `used_by` names\nthem so the caller can unbind first.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentInUseError"}}}}}},"get":{"operationId":"get","summary":"Get Agent","description":"Retrieve a voice agent by ID.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Agent","description":"Update a voice agent. Only fields present on the request body are changed.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentRequest"}}}}}},"/v1/agents/{agent_id}/widget-config":{"get":{"operationId":"getWidgetConfig","summary":"Get Agent Widget Config","description":"Return the embed-widget appearance config for an agent. Works\nunauthenticated for public agents; the body is cosmetic only.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's widget configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WidgetConfig"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/flow/schema":{"get":{"operationId":"getSchema","summary":"Get Flow Graph Schema","description":"Return the JSON Schema describing the flow graph node taxonomy.\nUnauthenticated; flow editors fetch it to validate graphs client-side.\n","tags":["flow"],"parameters":[{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A JSON Schema document for the flow graph.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/agent_flow_getSchema_Response_200"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/flow":{"get":{"operationId":"getFlow","summary":"Get Agent Flow","description":"Return the agent's flow graph: the current draft (if any), the\nactive published graph (if any), and the version history.\n","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The draft graph, active graph, and version history.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetFlowResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateFlow","summary":"Update Agent Flow Draft","description":"Replace the agent's draft flow graph. The graph is validated\nbefore it is stored; publish it separately to make it active.\n","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The stored draft graph.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowGraph"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutFlowRequest"}}}}}},"/v1/agents/{agent_id}/flow/publish":{"post":{"operationId":"publish","summary":"Publish Agent Flow","description":"Publish the agent's draft graph as a new active flow version.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The newly published flow version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowVersion"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The behavioral-eval publish gate refused the publish\n(`agent_publish_gate_failed`): the agent's configured suite did not\npass. `error.details` carries the verdict (per-case, per-criterion).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The publish checks could not run (`agent_publish_gate_unavailable`):\nthe eval worker, the judge or the probe corpus errored, so nothing\nabout the agent was judged. Not a verdict on the configuration; retry\nafter `Retry-After`. `error.details` lists the cases that could not\nrun.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishFlowRequest"}}}}}},"/v1/agents/{agent_id}/flow/rollback":{"post":{"operationId":"rollback","summary":"Roll Back Agent Flow","description":"Publish a prior flow version as the active graph.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The flow version that is now active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowVersion"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The behavioral-eval publish gate refused the rollback\n(`agent_publish_gate_failed`): the target version did not pass the\nagent's configured suite. `error.details` carries the verdict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The gate's checks could not run on the rollback target\n(`agent_publish_gate_unavailable`): the eval worker, the judge or\nthe probe corpus errored, so nothing about the version was judged.\nNot a verdict on it; retry after `Retry-After`. `error.details`\nlists the cases that could not run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RollbackFlowRequest"}}}}}},"/v1/agents/{agent_id}/flow/deactivate":{"post":{"operationId":"deactivate","summary":"Deactivate Agent Flow","description":"Deactivate the agent's published flow so the agent runs the synthesized default flow.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Flow deactivated.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/flow/draft":{"delete":{"operationId":"discardDraft","summary":"Discard Agent Flow Draft","description":"Discard the agent's unpublished draft flow graph.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Draft discarded.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/flow/versions":{"get":{"operationId":"listVersions","summary":"List Agent Flow Versions","description":"List every published flow version for the agent, newest first.\nCursor-paginated: omit `cursor` for the first page; walk pages\nwhile `has_more` is true (default page size 50, max 200).","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's flow version history.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListFlowVersionsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/flow/versions/{version_id}":{"get":{"operationId":"getVersion","summary":"Get Agent Flow Version","description":"Return the full flow graph for a specific published version.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"version_id","in":"path","description":"Prefixed flow version id (`fver_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested flow version's graph.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowGraph"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/flow/templates":{"get":{"operationId":"listTemplates","summary":"List Flow Templates","description":"List the reusable flow templates available to the workspace.\nCursor-paginated: omit `cursor` for the first page; walk pages\nwhile `has_more` is true (default page size 50, max 200).","tags":["flow"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The available flow templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListFlowTemplatesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createTemplate","summary":"Create Flow Template","description":"Create a reusable flow template from a graph.","tags":["flow"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created flow template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowTemplate"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFlowTemplateRequest"}}}}}},"/v1/agents/flow/templates/{flow_template_id}":{"get":{"operationId":"getTemplate","summary":"Get Flow Template","description":"Retrieve a flow template by id.","tags":["flow"],"parameters":[{"name":"flow_template_id","in":"path","description":"Flow template id (prefixed external id, `tmpl_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested flow template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowTemplate"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateTemplate","summary":"Update Flow Template","description":"Replace a flow template. The whole template is replaced, not patched field-by-field.","tags":["flow"],"parameters":[{"name":"flow_template_id","in":"path","description":"Flow template id (prefixed external id, `tmpl_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated flow template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowTemplate"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFlowTemplateRequest"}}}}},"delete":{"operationId":"deleteTemplate","summary":"Delete Flow Template","description":"Delete a flow template.","tags":["flow"],"parameters":[{"name":"flow_template_id","in":"path","description":"Flow template id (prefixed external id, `tmpl_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Template deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/flow/templates/{flow_template_id}/clone":{"post":{"operationId":"cloneTemplate","summary":"Clone Flow Template","description":"Clone a flow template onto an agent as a new draft graph.","tags":["flow"],"parameters":[{"name":"flow_template_id","in":"path","description":"Flow template id (prefixed external id, `tmpl_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's new draft graph.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowGraph"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloneFlowTemplateRequest"}}}}}},"/v1/agents/{agent_id}/evaluation-config":{"get":{"operationId":"getEvaluationConfig","summary":"Get Evaluation Config","description":"Retrieve the agent's post-call evaluation criteria + data-collection config.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluation config for the agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationConfig"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateEvaluationConfig","summary":"Update Evaluation Config","description":"Replace the agent's evaluation criteria + data-collection fields.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated evaluation config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationConfig"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEvaluationConfigRequest"}}}}}},"/v1/agents/{agent_id}/variables":{"get":{"operationId":"getDynamicVariables","summary":"Get Dynamic Variables","description":"Retrieve the agent's customer-scope dynamic variables and the read-only\ncatalogue of reserved `system__*` keys. The system variables list is\nprovided so editor UIs can render the reference list without maintaining\na client-side copy of the catalogue.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's variable catalogue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDynamicVariablesResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateDynamicVariables","summary":"Update Dynamic Variables","description":"Replace the agent's customer-scope dynamic variable definitions.\nThe supplied list overwrites the stored list wholesale (same\nsemantics as `updateEvaluationConfig`). Pass an empty array to\nclear all variables. Up to 20 variables per agent. Keys must\nmatch `[a-zA-Z0-9_]+` and must not start with the reserved\n`system__` prefix.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated variable catalogue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDynamicVariablesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDynamicVariablesRequest"}}}}}},"/v1/agents/{agent_id}/conversations":{"post":{"operationId":"createConversation","summary":"Create Conversation","description":"Start a new voice conversation with the agent. Returns a realtime\nvoice session + short-lived client token so the caller can\nconnect the audio pipeline directly. The agent is dispatched\nserver-side; no additional client action required.\n\nPass `dynamic_variables` to supply per-session values that override\nthe agent's stored variable defaults for this one conversation.\nKeys in the `system__` namespace are rejected at this boundary.\n\nPass `user_identity` as the stable caller key your application uses\nfor memory and caller correlation. It is kept separate from the\nopaque, per-session realtime participant identity.\n\n`overrides.voice_id` replaces the configured voice for the entire\nconversation, including every configured language the voice can\nserve. The request is rejected when the voice is unavailable or\ncannot cover the agent's configured language set; the rejection\nnames `overrides.voice_id` in the error envelope's `fields` map.\nThe override belongs to this conversation only: if the call is\ntransferred to another agent mid-conversation, that agent speaks\nwith its own configured voice.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created conversation with its realtime session token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationRequest"}}}}}},"/v1/agents/{agent_id}/sessions":{"post":{"operationId":"createSession","summary":"Create Session","description":"Mint a realtime voice session for the given agent. Widget-friendly\ncounterpart to `createConversation` \u2014 same response shape, dual\nauthentication:\n\n* **Authenticated (Bearer)**: works for any agent the caller\n owns. Typical server-to-server flow where the embedding\n site's backend mints a token and hands it to the browser so\n the API key never reaches the client.\n* **Unauthenticated**: works only when `agent.is_public = true`\n AND the request's `Origin` header matches `agent.allowed_origins`\n (or that list is empty). When `agent.hostname_allowlist` is\n non-empty, the `Origin` hostname must additionally be a\n member of that list. Used directly by the\n `` web component.\n\nResponds with the same `CreateConversationResponse` as\n`createConversation`.\n\n`Idempotency-Key` replay is scoped to the authenticated caller, so it\napplies to Bearer requests only: an unauthenticated mint has no scope\nto replay under, and retrying one mints a fresh session.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created session with its realtime token + URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSessionRequest"}}}}}},"/v1/agents/conversations":{"get":{"operationId":"list","summary":"List Conversations","description":"List conversations owned by the caller, ordered by most recent.\nCursor-paginated: omit `cursor` to fetch the first page; pass the\nprevious response's `next_cursor` back to fetch the next page.\nWalk pages while `has_more` is true.\n\n`project_id` matches the project the call was DISPATCHED under, which\nis frozen when the conversation starts. Moving an agent to another\nproject therefore leaves its past calls attributed where they ran, the\nsame way per-project usage reports them.","tags":["conversations"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"agent_id","in":"query","description":"Filter to conversations for this agent.","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter by conversation status.","required":false,"schema":{"$ref":"#/components/schemas/ConversationStatus"}},{"name":"transport","in":"query","description":"Filter by transport.","required":false,"schema":{"$ref":"#/components/schemas/ConversationTransport"}},{"name":"caller_identity","in":"query","description":"Filter by caller identity.","required":false,"schema":{"type":"string"}},{"name":"contact_id","in":"query","description":"Narrow to one person's conversations, whichever agent or channel\nreached them. Prefixed `contact_...` id.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Free-text search across conversation content.","required":false,"schema":{"type":"string"}},{"name":"started_after","in":"query","description":"Only conversations started at or after this RFC 3339 timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"started_before","in":"query","description":"Only conversations started at or before this RFC 3339 timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"duration_min_ms","in":"query","description":"Minimum conversation duration in milliseconds.","required":false,"schema":{"type":"integer"}},{"name":"duration_max_ms","in":"query","description":"Maximum conversation duration in milliseconds.","required":false,"schema":{"type":"integer"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of conversations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConversationsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}":{"get":{"operationId":"get","summary":"Get Conversation","description":"Retrieve a conversation by ID.","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/recent-callees":{"get":{"operationId":"recent_callees","summary":"List recent callees","description":"Distinct phone numbers the caller's workspace has dialled on\noutbound calls, ordered by most recent. Feeds the batch-calls\ncomposer's \"Suggested from history\" surface. Cursor-paginated:\nomit `cursor` to fetch the first page. Default page size is 50\nand max is 200. Walk pages while `has_more` is true.\n","tags":["conversations"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recent callees for the caller's workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListRecentCalleesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/stats":{"get":{"operationId":"stats","summary":"Conversation stats","description":"Aggregated counts and averages over the caller's conversations, scoped\nby the same filters as the list endpoint, `project_id` included.","tags":["conversations"],"parameters":[{"name":"agent_id","in":"query","description":"Filter to conversations for this agent.","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter by conversation status.","required":false,"schema":{"$ref":"#/components/schemas/ConversationStatus"}},{"name":"transport","in":"query","description":"Filter by transport.","required":false,"schema":{"$ref":"#/components/schemas/ConversationTransport"}},{"name":"caller_identity","in":"query","description":"Filter by caller identity.","required":false,"schema":{"type":"string"}},{"name":"contact_id","in":"query","description":"Narrow to one person's conversations, whichever agent or channel\nreached them. Prefixed `contact_...` id.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Free-text search across conversation content.","required":false,"schema":{"type":"string"}},{"name":"started_after","in":"query","description":"Only conversations started at or after this RFC 3339 timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"started_before","in":"query","description":"Only conversations started at or before this RFC 3339 timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"duration_min_ms","in":"query","description":"Minimum conversation duration in milliseconds.","required":false,"schema":{"type":"integer"}},{"name":"duration_max_ms","in":"query","description":"Maximum conversation duration in milliseconds.","required":false,"schema":{"type":"integer"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Stats for the matched conversations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationStats"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/messages":{"get":{"operationId":"listMessages","summary":"List Messages","description":"Retrieve the transcript for a conversation in started_at order\n(oldest first). Cursor-paginated: omit `cursor` to fetch the\nfirst page. Default page size is 50 and max is 200. Walk pages\nwhile `has_more` is true.","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The messages for the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMessagesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/recording":{"get":{"operationId":"streamRecording","summary":"Stream Recording","description":"Proxy the GCS-stored audio recording for a conversation through\nthe Cloud Run service identity. Returns OGG/Opus bytes (LiveKit\nroom-composite egress default). The response is streamed so a\nlong recording does not buffer in memory; `` consumers\ncan seek directly. Only present when the agent had\n`save_audio_recording` enabled at session start.\n","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The recorded audio.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/evaluations":{"get":{"operationId":"listEvaluations","summary":"List Evaluations","description":"Retrieve post-call evaluation results for a conversation.\nCursor-paginated: omit `cursor` for the first page; walk pages while\n`has_more` is true (default page size 50, max 200).","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluations for the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEvaluationsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/memories":{"get":{"operationId":"listMemories","summary":"List Conversation Memories","description":"List memories extracted from a specific conversation.","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Memories written during this conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMemoriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/retrieval-log":{"get":{"operationId":"listRetrievalLog","summary":"List Retrieval Log","description":"Per-conversation retrieval log, newest first \u2014 one row per\n`search_knowledge` invocation made during the call. Each entry\nrecords the query, ranked chunks (denormalised so deletions\ndon't render history unreadable), `limit`, and hit count.\nPowers the Retrieval panel on the conversation detail view.\nCursor-paginated: omit `cursor` to fetch the first page.\nDefault page size is 50 and max is 200. Walk pages while\n`has_more` is true.\n","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The retrieval log entries for the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListRetrievalLogsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/webhook-deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"List Webhook Deliveries","description":"List post-call webhook delivery attempts for a conversation,\nnewest first. Rows appear once the call ends and the post-call\nwebhook has been dispatched to the agent's configured URL. One\nrow per\n`(conversation, webhook-url)`, updated in place across retries.\nEach row includes the exact request payload and signed headers\nSpeechify sent (`request_body`, `request_headers`) and the\nresponse your server returned (`last_status_code`,\n`last_response_body`, `last_response_headers`), so you can verify\nwhat was delivered and debug a failing endpoint.\nCursor-paginated: omit `cursor` to fetch the first page.\nDefault page size is 50 and max is 200. Walk pages while\n`has_more` is true.\n","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The post-call webhook deliveries for the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhookDeliveriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/end":{"post":{"operationId":"forceEnd","summary":"Force-end conversation","description":"Force-terminate an ongoing conversation. Idempotent: a\nconversation that has already ended returns 204 the same as a\nsuccessful first-time termination. Requires an `owner` or `admin`\nof the workspace \u2014 the same gate as take-over.\n","tags":["admin"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Conversation ended.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/shadow-token":{"post":{"operationId":"shadowToken","summary":"Mint shadow-call token","description":"Mint a listen-only realtime access token so an authorized observer\ncan join an ongoing voice-agent conversation as a hidden\nparticipant. Any `member`, `admin`, or `owner` of the workspace\nthe conversation belongs to may listen in \u2014 it is read-only\nobservation. The token cannot publish audio or data; the observer\nis invisible to the caller and the agent. Speechify support\nengineers reach this endpoint the same way as any other observer\n\u2014 by being granted a role on the customer's workspace (typically\nunder an NDA-backed support arrangement).\n","tags":["admin"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Shadow-call connection details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShadowConversationResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/publish":{"post":{"operationId":"publish","summary":"Publish Agent For Runs","description":"Publish an agent's current configuration for durable runs.\n\nThe configuration a run executes is live as soon as it is written, so\nthis promotes nothing. What a publish does is submit the configuration\nto the workspace's publish gates and record the decision against a\nfingerprint of that configuration. On a gated workspace a run will only\nstart against a configuration that carries a passing (or overridden)\ndecision, so this is the step that makes an agent runnable after it is\ncreated or edited.\n\nAny agent publishes here. The conversations it answers are versioned\nseparately, by promoting a flow version via\n`/v1/agents/{agent_id}/flow/publish`.\n\nOn an ungated workspace this succeeds and returns no `gate` object.\n\nThis endpoint is in beta: the gate runs on workspaces granted\n`publish_eval_gate` **or** `red_team_publish_gate`, and the second is\nenabled automatically for every workspace granted `durable_runs_access`\n- so if you can start durable runs, you are gated and this call is a\nprerequisite, not an option. The runs a publish authorises need\n`durable_runs_access` too (the run endpoints answer\n`402 durable_runs_not_in_plan` without it).","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"The published configuration, with the gate outcome when gated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishAgentResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The publish gate refused the publish (`agent_publish_gate_failed`):\nthe configuration did not pass its behavioral and/or adversarial\nred-team checks. `error.details` carries the verdict (per-case,\nper-criterion). Correct the agent and publish again, or publish with a\nrecorded `gate_override_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The publish checks could not run (`agent_publish_gate_unavailable`):\nthe eval worker, the judge or the probe corpus errored, so nothing\nabout the agent was judged. Not a verdict on the configuration; retry\nafter `Retry-After`. `error.details` lists the probes that could not\nrun.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishAgentRequest"}}}}}},"/v1/agents/{agent_id}/publish-gate/latest":{"get":{"operationId":"latest","summary":"Get Latest Publish Gate Run","description":"Return the agent's most recent publish-gate decision, or `null` when it has\nnever been gated. This is the durable answer to \"are this agent's checks\npassing, and when were they last run\", without republishing it.\n\nThis endpoint is in beta: a decision exists on workspaces granted\n`publish_eval_gate` or `red_team_publish_gate` - the second is enabled\nautomatically alongside `durable_runs_access`, so any workspace that can\nstart durable runs records decisions here. Every other workspace reads\n`null`.","tags":["publishGate"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The latest gate decision, or null if the agent was never gated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LatestPublishGateRunResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs":{"post":{"operationId":"createRun","summary":"Create Agent Run","description":"Enqueue a **durable async run**: hand the agent an instruction and get a\nrun handle back immediately (`202`, status `queued`). The run executes on\nits own - poll it with `getRun` or receive a completion webhook - and it\nsurvives a deploy (it is backed by a durable job, not a request socket or\na short-lived store). This is the managed-agent-platform primitive: start\nwork, walk away, come back for the result.\n\nIdempotent via `Idempotency-Key`: a retry replays the first run instead of\nstarting a duplicate (the `run_id` is the idempotency handle).\n\n## Publish the agent first\n\n**Every workspace granted `durable_runs_access` is publish-gated**, so\nthis is the first thing a new integration hits. Call\n`POST /v1/agents/{agent_id}/publish` before the first run, and again\nafter any change to the agent's configuration: the gate is keyed to a\nfingerprint of that configuration, so an edited agent stops running\nuntil it is republished. Until it passes, this endpoint returns\n`422 agent_publish_gate_required`.\n\nRefused with `422 tool_transport_unsupported` when one of the agent's\nattached MCP tools uses the legacy `sse` transport, which durable runs\ncannot execute; the message names the tool. Switch it to\n`http_streamable` or detach it, then retry.\n\nReturns `429 concurrency_limit_reached` when the workspace already has\n200 runs queued or running, and the same code when the run's PROJECT is\nat its own `max_concurrent_runs` ceiling - only the message says which\nbit, so one retry path handles both. Runs execute on a shared queue, so\nthe workspace ceiling is what keeps one workspace's backlog from\ndelaying everyone else's next run; it is not a plan limit. The project\nceiling narrows it further, which is how an application keeps one of its\ncustomers from occupying every slot the workspace has. `Retry-After` is\na hint at the scale runs take, not a promise - what actually frees a\nslot is one of your own runs ending, so follow the ones you have with\nthe event stream and start the next when one does.\n\n## The project a run bills to\n\nA run is attributed to its AGENT's project, captured at creation and\nfrozen there, so moving the agent later never moves a finished run's\ncost. That project's money gates apply to the run exactly as they apply\nto a call in it: `402 project_spend_limit_exceeded` once its\n`monthly_budget` is reached, and `409 project_archived` while it is\narchived. Both can fire for a workspace-wide key, because the project\ncharged is the agent's rather than the key's pin.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nKeys inside `variables` and `metadata`, and the contents of\n`output_schema`, are your own data rather than field names, and are\nnever refused.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The run was accepted and queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentRunRequest"}}}}},"get":{"operationId":"listRuns","summary":"List Agent Runs","description":"List an agent's runs, newest first.\n\nThe filters are exactly the ones listed here. A query parameter this\nlist does not accept is a 400 naming it, never silently ignored.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Narrow the list to one run status. Omit for every status. A value\noutside the enum is rejected with a 400 naming `status`, rather than\nquietly widening to everything - a filter that ignores a typo is how a\nsurface reports \"nothing waiting\" while a run waits.\n","required":false,"schema":{"$ref":"#/components/schemas/V1AgentsAgentIdRunsGetParametersStatus"}},{"name":"user_identity","in":"query","description":"Narrow the list to runs acting for one person - the `user_identity`\nthey were started with, matched exactly. Omit for every run. A\nscheduled run started by a trigger carrying `user_identity` is found\nhere too.\n","required":false,"schema":{"type":"string","maxLength":256}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's runs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/runs":{"get":{"operationId":"list","summary":"List Runs","description":"List runs across the whole workspace, newest first, optionally narrowed\nto one `status`.\n\nUse this rather than listing per agent when you want to know what is\nhappening overall, and above all what is waiting on a person: pass\n`?status=requires_action`. Listing per agent cannot answer that\nquestion completely, because a run delegated to a team member executes\nunder that member's agent - so an approval that blocks the team's work\nappears under an agent you would not think to open.\n\nRespects `?project_id` like every other workspace-wide list, and\n`?agent_id` narrows it to one agent's runs.\n\nThe filters are exactly the ones listed here. A query parameter this\nlist does not accept is a 400 naming it, never silently ignored: a\nfilter that is accepted and dropped returns the wrong runs with a 200.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"status","in":"query","description":"Narrow the list to one run status. Omit for every status. A value\noutside the enum is rejected with a 400 naming `status`, rather than\nquietly widening to everything - a filter that ignores a typo is how a\nsurface reports \"nothing waiting\" while a run waits.\n","required":false,"schema":{"$ref":"#/components/schemas/V1AgentsRunsGetParametersStatus"}},{"name":"user_identity","in":"query","description":"Narrow the list to runs acting for one person - the `user_identity`\nthey were started with, matched exactly. Omit for every run. A\nscheduled run started by a trigger carrying `user_identity` is found\nhere too.\n","required":false,"schema":{"type":"string","maxLength":256}},{"name":"agent_id","in":"query","description":"Narrow the list to one agent's runs (prefixed external id,\n`agent_...`), delegated children of that agent included. Omit for\nevery agent. A value that is not an agent id is rejected with a 400\nnaming `agent_id`; an agent id your workspace does not own matches\nnothing.\n","required":false,"schema":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The workspace's runs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/runs/{run_id}":{"get":{"operationId":"get","summary":"Get Run","description":"Get a run by its id alone, when that is all you hold: the run id\narrives on its own in the `Speechify-Idempotency-Key` an MCP\nconnector receives (`:`), in the `Idempotency-Key` a\nwebhook tool receives, and on every `run.*` webhook event. The\nresponse is the same run object `getRun` returns, `agent_id`\nincluded, so one call resolves the agent and the run together.\n\nScoped to your workspace and to the projects your key can reach; a\nrun outside either is a 404.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}":{"get":{"operationId":"getRun","summary":"Get Agent Run","description":"Poll a run by its handle. Branch on `status`.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/cancel":{"post":{"operationId":"cancelRun","summary":"Cancel Agent Run","description":"Request cancellation. Idempotent: a queued run cancels immediately; a\nrunning run flips to `canceling` and settles at its next checkpoint; a\nterminal run is unchanged.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"The run after the cancel request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/submit":{"post":{"operationId":"submitRun","summary":"Submit Agent Run Approval","description":"Resolve a run that is durably waiting for human approval (status\n`requires_action`). Read the run's `pending_action` first - it describes,\nverbatim, the exact action awaiting approval and the effect of each\ndisposition. `approve` resumes the run to execute the action; `deny`\nresumes it down its could-not-complete path. Because the wait is durable,\na run resumes even across a deploy.\n\nIdempotent via `Idempotency-Key`: a retry replays the first decision\nrather than re-deciding. If nobody responds before the pending action's\n`expires_at`, the run applies its `default_decision` (deny, for a\nconsequential action).\n\nReturns `409 agent_run_not_pending` if the run is not awaiting approval,\nand `409 agent_run_action_stale` if `action_key` no longer matches the\nrun's current pending action.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"The run after the approval decision (resumed to `running`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitAgentRunRequest"}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/steps":{"get":{"operationId":"listRunSteps","summary":"List Agent Run Steps","description":"List a run's **step journal**: the append-only plan-act-observe trail the\ndurable runtime leaves behind, in order (`seq` ascending). Each step is one\niteration of the loop - a `plan` (the agent's rationale), a `tool_call` (the\ntool and its arguments), or an `observation` (the tool's result). The run's\nfinal answer is not a step; it lives on the run object (`getRun`). Use this\nto render or audit exactly what a run did between its instruction and its\nresult.\n\nCredential-shaped values in a step's arguments and content are redacted.\n\n**The journal is kept for 90 days**, after which an older run returns an\nempty list here. The run itself is not deleted with it: its instruction,\nanswer and usage stay on the run object indefinitely. Export the trail\nif you need it beyond that window.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run's steps, oldest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunStepsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/events":{"get":{"operationId":"streamEvents","summary":"Stream Run Events","description":"Server-Sent Events stream of a run's progress, so a client can follow\na long-running agent instead of polling it.\n\nHolds the connection open and tails the run's journal: a\n`run.step.added` event per step (its `data` is a RunStep object, the\nsame shape List Run Steps returns), a `run.status.changed` event when\nthe run's status moves, and a terminal `run.ended` event carrying the\nfinal status and, when the run stopped short, its\n`incomplete_reason`. Consumers must ignore unknown event types.\n\nA run waiting on a human approval is not terminal: the stream reports\n`requires_action` and keeps tailing, so the client learns it has\nsomething to decide.\n\n## The stream is expected to reconnect\n\n**The server closes the connection after 4 minutes whether or not the\nrun has settled**, so a long run spans several connections. Only\n`run.ended` means the run is over - a closed socket does not. The\nresponse opens with `retry: 2000`, so a browser `EventSource`\nreconnects on its own; a hand-rolled client must do the same.\n\nResume with the standard `Last-Event-ID` header. Step events carry\ntheir `seq` as the event id, so a reconnect continues exactly where it\nleft off; status events carry no id, and an absent or unparseable\n`Last-Event-ID` replays the journal from the beginning rather than\nskipping it. On every connection, including a resume, the server emits\none `run.status.changed` carrying the run's current status before it\nstarts tailing. A `: keepalive` comment arrives every 15 seconds so an\nintermediary does not time the connection out while the agent is\nthinking.\n\nSame read access as List Run Steps, and the same\n`402 durable_runs_not_in_plan` on a workspace without the grant.","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"An event stream (text/event-stream) of run steps and status changes.","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/AgentRunStreamEvent"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/children":{"get":{"operationId":"listRunChildren","summary":"List Agent Run Children","description":"List the runs this run **delegated to**, newest first. When a run hands a\nsub-goal to another agent (a team member), the sub-goal executes as its own\nbounded child run with its own status, output, and `usage` - so a run's true\ncost is its own usage plus its children's.\n\nEach child runs under the MEMBER's agent, not this one, so children never\nappear in this agent's `listRuns`. This is the downward half of lineage; the\nupward half is `parent_run_id` on the run object. A run that delegated\nnothing returns an empty list.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run's delegated child runs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases":{"get":{"operationId":"list","summary":"List Knowledge Bases","description":"List knowledge bases owned by the caller. Cursor-paginated:\nomit `cursor` to fetch the first page. The default page size is\n50 and the max is 200; values outside that range are clamped.\nWalk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The knowledge bases for the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBasesResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Knowledge Base","description":"Create a new knowledge base.","tags":["knowledgeBases"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBase"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKnowledgeBaseRequest"}}}}}},"/v1/agents/knowledge-bases/search":{"post":{"operationId":"search","summary":"Search Knowledge Bases","description":"Semantic search across a caller-owned list of knowledge bases.\nReturns ranked chunks with source filename and a cosine-similarity\nscore. Limited to 50 results per request.","tags":["knowledgeBases"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Ranked search hits across the selected knowledge bases.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchKnowledgeBasesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchKnowledgeBasesRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}":{"get":{"operationId":"get","summary":"Get Knowledge Base","description":"Retrieve a knowledge base by ID.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBase"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Knowledge Base","description":"Update a knowledge base.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBase"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateKnowledgeBaseRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Knowledge Base","description":"Soft-delete a knowledge base. Documents and chunks are cascaded.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Knowledge base deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/coverage":{"get":{"operationId":"getCoverage","summary":"Get Knowledge Base Coverage","description":"Report what callers asked that this knowledge base did not cover\nover a trailing window, and which of its documents no search\nsurfaced. Built from the searches agents ran against it: a health\nstrip (searches by verdict, conversations, median latency, documents\nused), the partial and insufficient questions clustered by the\ncontent words they share, and the ready documents no search in the\nwindow retrieved a passage from. Documents added in the last 24\nhours are not listed as unused yet.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"days","in":"query","description":"Trailing window in days (default 30, max 90).","required":false,"schema":{"type":"integer","minimum":1,"maximum":90,"default":30}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The coverage report for the window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseCoverage"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/eval-cases":{"get":{"operationId":"listEvalCases","summary":"List Evaluation Cases","description":"List the labelled questions of a knowledge base, newest first.\nCursor-paginated: omit `cursor` to fetch the first page. Default\npage size is 50 and max is 200. Walk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The cases.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseEvalCasesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createEvalCase","summary":"Create Evaluation Case","description":"Add a labelled question: the documents that answer it, or\n`unanswerable` when nothing in the knowledge base should. Every\nexpected document must belong to this knowledge base. A knowledge\nbase holds at most 500 cases.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvalCase"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKnowledgeBaseEvalCaseRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/eval-cases/{case_id}":{"patch":{"operationId":"updateEvalCase","summary":"Update Evaluation Case","description":"Change a case. Omitted fields keep their value. Past evaluations\nkeep the question and expectation they ran against.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"case_id","in":"path","description":"Evaluation case id (prefixed external id, `kbcase_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvalCase"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateKnowledgeBaseEvalCaseRequest"}}}}},"delete":{"operationId":"deleteEvalCase","summary":"Delete Evaluation Case","description":"Remove a case. Past evaluations keep the outcome they recorded for\nit.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"case_id","in":"path","description":"Evaluation case id (prefixed external id, `kbcase_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Case deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/evaluations":{"get":{"operationId":"listEvaluations","summary":"List Knowledge Base Evaluations","description":"List evaluations of a knowledge base, newest first. Cursor-paginated:\nomit `cursor` to fetch the first page. Default page size is 50 and\nmax is 200. Walk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseEvaluationsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"startEvaluation","summary":"Start Knowledge Base Evaluation","description":"Run every case against the live retrieval pipeline. Returns 202 with\nthe evaluation; poll `GET /{kb_id}/evaluations/{evaluation_id}` until\nits `status` is terminal, then read the metrics on it and the per-case\noutcomes from `/results`. Each case is one search the workspace pays\nfor, so the same spend limits as a search apply.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Evaluation queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvaluation"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartKnowledgeBaseEvaluationRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/evaluations/{evaluation_id}":{"get":{"operationId":"getEvaluation","summary":"Get Knowledge Base Evaluation","description":"Retrieve one evaluation: its lifecycle status, progress counts, the\nverdict breakdown and, once completed, its metrics.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"evaluation_id","in":"path","description":"Knowledge-base evaluation id (prefixed external id, `kbeval_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvaluation"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/evaluations/{evaluation_id}/cancel":{"post":{"operationId":"cancelEvaluation","summary":"Cancel Knowledge Base Evaluation","description":"Stop a pending or running evaluation. Idempotent: a finished\nevaluation comes back unchanged.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"evaluation_id","in":"path","description":"Knowledge-base evaluation id (prefixed external id, `kbeval_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvaluation"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/evaluations/{evaluation_id}/results":{"get":{"operationId":"listEvaluationResults","summary":"List Knowledge Base Evaluation Results","description":"The per-case outcomes of an evaluation, in case order.\nCursor-paginated: omit `cursor` to fetch the first page. Default\npage size is 50 and max is 200. Walk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"evaluation_id","in":"path","description":"Knowledge-base evaluation id (prefixed external id, `kbeval_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The results.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseEvaluationResultsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents":{"get":{"operationId":"listDocuments","summary":"List Knowledge Base Documents","description":"List documents ingested into a knowledge base. Cursor-paginated:\nomit `cursor` to fetch the first page. Default page size is 50\nand max is 200. Walk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"folder_id","in":"query","description":"Folder filter: omit for root-level documents, pass `all` for\nevery document in the KB, or a folder id to scope to that\nfolder.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Substring match on filename and source_url.","required":false,"schema":{"type":"string"}},{"name":"source_kind","in":"query","description":"Comma-separated source kinds (file|url|text).","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The documents in the knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseDocumentsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"uploadDocument","summary":"Upload Knowledge Base Document","description":"Upload a document (PDF, plain text, markdown, or HTML) to a\nknowledge base. The document is extracted, chunked, embedded, and\nindexed synchronously; expect a few seconds per MB of input.\nMaximum 10 MB per upload.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The ingested document record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/{document_id}":{"get":{"operationId":"getDocument","summary":"Get Knowledge Base Document","description":"Retrieve a document by ID.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The document record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocumentDetail"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateDocument","summary":"Update Knowledge Base Document","description":"Update a document: move it between folders via `folder_id`, or pin\nit into every prompt via `injection_mode`. The two are independent;\na body naming only one leaves the other unchanged. A pin is refused\nwith 422 when the knowledge base's pinned documents would exceed\nthe prompt budget, naming the token counts.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocumentDetail"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"folder_id":{"type":["string","null"],"description":"Destination folder. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null moves the\ndocument to the knowledge base root.\n"},"injection_mode":{"$ref":"#/components/schemas/V1AgentsKnowledgeBasesKbIdDocumentsDocumentIdPatchRequestBodyContentApplicationJsonSchemaInjectionMode","description":"`always` keeps the document in every prompt; `on_demand`\nreturns it to retrieval.\n"}}}}}}},"delete":{"operationId":"deleteDocument","summary":"Delete Knowledge Base Document","description":"Delete a document and all its chunks.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Document deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/{document_id}/chunks":{"get":{"operationId":"listChunks","summary":"List Knowledge Base Chunks","description":"List the chunks for a document. Cursor-paginated: omit `cursor`\nto fetch the first page. Default page size is 50 and max is 200.\nWalk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The chunks for the document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseChunksResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/crawl":{"post":{"operationId":"createCrawlImport","summary":"Create Crawl Import","description":"Kick off an async website crawl. Returns 202 with the import\njob row; client polls `GET /{kb_id}/imports` for progress.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Import job queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCrawlImportRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/sitemap":{"post":{"operationId":"createSitemapImport","summary":"Create Sitemap Import","description":"Kick off an async sitemap import. Returns 202 with the import\njob row; client polls `GET /{kb_id}/imports` for progress.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Import job queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSitemapImportRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/text":{"post":{"operationId":"createTextDocument","summary":"Create Text Document","description":"Create a document from inline pasted text. Content is chunked,\nembedded, and indexed synchronously.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTextDocumentRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/url":{"post":{"operationId":"createURLDocument","summary":"Create URL Document","description":"Fetch a URL via Firecrawl and ingest the rendered content as a\ndocument. The fetch happens synchronously; expect a few\nseconds per page. Use the sitemap / crawl endpoints for\nmulti-page imports.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The document was accepted and is being fetched + embedded\nasynchronously. The returned row is a placeholder with\n`status: fetching`; poll the document until it reaches\n`ready` or `failed`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateURLDocumentRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/urls":{"post":{"operationId":"createURLBatchImport","summary":"Create Multi-URL Import","description":"Kick off an async multi-URL import. Accepts 1..N URLs in a\nsingle job (capped per-deployment, default 50) and runs the\nsame per-URL pipeline as the sitemap importer. Returns 202 with\nthe import job row; client polls `GET /{kb_id}/imports` for\nprogress.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Import job queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateURLBatchImportRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/batch":{"delete":{"operationId":"batchDeleteDocuments","summary":"Batch Delete Documents","description":"Delete multiple documents in a single transaction. All ids\nmust belong to the supplied knowledge base; mismatches fail\nthe request with 400 before any rows are touched. Capped at\n200 ids per call.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Documents deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchDeleteDocumentsRequest"}}}}},"patch":{"operationId":"batchMoveDocuments","summary":"Batch Move Documents","description":"Move multiple documents into a folder in a single transaction.\nPass `folder_id: null` to move every doc to root. Capped at\n200 ids per call. Returns the moved documents.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The moved documents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchMoveDocumentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchMoveDocumentsRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/{document_id}/refresh-config":{"patch":{"operationId":"updateRefreshConfig","summary":"Update Refresh Config","description":"Update the per-document auto-refresh state. Only meaningful\nfor url-sourced documents; file and text rows reject the\nrequest.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated refresh config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshConfig"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRefreshConfigRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/{document_id}/refresh-history":{"get":{"operationId":"listRefreshHistory","summary":"List Refresh History","description":"List auto-refresh attempts for a document, newest first.\nCursor-paginated: omit `cursor` to fetch the first page. Default\npage size is 50 and max is 200. Walk pages while `has_more` is\ntrue.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recent refresh attempts for the document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListRefreshHistoryResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/folders":{"get":{"operationId":"listFolders","summary":"List Folders","description":"List folders inside a knowledge base. Root-level folders have\n`parent_folder_id: null`. Cursor-paginated: omit `cursor` to\nfetch the first page. Default page size is 50 and max is 200.\nBuild the folder tree from `parent_folder_id`, so consumers\nshould walk every page until `has_more` is `false` before\nrendering the tree.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Folders in the knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseFoldersResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createFolder","summary":"Create Folder","description":"Create a folder inside a knowledge base.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseFolder"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFolderRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/folders/{folder_id}":{"patch":{"operationId":"updateFolder","summary":"Update Folder","description":"Update a folder. Pass `parent_folder_id: null` to move to\nroot; omit the field to leave it unchanged.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"folder_id","in":"path","description":"Knowledge-base folder id (prefixed external id, `kfolder_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseFolder"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFolderRequest"}}}}},"delete":{"operationId":"deleteFolder","summary":"Delete Folder","description":"Delete a folder. By default a folder that still contains\ndocuments or sub-folders is refused with `409` - pass\n`?force=true` to delete it anyway, which recursively moves its\ndocuments to root and removes its sub-folders.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"folder_id","in":"path","description":"Knowledge-base folder id (prefixed external id, `kfolder_...`).","required":true,"schema":{"type":"string"}},{"name":"force","in":"query","description":"When true, delete the folder even if it still contains documents or sub-folders; documents are moved to root and sub-folders are removed.","required":false,"schema":{"type":"boolean"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Folder deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/imports":{"get":{"operationId":"listImportJobs","summary":"List Import Jobs","description":"List import jobs (sitemap / crawl / refresh) for a knowledge\nbase, newest first. Cursor-paginated: omit `cursor` to fetch the\nfirst page. Default page size is 50 and max is 200. Walk pages\nwhile `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The import jobs for the knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListImportJobsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/imports/{import_id}":{"get":{"operationId":"getImportJob","summary":"Get Import Job","description":"Retrieve a single import job by ID. Poll this endpoint until\n`status` reaches a terminal state (`completed` / `failed` /\n`cancelled`).\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"import_id","in":"path","description":"Knowledge-base import job id (prefixed external id, `import_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The import job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/imports/{import_id}/cancel":{"post":{"operationId":"cancelImportJob","summary":"Cancel Import Job","description":"Cancel an import job. Moves a non-terminal job to `cancelled` and\nreturns `200` with the job so callers see its new `status` without a\nfollow-up `GET`. Idempotent (\u00a716), matching the other job-cancel verbs\n(`cancel` on batch calls, test runs, and suite runs): cancelling an\nalready-terminal job (`completed` / `failed` / `cancelled`) returns it\nunchanged. 404 only when the job id is unknown or its kb_id does not\nmatch the `{kb_id}` path segment.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"import_id","in":"path","description":"Knowledge-base import job id (prefixed external id, `import_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The cancelled import job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/knowledge-bases":{"get":{"operationId":"listAgentKnowledgeBases","summary":"List Agent Knowledge Bases","description":"List knowledge bases attached to an agent. Cursor-paginated: omit\n`cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200).\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The knowledge bases attached to the agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachedKnowledgeBasesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/knowledge-bases/{kb_id}":{"post":{"operationId":"attachKnowledgeBase","summary":"Attach Agent Knowledge Base","description":"Attach a knowledge base to an agent. The `search_knowledge` tool\nis auto-registered on the next conversation and can only query the\nattached knowledge bases.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Knowledge base attached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"detachKnowledgeBase","summary":"Detach Agent Knowledge Base","description":"Detach a knowledge base from an agent.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Knowledge base detached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/tools":{"get":{"operationId":"listTools","summary":"List Agent Tools","description":"List everything this agent can do: built-in capabilities and\nattached external tool definitions (webhook / client / MCP) in\none mixed-kind list. Each item carries its resolved config inline.\nCursor-paginated: omit `cursor` for the first page; walk pages while\n`has_more` is true (default page size 50, max 200).\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's tools.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentToolsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createTool","summary":"Create Agent Tool","description":"Add a tool to the agent. With `kind=builtin` this creates a\nper-agent built-in instance (e.g. `end_call`, `play_audio`); the\n`builtin` capability name comes from `config.builtin`. With\n`kind=webhook`, `client`, or `mcp` this atomically creates a\nworkspace tool definition AND attaches it to the agent in one\ncall. To attach a definition that already exists, use\n`PUT /v1/agents/{agent_id}/tools/{tool_id}` instead.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nThe contents of `config` are your own data at this level; they are\nvalidated against the tool kind's own schema, which has always\nrefused an unknown key there.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentToolRequest"}}}}}},"/v1/agents/{agent_id}/tools/{tool_id}":{"get":{"operationId":"getTool","summary":"Get Agent Tool","description":"Fetch one of the agent's tools by its id (built-in or attached definition).","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"tool_id","in":"path","description":"Tool id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTool"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"attachTool","summary":"Attach Tool","description":"Attach an existing workspace tool definition (webhook / client /\nMCP) to the agent by its definition id. Idempotent. To create and\nattach in one step, POST to `/v1/agents/{agent_id}/tools` instead.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"tool_id","in":"path","description":"Tool id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Tool attached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateTool","summary":"Update Agent Tool","description":"Update one of the agent's tools. For a built-in, every field\napplies. For an attached external tool, only `enabled` is\neditable here \u2014 its shared config/name is edited on the\ndefinition (`PATCH /v1/agents/tool-definitions/{tool_definition_id}`), since the\ndefinition is referenced live by every agent it is attached to;\nattempting a shared-config edit here returns `tool_config_shared`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nThe contents of `config` are your own data at this level; they are\nvalidated against the tool kind's own schema, which has always\nrefused an unknown key there.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"tool_id","in":"path","description":"Tool id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentToolRequest"}}}}},"delete":{"operationId":"deleteTool","summary":"Delete Agent Tool","description":"Remove a tool from the agent. A built-in instance is deleted; an\nattached external tool is detached (the workspace definition\nsurvives and stays attachable elsewhere).\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"tool_id","in":"path","description":"Tool id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Tool removed.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tool-definitions":{"get":{"operationId":"list","summary":"List Tools","description":"List tools in the caller's workspace, most recently updated\nfirst. Cursor-paginated: omit `cursor` to fetch the first page.\nDefault page size is 50 and max is 200. Walk pages while\n`has_more` is true.","tags":["toolDefinitions"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of tools.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListToolsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Tool","description":"Create a tool. For webhook tools, the response includes the HMAC\n`webhook_secret` exactly once \u2014 store it immediately; subsequent\nreads return a masked placeholder.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nThe contents of `config` are your own data at this level; they are\nvalidated against the tool kind's own schema, which has always\nrefused an unknown key there.\n","tags":["toolDefinitions"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateToolRequest"}}}}}},"/v1/agents/tool-definitions/{tool_definition_id}":{"get":{"operationId":"get","summary":"Get Tool","description":"Retrieve a tool by ID. Webhook secrets are always masked here.","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Tool","description":"Update a tool. Tool kind is immutable \u2014 create a new tool to change\nit.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nThe contents of `config` are your own data at this level; they are\nvalidated against the tool kind's own schema, which has always\nrefused an unknown key there.\n","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateToolRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Tool","description":"Delete a tool. Agents that had it attached get a soft-detach.","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Tool deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tool-definitions/{tool_definition_id}/attached-agents":{"get":{"operationId":"listAttachedAgents","summary":"List Tool Attached Agents","description":"List the agents in the caller's workspace that currently have\nthis tool attached. Useful before deleting a tool, to surface\nwhich agents will lose access. Soft-deleted agents are filtered\nout. Cursor-paginated: omit `cursor` for the first page; walk\npages while `has_more` is true (default page size 50, max 200).","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Agents in the caller's workspace attached to the tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListToolAttachedAgentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tool-definitions/{tool_definition_id}/rotate-secret":{"post":{"operationId":"rotateSecret","summary":"Rotate Tool Webhook Secret","description":"Rotate the HMAC signing secret on a webhook tool. The tool id\nis preserved so attached agents keep working; only the secret\nrolls. The new plaintext is returned on `webhook_secret`\nexactly once \u2014 store it immediately, subsequent reads always\nreturn the masked placeholder. The previous secret is\ninvalidated immediately on success.\n","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The tool with its newly-rotated webhook_secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tool-definitions/test-mcp-connection":{"post":{"operationId":"testMCPConnection","summary":"Test MCP Connection","description":"Probe a customer-supplied MCP server config without persisting\nanything. The server opens the configured transport, runs the\n`initialize` + `list_tools` handshake, and returns either the\ndiscovered tool catalogue or a structured error string. Pass\n`tool_id` from the edit-form flow when the auth payload carries\n`_set` markers but no plaintext, so the server can hydrate the\nstored secret from the encrypted column before probing.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n","tags":["toolDefinitions"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Probe result. The 200 envelope is used for both success and\nstructured failure \u2014 inspect `error` to disambiguate. Network\nand validation failures never bubble up as non-2xx so a\nclient can render them inline next to the form.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCPProbeResult"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestMCPConnectionRequest"}}}}}},"/v1/agents/tool-definitions/test-webhook-connection":{"post":{"operationId":"testWebhookConnection","summary":"Test Webhook Connection","description":"Probe a customer-supplied webhook tool config without persisting\nanything. The server fires the exact request shape used on a real\ninvocation \u2014 same JSON body, same HMAC-SHA256\nsignature \u2014 with an empty argument set, and reports the\nendpoint's status code, latency, and a truncated response body,\nor a transport-level failure reason. The probe carries a\n`Speechify-Webhook-Test: true` header so a careful endpoint\ncan recognise the test and skip its real side effect. Pass\n`tool_id` from the edit-form flow so the server signs the probe\nwith the tool's stored HMAC secret.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n","tags":["toolDefinitions"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Probe result. The 200 envelope is used for both success and\nstructured failure \u2014 inspect `error` to disambiguate. A\nnon-2xx response from the endpoint is NOT an `error`: it\npopulates `status_code` / `response_body` with `ok=false`.\nTransport and validation failures never bubble up as non-2xx\nso a client can render them inline next to the form.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookProbeResult"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestWebhookConnectionRequest"}}}}}},"/v1/agents/tool-capabilities":{"get":{"operationId":"listToolCapabilities","summary":"List Tool Capabilities","description":"Read-only catalogue of every built-in tool capability the agent\nruntime supports (the names usable as `config.builtin` when adding a\n`kind=builtin` tool to an agent). Fetch this at runtime rather\nthan maintaining a parallel client-side list; the server is the\nsingle source of truth for the label and description text a\ncustomer sees in the capability picker. Returns\nthe full set in a single response: bounded by the platform's\nbuilt-in tool-capability registry, so this list is intentionally\nnot paginated.\n","tags":["agent"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The catalogue of registered built-in capabilities.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSystemBuiltinsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests":{"post":{"operationId":"createTest","summary":"Create Test","description":"Create a workspace-level test. The optional `agent_id` is the\nauthoring agent: when supplied it seeds the test's tool schemas and\nvariables and is recorded as authored-from provenance. Omit it to\ncreate a bare workspace test with no authoring agent (`agent_id`\ncomes back `null`). `agent_id` is never a scope - every read, list,\nand run is scoped by workspace, and the run target is bound at run\ntime (`POST /v1/agents/tests/runs`) independently of it.","tags":["tests"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTest"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentTestRequest"}}}}},"get":{"operationId":"listAllTests","summary":"List Tests","description":"Workspace-wide list of tests across every agent the caller owns.\nSupports filters (agent, type, last-run status, folder), full-text\nsearch on name/description, and cursor pagination. Each row carries\nits newest run and attached agent IDs so the list renders without\nN+1 round-trips. Walk pages while `has_more` is true.\n\n`project_id` matches the project the test was AUTHORED under, frozen\nat create. Folders are deliberately not project-scoped, so a folder\ncan hold tests this filter splits across projects.","tags":["tests"],"parameters":[{"name":"agent_id","in":"query","description":"Comma-separated agent IDs to filter on.","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","description":"Comma-separated test types (reply|tool|simulation).","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Comma-separated last-run lifecycle statuses to filter on\n(pending, running, completed, failed, cancelled).\n","required":false,"schema":{"type":"string"}},{"name":"verdict","in":"query","description":"Comma-separated last-run verdicts to filter on (passed, failed).\nANDed with the `status` filter when both are present.\n","required":false,"schema":{"type":"string"}},{"name":"folder_id","in":"query","description":"Folder ID to filter on, or \"root\" for unfiled tests.","required":false,"schema":{"type":"string"}},{"name":"updated_after","in":"query","description":"Only return tests updated after this RFC3339 timestamp.","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Substring match on name or description.","required":false,"schema":{"type":"string"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTestsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/{test_id}":{"get":{"operationId":"getTest","summary":"Get Agent Test","description":"Retrieve a test by ID.","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTest"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateTest","summary":"Update Agent Test","description":"Update a test. Only fields present on the request body are changed.","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTest"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentTestRequest"}}}}},"delete":{"operationId":"deleteTest","summary":"Delete Agent Test","description":"Delete a test and all its run history.","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Test deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/folders":{"get":{"operationId":"listTestFolders","summary":"List Test Folders","description":"List every test folder the caller owns. Flat list; build the tree\nclient-side. Cursor-paginated: omit `cursor` for the first page;\nwalk pages while `has_more` is true (default page size 50, max 200).\n\nFolders are workspace-wide, so `project_id` never hides a folder:\nit narrows each folder's `test_count` to the tests that project\nscope admits, the same rows `GET /v1/agents/tests?folder_id=`\nlists under that scope, while `total_test_count` counts what the\ncaller could see with no filter (pin and grants still apply).","tags":["tests"],"parameters":[{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Folder list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentTestFoldersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createTestFolder","summary":"Create Test Folder","description":"Create a test folder. Max depth is 3.","tags":["tests"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"Created folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestFolder"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentTestFolderRequest"}}}}}},"/v1/agents/tests/folders/{test_folder_id}":{"patch":{"operationId":"updateTestFolder","summary":"Update Test Folder","description":"Rename or reparent a test folder. Cycles are rejected.","tags":["tests"],"parameters":[{"name":"test_folder_id","in":"path","description":"Agent test folder id (prefixed external id, `folder_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestFolder"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentTestFolderRequest"}}}}},"delete":{"operationId":"deleteTestFolder","summary":"Delete Test Folder","description":"Soft-delete a folder. Child tests drop back to root.","tags":["tests"],"parameters":[{"name":"test_folder_id","in":"path","description":"Agent test folder id (prefixed external id, `folder_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/{test_id}/runs":{"get":{"operationId":"listTestRuns","summary":"List Agent Test Runs","description":"List one page of run history for a test, newest first.\nPaginate by passing `cursor` from the previous response.\n","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Run history for the test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentTestRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"runTest","summary":"Run Agent Test","description":"Enqueue a single run of the test. The run binds its target agent at\nrun time: pass `agent_id` to run against any agent in the workspace,\nor omit it to run against the agent the test was authored against. A\ntest with no authoring agent requires an explicit `agent_id`. The\nreturned run starts in `pending` status. Poll\n`GET /v1/agents/tests/runs/{test_run_id}` until the status reaches a\nterminal state (`completed`, `failed`, or `cancelled`). A run that\nreached `completed` produced a judgment - read the separate\n`verdict` field (`passed` / `failed`) for the pass/fail result.\n\nA run is admitted against the workspace's remaining credit and its\nspending limits, and executes on the models the workspace's plan\nincludes. A depleted balance or an exhausted spend limit returns\n`402`.","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The queued run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestRun"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"agent_id":{"type":"string","description":"Bind the run to this agent (any agent in the workspace).\nOmit to run against the agent the test was authored\nagainst; required when the test has no authoring agent.\n"}}}}}}}},"/v1/agents/tests/runs":{"post":{"operationId":"runTargetedTests","summary":"Run Tests (Targeted)","description":"Run a chosen set of tests against a target agent, bound at run\ntime. The tests are not attached to the agent, so the same set can\nrun against another agent variant in a second call (A/B) and,\nonce agent versioning ships, against a pinned version\n(version-pinned regression). Every child run records the resolved\ntarget on the returned suite run. Total runs are capped at 100 per\ncall; poll `GET /v1/agents/tests/runs/{test_run_id}` for each.","tags":["tests"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Runs queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAgentTestsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunTargetedTestsRequest"}}}}}},"/v1/agents/tests/runs/batch":{"post":{"operationId":"runTestsBatch","summary":"Run Tests (Batch)","description":"Queue runs for every (test, agent) pair in the body. Entries\nwithout an `agent_id` fan out to every agent the test is\nattached to. Total expanded runs are capped at 100 per call.\nEach entry in the response is a queued run; poll\n`GET /v1/agents/tests/runs/{test_run_id}` for each.","tags":["tests"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Runs queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAgentTestsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunBatchRequest"}}}}}},"/v1/agents/tests/runs/{test_run_id}":{"get":{"operationId":"getTestRun","summary":"Get Agent Test Run","description":"Retrieve a single test run by ID. Poll this endpoint until\n`status` reaches a terminal state (`completed`, `failed`, or\n`cancelled`). On `completed`, read `verdict` for the pass/fail\njudgment and `result` for the detail.","tags":["tests"],"parameters":[{"name":"test_run_id","in":"path","description":"Test run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The test run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/runs/{test_run_id}/cancel":{"post":{"operationId":"cancelTestRun","summary":"Cancel Agent Test Run","description":"Cancel a non-terminal test run. Idempotent on a terminal run\n(`completed` / `failed` / `cancelled`) - the call returns the\nunchanged run.","tags":["tests"],"parameters":[{"name":"test_run_id","in":"path","description":"Test run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The (possibly already-terminal) test run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/suite-runs":{"get":{"operationId":"listSuiteRuns","summary":"List Suite Runs","description":"List one page of suite runs (test invocations), newest first.\nA suite run groups every test run dispatched by one Run All,\nbatch, or resubmit call. Paginate by passing `cursor` from the\nprevious response.\n","tags":["tests"],"parameters":[{"name":"agent_id","in":"query","description":"Narrow the list to the suite runs of one agent.","required":false,"schema":{"type":"string"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"One page of suite runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSuiteRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/suite-runs/{suite_run_id}":{"get":{"operationId":"getSuiteRun","summary":"Get Suite Run","description":"Retrieve a suite run by ID with its child runs and the derived\naggregate `status`, `verdict`, and progress counts.","tags":["tests"],"parameters":[{"name":"suite_run_id","in":"path","description":"Suite run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The suite run with its child runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestSuiteRunWithRuns"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/suite-runs/{suite_run_id}/resubmit":{"post":{"operationId":"resubmitSuiteRun","summary":"Resubmit Suite Run","description":"Re-run the failed and errored tests of a suite run as a fresh\nsuite run, linked back to the original via\n`parent_suite_run_id`. Returns 400 when the suite run has no\nfailed or errored tests to re-run.\n\nThe parent's `config_override` is re-applied, so it is re-checked\nagainst the workspace's current plan: a model the workspace no\nlonger has returns `402` rather than replaying it.","tags":["tests"],"parameters":[{"name":"suite_run_id","in":"path","description":"Suite run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The new suite run and its queued child runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestSuiteRunWithRuns"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/suite-runs/{suite_run_id}/cancel":{"post":{"operationId":"cancelSuiteRun","summary":"Cancel Suite Run","description":"Cancel a suite run: every child run still pending or running is\ncancelled, which makes the suite's derived `status` `cancelled`.\nChild runs that already produced a verdict or errored are left\nuntouched. Idempotent on a terminal suite - returns it unchanged.","tags":["tests"],"parameters":[{"name":"suite_run_id","in":"path","description":"Suite run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The suite run with its child runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestSuiteRunWithRuns"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/tests/runs":{"post":{"operationId":"runAllTests","summary":"Run All Agent Tests","description":"Enqueue runs for every test on the agent concurrently. Up to 50\ntests are dispatched in one call. Each returned run starts in\n`pending` status; poll `GET /v1/agents/tests/runs/{test_run_id}` for the terminal\nresult (`completed` / `failed` / `cancelled`, with the pass/fail\njudgment in the separate `verdict` field).\n\nAn optional request body runs the whole suite against\na proposed config: a `config_override` (prompt / model / tools)\napplied to every test without editing the tests, and/or a\n`flow_version_id` to target a specific flow version instead of\nthe agent's active flow. Omit the body to run against the\nagent's live config and active flow.\n\nRuns are admitted against the workspace's remaining credit and its\nspending limits. `config_override.model` must be a model the\nworkspace's plan includes; an over-tier model returns `402` before\nany run is queued.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Queued runs for all tests on the agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAgentTestsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAllTestsRequest"}}}}}},"/v1/agents/tests/stats":{"get":{"operationId":"getTestStats","summary":"Get Test Stats","description":"Aggregate pass-rate metrics over the last N days. Returns dense\ndaily buckets (one entry per day, zero-filled) plus totals and a\nper-type breakdown. Powers the header chart on the global tests\npage. Default window is 30 days, max 90.\n\nScoped by the same `project_id` filter as the list endpoint, so the\nchart above a narrowed table always counts the same tests.","tags":["tests"],"parameters":[{"name":"window_days","in":"query","description":"Trailing window in days (default 30, max 90).","required":false,"schema":{"type":"integer"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Stats payload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestStats"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/share-links":{"get":{"operationId":"list","summary":"List Share Links","description":"List the agent's share links, most recently created first.\nCursor-paginated: omit `cursor` to fetch the first page.\n\nRevoked and expired links are included \u2014 an owner needs to see what\nthey have handed out, not only what is currently live. The bearer\ntoken is never returned; only a short masked prefix so two links can\nbe told apart.","tags":["shareLinks"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of share links.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListShareLinksResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Share Link","description":"Mint a share link for the agent.\n\nThe response carries the link's bearer `token`. It is also stored, so\na caller who loses it reads it back with `revealShareLinkToken`\ninstead of revoking and re-creating \u2014 which would kill the copy the\nrecipient already has.\n\nEvery cap is optional and falls back to a bounded default. There is\nno way to express \"unlimited\": an anonymous link that spends the\nworkspace's balance should not be creatable by omitting a field.\n\nAn agent may hold at most 10 live links at once; an eleventh is\nrefused with `409 share_link_limit_reached`. Revoking a link, or\nletting one expire, frees its slot immediately.\n","tags":["shareLinks"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created link, including its one-time token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateShareLinkResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateShareLinkRequest"}}}}}},"/v1/agents/{agent_id}/share-links/{share_link_id}/reveal":{"post":{"operationId":"reveal","summary":"Reveal Share Link Token","description":"Return a share link's bearer token, so the URL can be copied again.\n\nA `POST` rather than a `GET`, and permission-gated like a mutation\neven though it changes nothing: the response is a live bearer\ncredential, so it must never be cacheable or replayable from a\nbrowser's history, and whoever may hand a link out is whoever may\nhand it out again. It is separately rate-limited.\n\nRevoked and expired links still reveal \u2014 the owner can already see\nthat state and may need to tell a recipient which link they hold.\n","tags":["shareLinks"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"share_link_id","in":"path","description":"Share link id (prefixed external id, `share_...`).","required":true,"schema":{"type":"string","pattern":"^share_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The link's bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevealShareLinkTokenResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/share-links/{share_link_id}":{"delete":{"operationId":"revoke","summary":"Revoke Share Link","description":"Revoke a share link. Idempotent \u2014 revoking an already-revoked link\nsucceeds and leaves the original timestamp intact.\n\nRevocation stops new calls immediately. It does not hang up a call\nalready in progress: that call is already bounded by its own\nduration limit and has already drawn its time from the link's\nbudget, so ending it mid-sentence would surprise the person talking\nwithout saving the owner anything.\n","tags":["shareLinks"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"share_link_id","in":"path","description":"Share link id (prefixed external id, `share_...`).","required":true,"schema":{"type":"string","pattern":"^share_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The link was revoked.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/share-links/preview":{"get":{"operationId":"preview","summary":"Preview Share Link","description":"Resolve a share link for display, before any call is started.\n\nUnauthenticated: the link token is the credential. The response is\ndeliberately cosmetic-only \u2014 the agent's display name, the embed's\nvisual configuration, and the link's remaining budget. It never\nreturns the agent id, the workspace id, the prompt, the model, or\nany tool or knowledge-base name.\n\nThis endpoint performs no write and consumes no budget. Link\nunfurlers (chat clients, mail providers, security scanners) will\nfetch a pasted URL, so a preview must never be able to spend a\nlink's remaining call time.\n\nUnknown, revoked and expired links all answer `404\nshare_link_not_found`. The states are indistinguishable on purpose:\nthe token is a bearer credential that spends money, so telling a\ncaller which of their guesses named a real link would hand them an\nenumeration oracle.\n","tags":["shareLinks"],"parameters":[{"name":"token","in":"query","description":"The share link's opaque bearer token.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The link's public, cosmetic projection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLinkPreview"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/share-links/sessions":{"post":{"operationId":"createSession","summary":"Create Share Link Session","description":"Start a realtime voice session against a share link.\n\nUnauthenticated: the link token is the credential, and it is sent in\nthe request body rather than the URL so it never reaches request\nlogs. The agent's owner is billed for the call.\n\nThe response carries only the realtime handles \u2014 `token` and `url` \u2014\nwhich is exactly the shape the `` element's\n`sessionProvider` expects. It deliberately omits the conversation id\nand the agent snapshot that `createSession` returns, because those\nare owner-side facts an anonymous visitor should not hold.\n\nCall time is claimed from the link's budget before anything is\ndispatched. A link with too little budget left for another call\nanswers `409 share_link_exhausted`; one already running its maximum\nnumber of simultaneous calls answers `429`.\n","tags":["shareLinks"],"parameters":[{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The realtime handles for the new session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLinkSession"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateShareLinkSessionRequest"}}}}}},"/v1/agents/phone-numbers":{"get":{"operationId":"list","summary":"List Phone Numbers","description":"List all phone numbers in the caller's workspace. Cursor-paginated:\nomit `cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200).","tags":["phoneNumbers"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The phone numbers for the workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListPhoneNumbersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"import","summary":"Import Phone Number","description":"Import a phone number into the workspace. The `provider` field\ndetermines the provisioning path:\n\n- `livekit` - LiveKit purchases the number on your behalf. US\n inbound only. Quickest path for local testing.\n- `twilio` - Provide your Twilio Account SID, Auth Token, and\n the E.164 number you already own. We provision an Elastic SIP\n Trunk on your Twilio account automatically.\n- `byoc` - Provide an existing SIP trunk ID. The number is\n registered against that trunk.\n\nReturns 402 when the workspace has reached the 100-number cap.\n","tags":["phoneNumbers"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The imported phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportPhoneNumberRequest"}}}}}},"/v1/agents/phone-numbers/available":{"get":{"operationId":"searchAvailable","summary":"Search Available Phone Numbers","description":"Search carrier inventory for phone numbers available to purchase.\nCurrently restricted to the US (`country=US`); pass `area_code`\nto narrow to a specific NPA. The returned numbers are not held;\na subsequent `POST /v1/agents/phone-numbers/purchase` against the same\nE.164 may fail with 4xx if the number has been taken in the\nmeantime.\n","tags":["phoneNumbers"],"parameters":[{"name":"country","in":"query","description":"ISO-3166 alpha-2 country code. Defaults to \"US\"; only \"US\" is supported in v1.","required":false,"schema":{"type":"string","default":"US"}},{"name":"area_code","in":"query","description":"Three-digit NPA to filter inventory to a region.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results to return. Capped at 50.","required":false,"schema":{"type":"integer","default":20}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Available numbers (may be empty if no inventory matches).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAvailablePhoneNumbersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/phone-numbers/purchase":{"post":{"operationId":"purchase","summary":"Purchase Phone Number","description":"Purchase a phone number on Speechify's master Twilio account.\nThe number is billed to Speechify until released. A plan that\nincludes no purchased numbers (e.g. Free) returns\n`402 purchased_numbers_not_included`; a plan that has used its full\nincluded quota returns 422. This is independent of the overall\n100-number cap. Because the number is a recurring Speechify-borne\ncarrier cost, the workspace must also have a funded balance (a paid\nplan or a payment method on file, not just trial / granted credits),\nelse the buy is refused with `402 funded_balance_required`.\n`e164` must come from a recent `SearchAvailablePhoneNumbers`\nresponse \u2014 carriers reject buys against numbers that are no\nlonger in inventory. The returned phone number is wired for\nboth inbound (when `agent_id` is set, or after binding the\nnumber to an agent via\n`POST /v1/agents/{agent_id}/phone-numbers/{phone_number_id}`) and\noutbound calls (via the workspace's shared outbound trunk).\n","tags":["phoneNumbers"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The purchased phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchasePhoneNumberRequest"}}}}}},"/v1/agents/phone-numbers/{phone_number_id}":{"get":{"operationId":"get","summary":"Get Phone Number","description":"Retrieve a phone number by ID.","tags":["phoneNumbers"],"parameters":[{"name":"phone_number_id","in":"path","description":"Phone number id (prefixed external id, `phone_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Phone Number","description":"Update a phone number's own attributes (today: `label`), or point it\nat one of your external brains with `brain_id`. `source` and `e164` are\nimmutable after import. To bind or unbind an agent, use the\nrelationship endpoints\n`POST`/`DELETE /v1/agents/{agent_id}/phone-numbers/{phone_number_id}`;\na number's brain is a hosted agent or a brain of your own, never both,\nand setting one clears the other. `brain_id: null` takes the brain off\nthe number and leaves the brain itself untouched, so releasing a number\nnever destroys the service definition behind it. Pointing a number at a\nbrain requires the Phone product to be enabled for the workspace.\n","tags":["phoneNumbers"],"parameters":[{"name":"phone_number_id","in":"path","description":"Phone number id (prefixed external id, `phone_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePhoneNumberRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Phone Number","description":"Delete a phone number from the workspace. For Twilio and LiveKit\nnumbers this also deprovisions the backing SIP trunk and dispatch\nrule on LiveKit Cloud.\n","tags":["phoneNumbers"],"parameters":[{"name":"phone_number_id","in":"path","description":"Phone number id (prefixed external id, `phone_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Phone number deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/outbound-calls":{"post":{"operationId":"create","summary":"Create Outbound Call","description":"Place an outbound call from an agent to a phone number. LiveKit\noriginates the SIP INVITE through the outbound trunk bound to the\nagent's workspace; the agent is dispatched into the call\nautomatically.\n\nThe response is returned as soon as LiveKit accepts the INVITE.\nPoll `GET /v1/agents/conversations/{conversation_id}` for status\ntransitions: `pending` \u2192 `active` (answered) \u2192 `completed`.\n\nThis is also how a Phone number whose brain is your own webhook\ndials out: when the caller ID resolves to a number carrying a\nrelay binding, every turn on the call is answered by your endpoint\ninstead of an LLM, and `call.started`'s reply is the opening line\nspoken to the callee. Everything else is unchanged - same gates,\nsame conversation, same transcript, same recording. Pass the agent\nthe webhook bind provisioned (it is returned on the number) and,\nfor a workspace with several numbers, `caller_id_number`.\n\nRequires a Twilio or BYOC trunk. LiveKit-native numbers are\ninbound-only.\n\nInternational (non-NANP) destinations require a funded balance - a\npaid plan or a payment method on file, not just trial / granted\ncredits - and are otherwise refused with\n`402 funded_balance_required`. Domestic (`+1`) calls are unaffected.\n","tags":["outboundCalls"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The outbound call was accepted by LiveKit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOutboundCallResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOutboundCallRequest"}}}}}},"/v1/agents/batch-calls":{"get":{"operationId":"list","summary":"List Batch Calls","description":"Returns one page of batch calls for the workspace, newest first.\nPaginate by passing `cursor` from the previous response.\n\n`project_id` matches the project the batch was STARTED under, frozen\nat create. Moving its agent to another project therefore leaves past\nbatches attributed where they ran.\n","tags":["batchCalls"],"parameters":[{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBatchCallsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Batch Call","description":"Dial a list of phone numbers through one of your voice agents in a\nsingle request. Each recipient can receive personalised dynamic\nvariables that your agent prompt references via `{{key}}` placeholders.\nBatches can run immediately or be scheduled up to 30 days in advance.\n\nAccepts `application/json` or `multipart/form-data` (with a CSV file).\nMax 1000 recipients per batch.\n","tags":["batchCalls"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Batch accepted for processing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCall"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBatchCallRequest"}}}}}},"/v1/agents/batch-calls/{batch_call_id}":{"get":{"operationId":"get","summary":"Get Batch Call","description":"Returns the batch call. List its recipients with\n`GET /v1/agents/batch-calls/{batch_call_id}/recipients`.\n","tags":["batchCalls"],"parameters":[{"name":"batch_call_id","in":"path","description":"Batch call ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCall"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/batch-calls/{batch_call_id}/recipients":{"get":{"operationId":"listRecipients","summary":"List Batch Call Recipients","description":"Returns one page of recipients for a batch call, ordered by id.\nPaginate by passing `cursor` from the previous response.\n","tags":["batchCalls"],"parameters":[{"name":"batch_call_id","in":"path","description":"Batch call ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBatchRecipientsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/batch-calls/{batch_call_id}/cancel":{"post":{"operationId":"cancel","summary":"Cancel Batch Call","description":"Cancel a batch call. Moves any non-terminal batch - `pending`\n(including one deferred to a future `scheduled_at`) or `running` - to\n`cancelled` and stops the dispatcher from dialing the not-yet-dialed\nrecipients (they become `cancelled`; calls already placed keep their\noutcome). Idempotent (\u00a716): cancelling an already-terminal batch\n(`completed` / `failed` / `cancelled`) returns it unchanged. 404 only\nwhen the batch id is unknown.\n","tags":["batchCalls"],"parameters":[{"name":"batch_call_id","in":"path","description":"Batch call ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The (possibly already-terminal) batch call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCall"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/ivr-menus":{"get":{"operationId":"list","summary":"List IVR Menus","description":"List the active IVR menus the caller's workspace has learned.\nOne row per (fingerprint, tenant).\nInvalidated rows and the cross-tenant shared slot are excluded.\nSorted by `last_observed_at` DESC so the freshest IVRs land at\nthe top. Cursor-paginated: omit `cursor` to fetch the first\npage. Default page size is 50 and max is 200. Walk pages while\n`has_more` is true.\n","tags":["ivrMemory"],"parameters":[{"name":"fingerprint","in":"query","description":"Optional SHA-256 fingerprint hash to narrow the list to one menu.","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of cached IVR menus.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListIVRMenusResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/ivr-menus/{ivr_menu_id}":{"get":{"operationId":"get","summary":"Get IVR Menu","description":"Fetch one menu's full shape. Returns 404 for missing,\nsoft-deleted, or foreign-tenant menus \u2014 existence information\nis never leaked across tenants.\n","tags":["ivrMemory"],"parameters":[{"name":"ivr_menu_id","in":"path","description":"IVR menu ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The IVR menu detail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IVRMenu"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateLabel","summary":"Update IVR Menu Option Label","description":"Re-label one option in the stored menu_tree, matched on the\nsupplied DTMF value. The label is shown when reviewing the menu\nand is read back to the agent at navigate time to surface the\noption semantically. Unknown DTMF values\nare a no-op (the response echoes the unchanged menu).\n","tags":["ivrMemory"],"parameters":[{"name":"ivr_menu_id","in":"path","description":"IVR menu ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The refreshed menu shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IVRMenu"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateIVRMenuLabelRequest"}}}}}},"/v1/agents/ivr-menus/{ivr_menu_id}/invalidate":{"post":{"operationId":"invalidate","summary":"Invalidate IVR Menu","description":"Soft-invalidate the named menu. Future lookups skip it; the\nnext discovery for the same fingerprint replaces it (clearing\nthe invalidation). Idempotent: re-invalidating\nan already-invalidated row returns 404.\n\nReason is optional and is captured in structured logs for\noperator triage. A future audit table may persist it.\n","tags":["ivrMemory"],"parameters":[{"name":"ivr_menu_id","in":"path","description":"IVR menu ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Menu invalidated.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvalidateIVRMenuRequest"}}}}}},"/v1/agents/sip-trunks":{"get":{"operationId":"list","summary":"List SIP Trunks","description":"List all SIP trunks in the caller's workspace. Cursor-paginated\nfor uniformity with the other list endpoints: omit `cursor` for\nthe first page; walk pages while `has_more` is true (default page\nsize 50, max 200). In practice the workspace is capped at 20\ntrunks, so every response fits in a single page.","tags":["sipTrunks"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The SIP trunks for the workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSIPTrunksResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create SIP Trunk","description":"Create a SIP trunk. For `provider=byoc` supply `sip_address` plus\noptional digest credentials and IP allowlist. For `provider=twilio`\nuse `ImportPhoneNumber` with a `twilio` spec instead - trunk\ncreation is handled automatically. Returns 422 when the workspace\nhas reached the 20-trunk cap.\n","tags":["sipTrunks"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created SIP trunk.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SIPTrunk"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSIPTrunkRequest"}}}}}},"/v1/agents/sip-trunks/{sip_trunk_id}":{"get":{"operationId":"get","summary":"Get SIP Trunk","description":"Retrieve a SIP trunk by ID.","tags":["sipTrunks"],"parameters":[{"name":"sip_trunk_id","in":"path","description":"SIP trunk id (prefixed external id, `trunk_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested SIP trunk.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SIPTrunk"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete","summary":"Delete SIP Trunk","description":"Delete a SIP trunk. This also removes the backing LiveKit inbound\ntrunk, outbound trunk, and dispatch rule if they were provisioned\nby us. Phone numbers attached to this trunk are left in place but\nbecome non-functional until rebound to a new trunk.\n","tags":["sipTrunks"],"parameters":[{"name":"sip_trunk_id","in":"path","description":"SIP trunk id (prefixed external id, `trunk_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"SIP trunk deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/callers":{"get":{"operationId":"list","summary":"List Callers","description":"List the workspace's callers, ordered by most-recently-seen first.\nA caller is the per-(tenant, agent, identity) entity that owns\nlong-term memories and conversation history.\n\n`project_id` matches the project the caller was FIRST SEEN under,\nfrozen at that moment. Moving an agent to another project therefore\nleaves the callers it already knows attributed where they were met.\n","tags":["callers"],"parameters":[{"name":"agent_id","in":"query","description":"Narrow the list to callers attached to one agent.","required":false,"schema":{"type":"string"}},{"name":"contact_id","in":"query","description":"Narrow the list to one person's rows: the contact's memory, agent by\nagent. Prefixed `contact_...` id.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Identity-prefix search. Filters to rows where `identity LIKE q + '%'`\n(`%`/`_` characters in the input are escaped as literals).\n","required":false,"schema":{"type":"string"}},{"name":"last_seen_after","in":"query","description":"RFC 3339 timestamp. Narrows to callers active strictly AFTER the\nsupplied moment. Useful for \"active this week / month\" filters.\n","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCallersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/callers/{caller_id}":{"get":{"operationId":"get","summary":"Get Caller","description":"Fetch a single caller by id. Returns 404 for soft-deleted or\nforeign-tenant rows \u2014 GDPR-purged callers appear as \"not found\"\nto the API.\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Caller"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Caller","description":"Update the customer-editable fields on a caller. PATCH semantics:\nomitted fields are unchanged, present fields overwrite. To clear\na nullable field (`display_name`, `external_ref`) pass an empty\nstring. `metadata` REPLACES the existing JSONB blob when supplied.\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The refreshed caller row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Caller"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCallerRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Caller (GDPR purge)","description":"Soft-delete the caller AND cascade soft-delete every memory row\npointing at it. Conversations survive (forensic / billing records)\nbut their caller pointer surfaces as \"deleted\" through the API.\n\nIdempotent \u2014 re-deleting an already-purged caller returns\n`{caller_purged: 0, memories_purged: 0}`. Audit row counts\naccompany every response so a privacy operator has direct\nevidence of the purge without re-querying.\n\nReturns `200` with the row-count body rather than a bodiless\n`204`: the counts are GDPR-purge audit evidence. This is a\ndeliberate exception to the delete-returns-204 convention,\nshared with the bulk memory purge\n(`DELETE /v1/agents/{agent_id}/memories`).\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Soft-delete completed; row counts in the body (GDPR audit evidence).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteCallerResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/callers/{caller_id}/conversations":{"get":{"operationId":"listConversations","summary":"List Conversations For Caller","description":"List one page of conversations belonging to the caller, newest\nstarted first. Same wire envelope as the workspace-wide\n`GET /v1/agents/conversations`, narrowed to one caller.\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCallerConversationsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/callers/{caller_id}/memories":{"get":{"operationId":"listMemories","summary":"List Memories For Caller","description":"List one page of memories belonging to the caller, newest first.\nSoft-deleted memories AND memories whose parent caller is\nsoft-deleted are hidden \u2014 the GDPR purge semantics require the\nAPI to behave as if those rows do not exist.\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCallerMemoriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/memories":{"get":{"operationId":"listMemories","summary":"List Agent Memories","description":"List per-caller memories extracted for an agent. Memories are\nwritten post-call by the built-in extractor when `memory_enabled`\nis true on the agent; the list is sorted newest-first.\nCursor-paginated: omit `cursor` for the first page; walk pages\nwhile `has_more` is true (default page size 50, max 200).","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Memories for the agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMemoriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/memories/{memory_id}":{"delete":{"operationId":"delete","summary":"Delete Memory","description":"Soft-delete one memory row.","tags":["memories"],"parameters":[{"name":"memory_id","in":"path","description":"User memory id (prefixed external id, `memory_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Memory deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/audio-assets":{"get":{"operationId":"list","summary":"List Audio Assets","description":"List every non-deleted audio asset in the caller's workspace.\nAudio assets are pre-recorded WAV clips (intro jingles, legal\ndisclaimers, hold cues) referenced from `play_audio` flow nodes\nand the corresponding system builtin. Cursor-paginated: omit\n`cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200).","tags":["audioAssets"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of audio assets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAudioAssetsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"upload","summary":"Upload Audio Asset","description":"Upload a new audio asset. The body is a multipart/form-data\nrequest with a single `file` field carrying the WAV bytes.\n\nThe WAV is validated server-side against a strict format\ncontract \u2014 PCM 16-bit signed, mono, 48000 Hz, \u226430s, \u22644 MiB \u2014\nbefore any bytes hit storage. The format matches what voice\nagents play back natively, so clips stream with no transcoding;\nconvert MP3 sources with `ffmpeg -i in.mp3 -ar 48000 -ac 1\n-sample_fmt s16 out.wav`.\n","tags":["audioAssets"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The uploaded asset's metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioAsset"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"file":{"type":"string","format":"binary","description":"The WAV file bytes. Must be PCM 16-bit signed, mono,\n48000 Hz, \u226430s duration, \u22644 MiB total.\n"}},"required":["file"]}}}}}},"/v1/agents/audio-assets/{audio_asset_id}":{"delete":{"operationId":"delete","summary":"Delete Audio Asset","description":"Soft-delete an audio asset. The underlying GCS object is\nretained so any flow node or tool still referencing the asset\nkeeps working until the config is updated; the agent runtime logs\nand skips on a missing row at session start (fail-soft).\n","tags":["audioAssets"],"parameters":[{"name":"audio_asset_id","in":"path","description":"Audio asset ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Audio asset soft-deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"get","summary":"Get Audio Asset","description":"Fetch one audio asset's metadata. Returns 404 for missing,\nsoft-deleted, or foreign-tenant assets \u2014 existence information\nis never leaked across tenants.\n","tags":["audioAssets"],"parameters":[{"name":"audio_asset_id","in":"path","description":"Audio asset ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The audio asset's metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioAsset"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/audio-assets/{audio_asset_id}/bytes":{"get":{"operationId":"getBytes","summary":"Get Audio Asset Bytes","description":"Stream the raw WAV bytes for an audio asset. Byte-stream\nsibling of the metadata endpoint at /v1/agents/audio-assets/{audio_asset_id}.\nThe agent runtime fetches through here for the play_audio\nbuiltin; SDK consumers can also download originals. Returns 404\nfor missing / soft-deleted / foreign-tenant assets.\n","tags":["audioAssets"],"parameters":[{"name":"audio_asset_id","in":"path","description":"Audio asset ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The raw audio bytes (PCM 16-bit signed, mono, 48 kHz, WAV-wrapped).","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}}}}},"/v1/agents/teams":{"get":{"operationId":"list","summary":"List Teams","description":"List the teams in the caller's workspace, most recently updated\nfirst. Cursor-paginated: omit `cursor` to fetch the first page.\nDefault page size is 50 and max is 200. Walk pages while `has_more`\nis true.","tags":["teams"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of teams.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTeamsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Team","description":"Compose a team: a `manager_agent_id` that orchestrates plus the\n`members` it delegates to. The manager and every member must be an\nagent in the caller's workspace. Idempotent via `Idempotency-Key`.\n","tags":["teams"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTeamRequest"}}}}}},"/v1/agents/teams/{team_id}":{"get":{"operationId":"get","summary":"Get Team","description":"Retrieve a team by id.","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Team","description":"Update a team's name, description, manager, and/or members. Merge-patch\nsemantics: an omitted field is left unchanged, `manager_agent_id: null`\nclears the manager, and a `members` array replaces the whole roster.\n","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTeamRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Team","description":"Delete a team. Removes the saved composition.","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Team deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/teams/{team_id}/runs":{"post":{"operationId":"run","summary":"Run Team","description":"**Run a team**: start a durable async run of the team's manager agent,\nwhich pursues the given instruction and delegates sub-goals to the team\nmembers (each executing as its own bounded child run). Returns a run\nhandle immediately (`202`, status `queued`); poll it with `getRun` or\nwatch its step timeline - it is the same durable run primitive as\n`createRun`, so the returned handle addresses the manager run.\n\nThe team must have a manager assigned; a draft team without one is a\n`400`. Idempotent via `Idempotency-Key`: a retry replays the first run.\n\nEvery admission rule `createRun` describes applies here, on the manager\nagent: the publish gate (`422 agent_publish_gate_required` until the\nmanager's current configuration is published), the MCP transport check\n(`422 tool_transport_unsupported`), the 200-run workspace backlog\nceiling and the manager's project ceiling\n(`429 concurrency_limit_reached` - and a team run's children count\ntowards both as they start), and the spend gate, including the\nmanager's project (`402 project_spend_limit_exceeded`,\n`409 project_archived`).\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nKeys inside `variables` and `metadata`, and the contents of\n`output_schema`, are your own data rather than field names, and are\nnever refused.\n","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The team run was accepted and queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTeamRunRequest"}}}}},"get":{"operationId":"listRuns","summary":"List Team Runs","description":"**List a team's runs**, newest first: the team's run history. Only the\nteam's ROOT runs are returned - the ones started with `runTeam`. Each\nmember's delegated child run is attributed to the member agent that ran\nit; read a run's children with `listRunChildren`.\n\nA run started directly against the team's manager agent (`createRun`) is\nNOT a team run and never appears here.\n","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The team's runs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/triggers":{"get":{"operationId":"list","summary":"List Agent Triggers","description":"List a task agent's triggers, most recently updated first.\nCursor-paginated: omit `cursor` to fetch the first page. Default page\nsize is 50 and max is 200. Walk pages while `has_more` is true.","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of triggers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentTriggersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Agent Trigger","description":"Create a trigger that starts a durable run of a task agent. A\n`schedule` trigger fires on a cron expression or an interval; a\n`webhook` trigger returns a one-time `secret` plus a `fire_path` your\nbackend POSTs to. Idempotent via `Idempotency-Key`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nKeys inside `run.variables` and `run.metadata`, and the contents of\n`run.output_schema`, are your own data rather than field names, and\nare never refused.\n","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created trigger. For a webhook trigger the plaintext `secret`\nis present in this response only.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTrigger"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentTriggerRequest"}}}}}},"/v1/agents/{agent_id}/triggers/{trigger_id}":{"get":{"operationId":"get","summary":"Get Agent Trigger","description":"Retrieve a trigger by id.","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"trigger_id","in":"path","description":"Trigger id (prefixed external id, `trigger_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested trigger.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTrigger"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Agent Trigger","description":"Update a trigger. Merge-patch semantics: an omitted field is left\nunchanged. `type` is immutable. Changing a schedule's cadence or\n`enabled` reschedules its next fire.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nKeys inside `run.variables` and `run.metadata`, and the contents of\n`run.output_schema`, are your own data rather than field names, and\nare never refused.\n","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"trigger_id","in":"path","description":"Trigger id (prefixed external id, `trigger_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated trigger.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTrigger"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentTriggerRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Agent Trigger","description":"Delete a trigger. A schedule stops firing; a webhook fire URL stops accepting.","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"trigger_id","in":"path","description":"Trigger id (prefixed external id, `trigger_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Trigger deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent-triggers/{trigger_id}/fire":{"post":{"operationId":"fire","summary":"Fire Agent Trigger","description":"Inbound fire URL for a webhook trigger. Authenticate with the trigger's\none-time fire token as `Authorization: Bearer `. The JSON body\nbecomes the started run's payload variable. Supply an `Idempotency-Key`\n(or `Speechify-Delivery-Id`) header to make a redelivery replay the\noriginal run instead of starting a second one.\n","tags":["triggers"],"parameters":[{"name":"trigger_id","in":"path","description":"Trigger id (prefixed external id, `trigger_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"202":{"description":"The fire was accepted and a run was started (or replayed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTriggerFireResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"description":"An arbitrary JSON payload, passed to the run as its payload variable.","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"description":"Any type"}}}}}}},"/v1/skills":{"get":{"operationId":"list","summary":"List Skills","description":"List the skills in the caller's workspace, most recently updated first.\nA skill is a versioned procedure - an instruction block plus the tools\nit needs - that you write once and attach to many agents. Each item\ncarries the body of its latest version.\nCursor-paginated; walk pages while `has_more` is true.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSkillsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Skill","description":"Create a skill at version 1. Names are unique per workspace. Every\n`tool_ids` and `knowledge_base_ids` entry must already exist in the\nsame project as the skill.\nBounded by the workspace's skill limit (409 `skill_limit_reached`).\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created skill.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Skill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSkillRequest"}}}}}},"/v1/skills/{skill_id}":{"get":{"operationId":"get","summary":"Get Skill","description":"Retrieve one skill at its latest version, or at the version named by\n`?version=`. An unknown version is a 404 `skill_version_not_found`\nrather than a fall back to the latest - a caller asking for one body\nis never handed a different one.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"version","in":"query","description":"Read this version instead of the latest.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Skill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Skill","description":"Update a skill. The two halves behave differently on purpose:\n`name` and `description` are labels and edit in place, while\n`instructions`, `tool_ids`, `knowledge_base_ids` and `variables` are\nthe body - touching any of them MINTS A NEW VERSION and leaves every\nexisting attachment on the version it pinned. Agents move when someone\nmoves them, never because the catalog changed.\nA present list replaces rather than appends.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The skill, at the version this call left it on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Skill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSkillRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Skill","description":"Delete a skill. Refused with 409 `skill_in_use` while any agent still\nhas it attached: deleting it would change every one of their prompts at\nonce, which is what the version pin exists to prevent. Detach it first.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/skills/{skill_id}/versions":{"get":{"operationId":"list_versions","summary":"List Skill Versions","description":"List a skill's version history, newest first. Each entry summarises its\nbody rather than returning it; read a full body with\n`GET /v1/skills/{skill_id}?version=`. Versions are minted by updating\nthe skill, never created directly.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSkillVersionsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/skills":{"get":{"operationId":"list_skills","summary":"List Agent Skills","description":"List the skills attached to this agent, oldest attachment first - the\norder dispatch renders them in. Each carries the version it is pinned to\nand the version the catalog currently holds; when the two differ an\nupgrade is available, and nothing upgrades on its own.\nCursor-paginated; walk pages while `has_more` is true.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["agents"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentSkillsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/skills/{skill_id}":{"put":{"operationId":"attach_skill","summary":"Attach Skill To Agent","description":"Attach a skill to this agent, pinned to one version. Omit `version` to\npin whatever is latest AT THIS MOMENT - the pin is a number, never a\nlive pointer, so a later edit to the skill does not reach this agent.\nIdempotent: attaching again re-pins.\n\nThree refusals worth knowing. The agent and the skill must be in the\nsame project (409 `cross_project_reference`). The skill's tools must\nnot collide by name with anything the agent already answers to\n(409 `skill_tool_name_conflict`) - the model picks a tool by name.\nAnd any knowledge base the skill names must already be attached to the\nagent (409), because retrieval re-authorizes against the agent's own\nattachments and would refuse a skill-contributed base mid-call.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["agents"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The attachment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSkill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachSkillRequest"}}}}},"patch":{"operationId":"update_skill","summary":"Update Agent Skill","description":"Move this agent's pin to another version, or switch the skill off\nwithout detaching it (the pin survives, so switching it back on\nrestores the same body). Moving the pin re-runs the tool-name and\nknowledge checks against the version being moved to.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["agents"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The attachment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSkill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentSkillRequest"}}}}},"delete":{"operationId":"detach_skill","summary":"Detach Skill From Agent","description":"Detach a skill from this agent. The skill itself survives, as do its\nattachments to other agents.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["agents"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Detached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores":{"get":{"operationId":"list","summary":"List Stores","description":"List the stores in the caller's workspace, most recently updated\nfirst. A store is the durable document + asset store a durable run\nwrites to (through its store tools) and a hosted-API route serves.\nCursor-paginated; walk pages while `has_more` is true.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListStoresResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Store","description":"Create a store. Names are unique per workspace among live stores.\nBounded by the workspace's store limit (409 `store_limit_reached`).\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStoreRequest"}}}}}},"/v1/stores/{store_id}":{"get":{"operationId":"get","summary":"Get Store","description":"Retrieve one store.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Store","description":"Rename or re-describe a store (merge-patch: absent fields are unchanged).\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStoreRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Store","description":"Delete a store and everything in it. The store disappears from every\nread at once; its objects are purged durably afterwards.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores/{store_id}/collections":{"get":{"operationId":"collections","summary":"Get Collections","description":"The collections a store holds with their live document counts: a\nbounded roll-up derived from the documents, not a resource list.\nCollections are implicit - writing a document names one.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListStoreCollectionsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores/{store_id}/collections/{collection}/documents":{"get":{"operationId":"listDocuments","summary":"List Documents","description":"List a collection's documents, most recently updated first, bodies\nincluded unless `include_data=false`. For filters and ordering use\n`queryDocuments`.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_data","in":"query","description":"Set `false` to return index rows only (no `data`).","required":false,"schema":{"type":"boolean"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryStoreDocumentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createDocument","summary":"Create Document","description":"Write a document, minting an id when none is given. Prefer `putDocument`\nwith a stable id you derive from the content, so a retry never\nduplicates. Bounded by the store's document limit (409\n`store_document_limit_reached`).\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The written document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteStoreDocumentRequest"}}}}}},"/v1/stores/{store_id}/collections/{collection}/documents/query":{"post":{"operationId":"queryDocuments","summary":"Query Documents","description":"Query a collection: filter on top-level scalar fields with `where`,\norder on one with `order_by`, page with `limit` + `cursor`. Values\ncompare with JSON semantics (numbers as numbers, strings lexically, so\nISO-8601 timestamps order as dates). A read carrying a body, hence a\nPOST on a literal sub-path; `query` and `batch` are reserved document ids.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryStoreDocumentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocumentQuery"}}}}}},"/v1/stores/{store_id}/collections/{collection}/documents/batch":{"post":{"operationId":"batchDocuments","summary":"Batch Write Documents","description":"Apply up to 50 writes (`set`, `update`, `delete`) in order. Not atomic\nacross writes: it stops at the first failure, whose index the error\nnames, and reports every write it applied.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreBatchResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreBatchRequest"}}}}}},"/v1/stores/{store_id}/collections/{collection}/documents/{document_id}":{"get":{"operationId":"getDocument","summary":"Get Document","description":"Read one document with its body.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Document id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"putDocument","summary":"Put Document","description":"Write (create or replace) a document at this id. 201 for a new\ndocument, 200 for a new version of an existing one. `data` must be a\nJSON object of at most 256 KiB; its top-level scalar fields become the\nqueryable projection.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Document id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A new version of the existing document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteStoreDocumentRequest"}}}}},"patch":{"operationId":"updateDocument","summary":"Update Document","description":"Shallow-merge `data` into the stored document (a JSON null removes a\nfield) and write the result as a new version.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Document id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteStoreDocumentRequest"}}}}},"delete":{"operationId":"deleteDocument","summary":"Delete Document","description":"Delete one document.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Document id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores/{store_id}/assets":{"get":{"operationId":"listAssets","summary":"List Assets","description":"List a store's assets, newest first.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListStoreAssetsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"uploadAsset","summary":"Upload Asset","description":"Upload one file (multipart form field `file`, at most 25 MiB). The\npart's content type is stored and served back from `bytes_path`.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The stored asset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreAsset"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The file to store (at most 25 MiB)."}},"required":["file"]}}}}}},"/v1/stores/{store_id}/assets/{asset_id}":{"get":{"operationId":"getAsset","summary":"Get Asset","description":"Read an asset's metadata.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"asset_id","in":"path","description":"Asset id (prefixed external id, `asset_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreAsset"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteAsset","summary":"Delete Asset","description":"Delete an asset.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"asset_id","in":"path","description":"Asset id (prefixed external id, `asset_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores/{store_id}/assets/{asset_id}/bytes":{"get":{"operationId":"getAssetBytes","summary":"Get Asset Bytes","description":"Stream the asset's bytes with its stored content type. Private and uncacheable.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"asset_id","in":"path","description":"Asset id (prefixed external id, `asset_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The asset bytes.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/stores":{"get":{"operationId":"listAttached","summary":"List Agent Stores","description":"List the stores attached to an agent, in attach order. A durable run\nof the agent is offered the store tools for exactly these.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentStoresResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/stores/{store_id}":{"post":{"operationId":"attach","summary":"Attach Store","description":"Attach a store to an agent (idempotent). The agent and the store must\nshare a project (409 `cross_project_reference` otherwise).\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Attached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"detach","summary":"Detach Store","description":"Detach a store from an agent.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Detached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apis":{"get":{"operationId":"list","summary":"List Hosted APIs","description":"List the hosted APIs in the caller's workspace, most recently updated\nfirst. A hosted API is the API you assemble: a slug that becomes\n`https://.`, the routes it answers, and the\nconsumer keys your own callers present.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListHostedAPIsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Hosted API","description":"Create a hosted API. The slug is a DNS label, globally unique on the\nshared domain (409 `hosted_api_slug_taken`) and immutable afterwards.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPI"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHostedAPIRequest"}}}}}},"/v1/apis/{api_id}":{"get":{"operationId":"get","summary":"Get Hosted API","description":"Retrieve one hosted API.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPI"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Hosted API","description":"Update a hosted API (merge-patch). Switching to `public` is refused\nwhile a `run` route exists: an anonymous caller must not start runs\nthat spend the workspace's budget.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPI"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHostedAPIRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Hosted API","description":"Delete a hosted API; its host stops answering at once.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apis/{api_id}/openapi":{"get":{"operationId":"getOpenApi","summary":"Get Hosted API OpenAPI Document","description":"The OpenAPI 3.1 document describing the hosted API's routes - the same\ndocument the API serves to its consumers at `/openapi.json`.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The OpenAPI document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"description":"Any type"}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apis/{api_id}/routes":{"get":{"operationId":"listRoutes","summary":"List Routes","description":"List a hosted API's routes in creation order.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListHostedAPIRoutesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createRoute","summary":"Create Route","description":"Add a route: a method + path answered by a resolver. `store_query` and\n`store_document` serve a store; `run_latest` serves the newest\nstructured output of a schedule trigger's runs; `run` starts a run\nthrough a webhook trigger per request (POST only, never on a public\nAPI) and waits up to `wait_seconds` before answering 202 with a\nhandle to poll at `/_runs/{run_id}`. Where-clause values and the\ndocument id may be `{{query.x}}`, `{{path.x}}` or `{{body.x}}`\ntemplates bound from the consumer's request; a clause whose template\nis absent is skipped.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created route.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIRoute"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHostedAPIRouteRequest"}}}}}},"/v1/apis/{api_id}/routes/{route_id}":{"get":{"operationId":"getRoute","summary":"Get Route","description":"Retrieve one route.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"route_id","in":"path","description":"Route id (prefixed external id, `route_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIRoute"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateRoute","summary":"Update Route","description":"Update a route (merge-patch); a changed method or resolver is re-validated.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"route_id","in":"path","description":"Route id (prefixed external id, `route_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIRoute"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHostedAPIRouteRequest"}}}}},"delete":{"operationId":"deleteRoute","summary":"Delete Route","description":"Delete a route.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"route_id","in":"path","description":"Route id (prefixed external id, `route_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apis/{api_id}/keys":{"get":{"operationId":"listKeys","summary":"List Consumer Keys","description":"List a hosted API's consumer keys, newest first, revoked ones included.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["keys"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListHostedAPIKeysResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createKey","summary":"Create Consumer Key","description":"Mint a consumer key (`ck_...`) for the API's own callers. The plaintext\n`secret` is present in this response only; every later read shows the\nmasked `key_hint`.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["keys"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created key, with its one-time secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIKey"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHostedAPIKeyRequest"}}}}}},"/v1/apis/{api_id}/keys/{consumer_key_id}/revoke":{"post":{"operationId":"revokeKey","summary":"Revoke Consumer Key","description":"Revoke a consumer key; idempotent. Requests carrying it are refused from now on.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["keys"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"consumer_key_id","in":"path","description":"Consumer key id (prefixed external id, `ckey_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIKey"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts":{"get":{"operationId":"list","summary":"List Contacts","description":"List the workspace's contacts, most-recently-seen first, each with its\nlive identifier set. Cursor-paginated: omit `cursor` for the first\npage; walk pages while `has_more` is true (default page size 50, max\n200).\n\nContacts are workspace-level, not per-agent: the same person reached\non two agents and on two channels is one row here, while\n`/v1/agents/callers` stays the per-agent observation of them. Filter\nby `agent_id` for the people one agent has reached; each row names\nthe agents that reached it in `agent_ids`.\n","tags":["contacts"],"parameters":[{"name":"agent_id","in":"query","description":"Narrow to the people this agent has reached: contacts with a\nper-agent caller row on it. Prefixed `agent_...` id.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Prefix search across the display name AND the identifier values, so\nthe same box finds \"Sarah\" and \"+1202555\". `%`/`_` in the input are\nescaped as literals.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"One page of the workspace's contacts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListContactsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/{contact_id}":{"get":{"operationId":"get","summary":"Get Contact","description":"Fetch one contact by id, scoped to the caller's workspace, with its\nlive identifiers. Returns 404 for missing, merged-away, or\nforeign-workspace contacts - contact existence is never leaked across\nworkspaces.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Contact","description":"Rename a contact or replace its metadata. Omitted fields are\nunchanged; an empty `display_name` clears the column; `metadata`\nreplaces the stored blob in full.\n\nIdentifiers are NOT editable here - each one is an assertion of record\nwith its own provenance, so they are attached and detached through\ntheir own sub-resource.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Contact (GDPR purge)","description":"Erase a person: the contact, every identifier that resolves to them,\nevery per-agent caller record of them, and every memory ever extracted\nabout them - on every agent and every channel, in one call.\n\nThis is the right-to-be-forgotten path. `DELETE\n/v1/agents/callers/{caller_id}` erases one agent's record of someone\nand has to be fired once per agent and once per channel; this reaches\nall of them, because \"everything about this person\" is a question the\ncontact model can answer.\n\nTheir calls, chats and durable runs are scrubbed rather than deleted,\nthe same way and for the same reason: the row keeps its id, status,\ntimings and usage - the accounting the workspace was billed on, which\nis not the person's to erase - and loses everything that describes\nthem. For a call that means the transcript, the identity, the phone\nnumbers, the frozen prompt and variables; for a run, the instruction,\nthe variables, the output and every journalled step. The facts\nextracted from either, and the copies frozen into past prompts, go\nwith them.\n\nThe transcript and the recording of a call both stop being readable\nfrom the moment of the request, and a call still in progress can no\nlonger append to its transcript, so a turn that lands after the\nerasure cannot put the person back.\n\nBoth are destroyed 30 days later, not immediately. This is\ndeliberate: an erasure that destroyed them on request would be a way\nto erase evidence of what happened on the call, so they stop being\nreachable at once and stay recoverable by us for the same 30 days a\ndeleted agent or a purged project does. Absent an erasure, a\nrecording is kept for one year from the call and removed after that.\n\n`conversations_erased` and `messages_erased` count what stopped being\nreadable, which is what the request asked for; the rows behind them\nare removed on the schedule above.\n\nThe identifiers are tombstoned as part of the purge, which is what\nstops the person's phone number ever resolving again: a later merge\nnaming the purged contact moves rows that are already dead rather than\nbringing a link back to life.\n\nIdempotent: re-deleting an already-purged contact returns all zeros\nrather than a 404, so a client that never saw the first response can\nsafely repeat it.\n\nReturns `200` with the row counts rather than a bodiless `204`: the\ncounts are the audit evidence a privacy operator needs, and re-reading\na corpus you just asked us to erase is not a way to obtain them. Same\ndeliberate exception `DELETE /v1/agents/callers/{caller_id}` takes.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Purge completed; row counts in the body (GDPR audit evidence).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteContactResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/{contact_id}/identifiers":{"post":{"operationId":"attachIdentifier","summary":"Attach Contact Identifier","description":"Assert that a typed identifier denotes this person - the mechanism by\nwhich a phone number becomes linked to the application user it belongs\nto, so a call from that number resolves to everything the agent\nalready knows.\n\nThe server stamps `asserted_by: api` and `verified_at` because the\nrequest authenticated: your backend identified the end user before\ntelling us. Neither can be supplied by the client.\n\nAttaching an identifier another contact already holds is refused with\n`409 contact_identifier_conflict` naming the holder - never a silent\nre-point, because that would be a merge nobody asked for, and a wrong\nmerge discloses one person's facts to another. Re-attaching an\nidentifier this same contact already holds succeeds unchanged.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The attached identifier.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactIdentifier"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachContactIdentifierRequest"}}}}}},"/v1/contacts/{contact_id}/identifiers/{identifier_id}":{"delete":{"operationId":"detachIdentifier","summary":"Detach Contact Identifier","description":"Reverse a link. A wrong link is a disclosure, so undoing one is a\nfirst-class operation rather than a support ticket: the identifier\nstops resolving to this contact immediately, and a future session on\nthat channel mints a fresh contact instead.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"identifier_id","in":"path","description":"Contact identifier id (prefixed external id, `ident_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Identifier detached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/{contact_id}/merge":{"post":{"operationId":"merge","summary":"Merge Contacts","description":"Fold one contact into another: every identifier, caller projection,\nconversation and memory that pointed at `from_contact_id` is\nre-pointed at the contact in the URL, the survivor's first/last-seen\nwindow widens to span both, and the merged-away contact is\ntombstoned.\n\nThis is the deliberate counterpart to the refusal on identifier\nattach. Merging is destructive and one-way, so it is never inferred -\nthe customer is the only party who knows two records are one person.\n\nAll-or-nothing, and safe to retry: a repeat call finds nothing left\npointing at the merged-away contact and returns the same survivor with\na zeroed tally. Send an `Idempotency-Key` header to have a retry\nreplay the first response verbatim.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"The SURVIVING contact's id (prefixed external id, `contact_...`).\nEverything from the contact named in the body lands here.\n","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"The surviving contact plus the row counts that moved onto it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeContactsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeContactsRequest"}}}}}},"/v1/workspaces/current/entitlements":{"get":{"operationId":"getEntitlements","summary":"Get Workspace Entitlements","description":"The per-tier entitlements catalog plus the caller's RESOLVED entitlements\nfor the current workspace (tier defaults composed with any per-tenant\noverride). Readable with an API key as well as a console session: it is\nhow an integration learns what it may use before a feature endpoint\nanswers `402`. Branch on `current.durable_runs_access`,\n`current.text_agent_access`, `current.hosted_apis_access` and\n`current.skills_access`, and size a run from `current.max_run_turns`.\nThe console renders quota affordances and upgrade-card limits from the\nsame single server-authoritative source instead of a hardcoded mirror.\n","tags":["workspaces"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The entitlements catalog + the current workspace's resolved entitlements.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntitlementsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/current/contact-resolver":{"get":{"operationId":"getResolver","summary":"Get Contact Resolver","description":"Read the workspace's contact-resolver configuration - the endpoint\nSpeechify asks \"who is on this call\" at session start.\n\nThe signing secret is never returned here. It is revealed once, by the\ncall that mints it, and again by a rotation.\n","tags":["contacts"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The configured resolver.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResolver"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"setResolver","summary":"Set Contact Resolver","description":"Configure the endpoint Speechify asks to identify an inbound caller.\n\nOn an inbound call from a number nobody has linked yet, Speechify\nPOSTs `{channel, identifier, agent_id, conversation_id}` to this URL,\nsigned with the same `Speechify-Signature` scheme as webhooks, and\nexpects `{external_id, display_name?, variables?}` back. The contact\nattaches before the first token is generated, so `{{memory}}` is\npopulated on the agent's opening line.\n\nThe call FAILS OPEN on a sub-second deadline. A slow, unreachable or\nmalformed response degrades to a call with no contact and no memory\nblock - never a failed call and never a delayed answer. Answers are\ncached briefly per (workspace, identifier kind, identifier value), so a\nrepeat caller does not re-hit your backend; they are scoped to the\nconfiguration that produced them, so changing the URL takes effect at\nonce rather than after the cache window.\n\nOne resolver per workspace: this endpoint sets it, and re-sending the\nsame body is a no-op. Configuring for the first time mints a signing\nsecret and returns it once; later edits keep the secret you already\ndeployed a verifier against and return it blank.\n\nThe URL must be `https` and must resolve to a public address. A\nloopback, private-range or cloud-metadata host is rejected here, and\nblocked again at dial time.\n","tags":["contacts"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The stored configuration. `secret` is present only when this call\ncreated the configuration; store it, it is not shown again.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResolver"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutContactResolverRequest"}}}}},"delete":{"operationId":"deleteResolver","summary":"Delete Contact Resolver","description":"Remove the workspace's resolver. Calls stop asking at the next\ndispatch and behave exactly as they did before it was configured;\ncontacts already linked by earlier answers are untouched.\n","tags":["contacts"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/current/contact-resolver/rotate-secret":{"post":{"operationId":"rotateResolverSecret","summary":"Rotate Contact Resolver Secret","description":"Mint a new signing secret for the workspace's resolver and return it\nonce. The previous secret stops signing immediately, so deploy the new\none to your verifier before rotating - a request your endpoint cannot\nverify should be rejected, and a rejected resolver call degrades to a\ncall with no contact.\n","tags":["contacts"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The configuration carrying the new secret (shown once).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResolver"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/endpoints":{"get":{"operationId":"list","summary":"List webhook endpoints","description":"The caller's workspace's registered webhook endpoints. Cursor-paginated:\nomit `cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200). The signing `secret` is never returned\nhere \u2014 it is shown only when an endpoint is created or its secret is\nrotated. Filter by delivery scope with `project_id`.","tags":["endpoints"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter endpoints by project scope: omit for everything the caller\nmay see, pass the literal `shared` for workspace-wide endpoints\nonly, or a `proj_...` id for endpoints scoped to that project.\nEndpoints have no Default project - a null `project_id` means\nworkspace-wide, so the literal here is `shared`, never `default`.\nReturns 404 project_not_found for a malformed id and for any\nproject outside your reach: a project-pinned API key or\nservice-account key reaches only its pinned project, and a member\nholding project grants reaches only the granted projects. That 404\nis the same in every case and does not reveal whether such a\nproject exists - outside your reach a project is answered as\nnonexistent, never as forbidden. `shared` is always inside your\nreach. Inside it, a well-formed id that matches nothing yields an\nempty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The workspace's webhook endpoints.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhookEndpointsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create webhook endpoint","description":"Register a webhook endpoint. Speechify mints an HMAC signing secret\nand returns it in the response `secret` field \u2014 exactly once. Store it\nthen; subsequent reads omit it (rotate it with the rotate-secret action\nif lost). Select events via `enabled_events`: a list of catalog event\nnames or `[\"*\"]` for every event. Optionally scope delivery to one\nproject with `project_id`; omit it for a workspace-wide endpoint that\nreceives every project's events. Limited to 50 endpoints per workspace.\n","tags":["endpoints"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created endpoint, including its one-time signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookEndpointRequest"}}}}}},"/v1/webhooks/endpoints/{webhook_endpoint_id}":{"get":{"operationId":"get","summary":"Get webhook endpoint","description":"Fetch a webhook endpoint by id. The signing secret is never returned.","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The webhook endpoint (without the signing secret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update webhook endpoint","description":"Partial update; omitted fields are left unchanged. Set `disabled` to pause delivery without deleting the endpoint.","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated endpoint (without the signing secret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookEndpointRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete webhook endpoint","description":"Delete a webhook endpoint. In-flight deliveries stop; returns 204 on success.","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Endpoint deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/endpoints/{webhook_endpoint_id}/rotate-secret":{"post":{"operationId":"rotateSecret","summary":"Rotate webhook endpoint secret","description":"Mint a new HMAC signing secret for the endpoint and return it in the\nresponse `secret` field (shown exactly once). The previous secret stops\nsigning immediately, so accept both during your cutover window.\n","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint, including its new one-time signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/endpoints/{webhook_endpoint_id}/deliveries":{"get":{"operationId":"listDeliveries","summary":"List webhook endpoint deliveries","description":"Delivery attempts for one webhook endpoint, newest first. One row per\n(endpoint, event, resource), updated in place across retries. Each row\nincludes the exact request payload and signed headers Speechify sent\n(`request_body`, `request_headers`) and the response your server returned\n(`last_status_code`, `last_response_body`, `last_response_headers`), so\nyou can verify the signature and debug failures. Cursor-paginated: omit\n`cursor` for the first page; walk pages while `has_more` is true (default\npage size 50, max 200).\n\nAn endpoint that was not a target of an event has no row for it: a\nproject-scoped endpoint records nothing for another project's events.\nAn empty list therefore means either nothing matched or nothing\nhappened. To tell them apart, list the project's own activity first\n(`GET /v1/agents/runs?project_id=\u2026`, the conversations list): activity\nthere with no delivery here is a defect to report; none there means\nthere was nothing to deliver.","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint's delivery attempts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhookEndpointDeliveriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects":{"get":{"operationId":"list","summary":"List Projects","description":"List the workspace's projects, newest first. The implicit Default\nproject is not a row and is never listed; resources with no\n`project_id` live in it. Archived projects are hidden unless\n`include_archived=true`, and purged ones unless\n`include_purged=true`. Cursor-paginated: omit `cursor` for the\nfirst page; walk pages while `has_more` is true (default page size\n50, max 200).\n","tags":["projects"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_archived","in":"query","description":"Include archived projects. Defaults to `false`, so the list shows\nonly live projects; archived ones stay readable by id.\n","required":false,"schema":{"type":"boolean","default":false}},{"name":"include_purged","in":"query","description":"Include purged projects that are still inside their 30-day restore\nwindow, each carrying the `purged_at` stamp its deadline is\nmeasured from. Defaults to `false`, so the list shows only projects\nthat still exist. This is the only read that returns a purged\nproject: the by-id read answers 404 for one, and a project past its\nwindow is never listed, because it is awaiting permanent deletion\nand a restore would refuse it. Independent of `include_archived`:\na purge runs only from the archived state, so asking for purged\nprojects never means asking for every archived one as well.\n","required":false,"schema":{"type":"boolean","default":false}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The workspace's projects.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListProjectsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Project","description":"Create a project in the caller's workspace. Names are unique per\nworkspace (case-insensitive). A workspace holds at most 100 live\nprojects; at the cap the create refuses with\n`409 project_limit_reached` until one is deleted.\n","tags":["projects"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProjectRequest"}}}}}},"/v1/projects/{project_id}":{"get":{"operationId":"get","summary":"Get Project","description":"Fetch one project by id, scoped to the caller's workspace. Returns\n404 for missing or foreign-workspace projects \u2014 project existence\nis never leaked across workspaces.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Project","description":"Edit a project in place - its name, its monthly spend limit, or its\ncapacity ceilings - keeping the same id so every grouped resource\nfollows the edit with no re-pointing. Names are unique per\nworkspace (case-insensitive). The limit fields require\n`billing.manage`; a capacity ceiling above the workspace's own is\nrefused, since it could never apply.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProjectRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Project","description":"Delete a project in one of two modes.\n\n**Detach** (the default, no body or `mode: detach`): only the\ngrouping row is removed; every resource in the project moves to the\nimplicit Default project. Refused with 409\n`project_has_scoped_credentials` while an API key, service account,\nvault credential, webhook endpoint, member grant or pending invite is\nscoped to the project, because detaching any of those would silently\nwiden it.\n\n**Purge** (`mode: purge` with `confirm` equal to the project's name):\navailable only on an ARCHIVED project, because a teardown needs a\nstate you can sit in and reverse first; a live project is refused with\nthe coded `409 project_not_archived`. Archive the project, confirm it\nis the one you mean, then purge. The project is removed WITH its\ncontents in one transaction. Agents\n(with their tests), knowledge bases (with their documents), tools,\naudio assets, scoped webhook endpoints and scoped vault credentials\nare deleted; API keys and service accounts pinned to the project are\nrevoked; member grants and pending-invite scopes on the project are\ncleared. The runs and conversations of a deleted agent go with that\nagent, transcripts and recordings included, and stop being readable\nthe moment the purge lands - a detach leaves them alone, because a\ndetached project's agents are still live. Callers, suite runs and\nmemories are operational records and survive either mode, moving to\nthe Default project; a batch call survives the project the same way,\nbut not the agent it dialled for, so a purge's agents take their\ncampaigns with them once they are permanently removed. Refused with 409 while a phone number is attached\n(release or move it first), while a member's only project grant is\nthis one, or while a live invite carries only this project (clearing\neither would widen that person to the whole workspace, the invite one\nacceptance earlier). Preview either mode with\n`GET /v1/projects/{project_id}/teardown`.\n\n**A purge is recoverable for 30 days.** The project disappears from\nevery list and read immediately, and its name is freed for reuse, but\nthe project and its resources are kept and permanently deleted only\nonce the window closes. `POST /v1/projects/{project_id}/restore`\nbrings the project and its resources back inside that window; the\ncredentials the purge revoked and the grants it cleared stay that way.\n\nThe 409 carries the blockers under `error.details.blockers` (`kind`,\ntyped `id`, `name`, and the `blocks` modes each refuses), their total\nunder `error.details.blocker_count`, and, for existing clients, the\nsame rows under `error.details.credentials`. The lists are capped at\n50 rows; the counts are not, and the refusal is decided on the count.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Project deleted. In detach mode its resources moved to the Default\nproject; in purge mode they went with it.\n","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteProjectRequest"}}}}}},"/v1/projects/{project_id}/archive":{"post":{"operationId":"archive","summary":"Archive Project","description":"Archive a project. From then on nothing new starts or bills inside\nit: conversation and widget sessions, outbound and inbound calls,\nbatch calls, test runs, knowledge-base imports, and synthesis or\ngateway requests on a credential pinned to the project are all\nrefused with the coded `409 project_archived`. Work already in\nflight is left to finish. Everything in the project stays readable\nand its configuration stays editable, and the project still answers\nby id.\n\nIdempotent: archiving an archived project is a no-op. Reverse it\nwith the unarchive operation.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The project is archived.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/unarchive":{"post":{"operationId":"unarchive","summary":"Unarchive Project","description":"Lift a project's archive so work and spend resume inside it.\nIdempotent: unarchiving a live project is a no-op.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The project is live.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/restore":{"post":{"operationId":"restore","summary":"Restore Project","description":"Walk back a purge. A purged project is recoverable for 30 days: its\nrow and its contents are kept, hidden from every list and read, and\npermanently deleted only once the window expires.\n\n**What comes back:** the project and its resources - agents (with\ntheir tests), knowledge bases, tools and audio assets - exactly the\nrows the purge removed. A resource you had deleted yourself before the\npurge stays deleted.\n\n**What does NOT come back, on purpose:** every credential the purge\nrevoked stays revoked, and every grant it cleared stays cleared. API\nkeys and service accounts pinned to the project are not re-issued,\nvault credentials and webhook endpoints scoped to it are not\nundeleted, and member grants and pending-invite scopes are not\nrestored. Bringing a credential or a grant back would re-grant access\nsomebody deliberately ended, so the restore reports them under\n`still_revoked` instead. Re-create the credentials and re-grant the\nmembers the project still needs.\n\nThe project returns ARCHIVED, the state it was purged from, so nothing\ndispatches or bills inside it until you unarchive it.\n\nRefused with `409 project_not_purged` when the project was never\npurged, `409 project_restore_window_expired` once the 30 days have\npassed, and `409 project_name_taken` when another project has taken\nthis one's name since the purge (a purge frees the name immediately -\nrename the project holding it, then restore).\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The project is restored, archived, and holds its resources again.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectRestore"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/teardown":{"get":{"operationId":"teardown","summary":"Project Teardown Plan","description":"What deleting this project would do, in either mode, without doing\nit: whether a purge is available right now (`archived` and\n`purge_available`), every row holding the project open and which\ndelete modes it refuses, what a purge would remove (a count per kind\nand a bounded sample of names), and the operational records a purge\nkeeps. Nothing is mutated. Takes the same permission as the delete.\nReturns 404 for missing or foreign-workspace projects.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The teardown plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTeardown"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/stats":{"get":{"operationId":"stats","summary":"Project Stats","description":"What this project contains: a live count of every resource kind\ngrouped under it. Credentials pinned to the project are deliberately\nnot counted here, because listing API keys and service accounts is an\naccount-level operation with its own authentication; use those\nendpoints directly. A derived resource (a conversation, caller, agent\ntest, or batch call) counts against the project it was created in,\neven if its agent has since moved to another project. Returns 404 for\nmissing or foreign-workspace projects.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The project's live resource counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectStats"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/promote":{"post":{"operationId":"promote","summary":"Promote Project","description":"Copy this project's configuration into another project in the same\nworkspace, creating equivalent resources there. Use it to move a\nstaging environment into production, or to seed one client's project\nfrom another's.\n\nCopied: agents with every setting (prompt, voice, model, memory and\nconsent settings, builtin tools, tool and knowledge-base bindings, the\ndraft and published flow versions, test definitions), tool\ndefinitions, knowledge bases with their folders (URL sources are\nre-imported in the target through the normal import path, so the\nresponse lists the import jobs to poll), audio assets (the object is\ncopied), and the webhook endpoints scoped to the source.\n\nNever copied: vault credentials, API keys and service accounts, phone\nnumbers and SIP trunks, conversations, callers, memories, call and\ntest history, batch calls, member grants, spend limits, uploaded or\npasted knowledge-base documents, and every secret. A copied tool or\nagent that referenced a credential has the reference cleared, every\nserver-minted signing secret is minted fresh, and each such item is\nlisted under `needs_attention` so nothing is silently half-configured.\n\nA copy keeps its name. Where the target already holds a resource of\nthe same kind and name, the copy is suffixed (`name (2)`, or `name_2`\nfor tools) and listed under `needs_attention` with reason `renamed`;\na target tool whose definition is identical to the source's is bound\ninstead of duplicated and appears under `reused`.\n\nThe copy runs in one transaction. A project holding more than 200\nresources (agents, tools, knowledge bases, URL sources, audio assets,\nwebhook endpoints and tests together) answers `409\nproject_too_large_to_promote`. Send an `Idempotency-Key` header to\nmake a retry safe: the first manifest is replayed rather than the\nproject copied twice. Both projects must be reachable to the caller;\na project-pinned credential cannot promote.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"The source project whose configuration is copied.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"What the promote created, reused, and left for a person to finish.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoteProjectResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoteProjectRequest"}}}}}},"/v1/projects/{project_id}/audit":{"get":{"operationId":"audit","summary":"Project Audit Trail","description":"Who changed this project's access or its lifecycle state, and when.\nNewest first. Covers the last 90 days; paginate by passing `cursor`\nfrom the previous response.\n\nEach entry names the SUBJECT (whose access changed) and the ACTOR (who\nchanged it), with the role the actor held at the time. When a change\nwas made by Speechify support acting on the workspace's behalf, the\nentry also carries that admin's email, so a support-initiated change\nnever reads as one a colleague made.\n\nRequires `members.manage_project_scope` (owner or admin): who widened\na member's access is a stronger fact than who currently holds it.\nReturns 404 for missing or foreign-workspace projects.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"One page of this project's audit trail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAuditResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/members":{"get":{"operationId":"listMembers","summary":"List Project Members","description":"List the workspace members granted access to this project, oldest\ngrant first. Paginate by passing `cursor` from the previous response.\n\nA member with no grants anywhere is workspace-wide and does not appear\nhere: this lists people who have been narrowed to specific projects,\nnot everyone who can reach this one.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"One page of the members granted this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectMembersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"grantMember","summary":"Grant Project Access","description":"Grant a workspace member access to this project. Once a member holds\nany grant, they see and touch only the projects they have been granted.\n\nRequires `members.manage_project_scope` (owner or admin). An owner or\nbilling admin cannot be scoped: both already hold workspace-wide access\nthat a project scope would contradict without restricting anything.\nIdempotent: granting a project the member already holds is a no-op.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The member now holds this project.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantProjectMemberRequest"}}}}}},"/v1/projects/{project_id}/members/{user_id}":{"delete":{"operationId":"revokeMember","summary":"Revoke Project Access","description":"Remove a member's access to this project.\n\nA member who loses their last grant is not locked out - they return\nto workspace-wide access, because holding no grants is the unrestricted\nstate. To restrict someone, grant them the projects they should keep\nrather than revoking everything. Requires `members.manage_project_scope`.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project ID.","required":true,"schema":{"type":"string"}},{"name":"user_id","in":"path","description":"The member's prefixed user id, as returned by the members list.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The grant was removed.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"servers":[{"url":"https://api.speechify.ai","description":"https://api.speechify.ai"}],"components":{"schemas":{"GetSpeechRequestAudioFormat":{"type":"string","enum":["wav","mp3","ogg","aac","pcm"],"default":"wav","description":"The format for the output audio. Note, that the current default is \"wav\", but there's no guarantee it will not change in the future. We recommend always passing the specific param you expect.","title":"GetSpeechRequestAudioFormat"},"GetSpeechRequestModel":{"type":"string","enum":["simba-3.0","simba-3.2"],"default":"simba-3.0","description":"Model used for audio synthesis. Defaults to `simba-3.0`, which is streaming-native and multilingual: it officially supports English plus `de-DE`, `es-ES`, `es-MX`, `fr-FR`, `it-IT` and `pt-BR`, and routes each request to its English or its multilingual training based on `language` (falling back to the voice's locale when `language` is omitted). `simba-3.2` is the streaming-native model with the lowest TTFB and richest expressivity, and the recommended Simba 3 model; it is English only, so a non-English voice returns 400.\n\nThe legacy Simba 1.6 models `simba-english` and `simba-multilingual` are retired from API version `2026-09-21`: naming one returns 400 `model_retired`. Pinning your API version to a date before `2026-09-21` keeps them working until **2026-11-21**, when both are switched off for every API version. Migrate to `simba-3.2` (English) or `simba-3.0` before then; call GET /v1/audio/models to see the set your workspace can select today.","title":"GetSpeechRequestModel"},"GetSpeechOptionsRequest":{"type":"object","properties":{"loudness_normalization":{"type":"boolean","default":false,"description":"Determines whether to normalize the audio loudness to a standard level.\nWhen enabled, loudness normalization aligns the audio output to the following standards:\nIntegrated loudness: -14 LUFS\nTrue peak: -2 dBTP\nLoudness range: 7 LU\nIf disabled, the audio loudness will match the original loudness of the selected voice, which may vary significantly and be either too quiet or too loud.\nEnabling loudness normalization can increase latency due to additional processing required for audio level adjustments."},"text_normalization":{"type":"boolean","default":true,"description":"Determines whether to normalize the text. If enabled, it will transform numbers, dates, etc. into words. For example, \"55\" is normalized into \"fifty five\".\nThis can increase latency due to additional processing required for text normalization."}},"description":"GetSpeechOptionsRequest is the wrapper for request parameters to the client","title":"GetSpeechOptionsRequest"},"AudioOutputFormat":{"type":"string","enum":["pcm_8000","pcm_16000","pcm_22050","pcm_24000","pcm_44100","pcm_48000","mp3_22050_32","mp3_22050_64","mp3_22050_96","mp3_22050_128","mp3_22050_160","mp3_22050_192","mp3_24000_32","mp3_24000_64","mp3_24000_96","mp3_24000_128","mp3_24000_160","mp3_24000_192","wav_24000","wav_48000","ulaw_8000","ogg_24000","aac_24000"],"description":"Audio output format as a `codec_sampleRate_bitrate` string, giving explicit control over sample rate and bitrate. `pcm_*` and `ulaw_8000` are headerless raw audio; `pcm_16000` and `ulaw_8000` are the telephony formats Twilio/LiveKit SIP expect.\n\n160 kbps is the highest bitrate an mp3 can carry at 22.05 and 24 kHz, so `mp3_22050_160` and `mp3_24000_160` are the maximum-fidelity mp3 formats; a request for `mp3_*_192` is encoded at 160 kbps and reported as the matching `mp3_*_160`. The two `mp3_*_160` formats are served by the Simba 3 models only.","title":"AudioOutputFormat"},"GetSpeechRequest":{"type":"object","properties":{"audio_format":{"$ref":"#/components/schemas/GetSpeechRequestAudioFormat","default":"wav","description":"The format for the output audio. Note, that the current default is \"wav\", but there's no guarantee it will not change in the future. We recommend always passing the specific param you expect."},"input":{"type":"string","maxLength":2000,"description":"Plain text or SSML to be synthesized to speech.\nRefer to https://docs.speechify.ai/docs/api-limits for the input size limits.\nEmotion, Pitch and Speed Rate are configured in the ssml input, please refer to the ssml documentation for more information: https://docs.speechify.ai/docs/ssml#prosody"},"language":{"type":"string","description":"Language of the input. Follow the format of an ISO 639-1 language code and an ISO 3166-1 region code, separated by a hyphen, e.g. en-US.\nPlease refer to the list of the supported languages and recommendations regarding this parameter: https://docs.speechify.ai/docs/language-support."},"model":{"$ref":"#/components/schemas/GetSpeechRequestModel","default":"simba-3.0","description":"Model used for audio synthesis. Defaults to `simba-3.0`, which is streaming-native and multilingual: it officially supports English plus `de-DE`, `es-ES`, `es-MX`, `fr-FR`, `it-IT` and `pt-BR`, and routes each request to its English or its multilingual training based on `language` (falling back to the voice's locale when `language` is omitted). `simba-3.2` is the streaming-native model with the lowest TTFB and richest expressivity, and the recommended Simba 3 model; it is English only, so a non-English voice returns 400.\n\nThe legacy Simba 1.6 models `simba-english` and `simba-multilingual` are retired from API version `2026-09-21`: naming one returns 400 `model_retired`. Pinning your API version to a date before `2026-09-21` keeps them working until **2026-11-21**, when both are switched off for every API version. Migrate to `simba-3.2` (English) or `simba-3.0` before then; call GET /v1/audio/models to see the set your workspace can select today."},"options":{"$ref":"#/components/schemas/GetSpeechOptionsRequest"},"output_format":{"$ref":"#/components/schemas/AudioOutputFormat","description":"The output audio format as a `codec_sampleRate_bitrate` string. Takes precedence over `audio_format` when set."},"voice_id":{"type":"string","description":"Id of the voice to be used for synthesizing speech. Refer to /v1/voices endpoint for available voices"}},"required":["input","voice_id"],"description":"Request body for POST /v1/audio/speech.","title":"GetSpeechRequest"},"GetSpeechResponseAudioFormat":{"type":"string","enum":["wav","mp3","ogg","aac","pcm","ulaw"],"description":"The codec of the audio data","title":"GetSpeechResponseAudioFormat"},"NestedChunk":{"type":"object","properties":{"end":{"type":"integer","format":"int64"},"end_time":{"type":"number","format":"double"},"start":{"type":"integer","format":"int64"},"start_time":{"type":"number","format":"double"},"type":{"type":"string"},"value":{"type":"string"}},"description":"It details the type of segment, its start and end points in the text, and its start and end times in the synthesized speech audio.","title":"NestedChunk"},"SpeechMarks":{"type":"object","properties":{"chunks":{"type":"array","items":{"$ref":"#/components/schemas/NestedChunk"},"description":"Array of NestedChunk, each providing detailed segment information within the synthesized speech."},"end":{"type":"integer","format":"int64"},"end_time":{"type":"number","format":"double"},"start":{"type":"integer","format":"int64"},"start_time":{"type":"number","format":"double"},"type":{"type":"string"},"value":{"type":"string"}},"required":["chunks","end","end_time","start","start_time","type"],"description":"It is used to annotate the audio data with metadata about the synthesis process, like word timing or phoneme details.","title":"SpeechMarks"},"GetSpeechResponse":{"type":"object","properties":{"audio_data":{"type":"string","format":"byte","description":"Synthesized speech audio, Base64-encoded"},"audio_format":{"$ref":"#/components/schemas/GetSpeechResponseAudioFormat","description":"The codec of the audio data"},"billable_characters_count":{"type":"integer","format":"int64","description":"The number of billable characters processed in the request."},"output_format":{"$ref":"#/components/schemas/AudioOutputFormat","description":"The full `codec_sampleRate_bitrate` format the audio was encoded in, returned when the request set `output_format`. It is the requested value unless the request named a bitrate above the mp3 ceiling, in which case it reports the bitrate actually delivered."},"speech_marks":{"$ref":"#/components/schemas/SpeechMarks"}},"required":["audio_data","audio_format","billable_characters_count","speech_marks"],"title":"GetSpeechResponse"},"ErrorCode":{"type":"string","enum":["bad_request","validation_failed","unauthorized","payment_required","forbidden","not_found","method_not_allowed","conflict","idempotency_conflict","payload_too_large","unsupported_media_type","rate_limited","concurrency_limit_reached","invalid_api_version","internal_error","upstream_failure","service_unavailable","caller_not_found","contact_not_found","contact_identifier_not_found","contact_identifier_conflict","contact_resolver_not_found","credential_not_found","credential_in_use","agent_not_found","agent_in_use","agent_run_not_found","kb_not_found","kb_document_not_found","kb_folder_not_found","tool_not_found","channel_instance_not_found","team_not_found","trigger_not_found","store_not_found","store_document_not_found","store_asset_not_found","hosted_api_not_found","api_route_not_found","consumer_key_not_found","skill_not_found","skill_version_not_found","store_limit_reached","store_document_limit_reached","store_bytes_limit_reached","store_not_configured","hosted_apis_not_in_plan","skills_not_in_plan","skill_in_use","skill_tool_name_conflict","skill_limit_reached","agent_skill_limit_reached","hosted_api_slug_taken","api_route_conflict","route_output_unavailable","route_run_timeout","route_run_failed","route_run_limit_reached","conversation_not_found","phone_number_not_found","sip_trunk_not_found","voice_not_found","audio_asset_not_found","builtin_not_found","batch_not_found","agent_test_not_found","workspace_not_found","invite_not_found","project_not_found","cross_project_reference","project_has_scoped_credentials","project_limit_reached","agent_limit_reached","project_too_large_to_promote","call_not_found","message_not_found","thread_not_found","call_not_active","relay_displaces_agent","brain_not_found","brain_in_use","insufficient_scope","purchased_numbers_not_included","phone_number_quota_reached","batch_calls_not_included","voice_cloning_not_included","consent_challenge_not_found","consent_challenge_expired","consent_challenge_already_used","consent_phrase_mismatch","consent_speaker_mismatch","consent_recording_unusable","consent_verification_unavailable","watermark_audio_unusable","watermark_detection_unavailable","workspace_last_owner","workspace_last_workspace","account_deletion_blocked","workspace_free_limit","workspace_single_owner","invite_email_mismatch","invite_already_pending","service_account_limit_reached","service_accounts_not_in_plan","speech_marks_unsupported","model_retired","too_many_voices","content_policy_violation","topup_not_in_plan","credit_purchase_unpaid","tool_config_shared","spend_cap_exceeded","spend_budget_exceeded","project_spend_limit_exceeded","project_archived","project_not_archived","project_not_purged","project_restore_window_expired","project_name_taken","funded_balance_required","agent_publish_gate_failed","agent_publish_gate_required","agent_publish_gate_unavailable","text_channel_not_in_plan","channel_not_in_plan","text_turn_failed","text_message_quota_exceeded","durable_runs_not_in_plan","tool_transport_unsupported","agent_run_not_pending","agent_run_action_stale","share_link_not_found","share_link_exhausted","share_link_limit_reached","destination_not_allowed","international_dialing_not_enabled","number_not_sms_capable","verification_required","intended_use_required"],"description":"Stable machine-readable error code. Additive only: codes are\nnever renamed, only deprecated. SDKs may map each code to a\ntyped exception class. Status-code semantics:\n4xx codes describe caller-fixable issues; 5xx codes describe\nserver-side failures and are safe to retry with backoff for\nidempotent requests.\n","title":"ErrorCode"},"ErrorDetail":{"type":"object","properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable explanation of this specific occurrence.\nSafe to surface in UI banners or pass to support. The\nwording can change between releases; clients should\nmatch on `code`, not on the message string.\n"},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Per-field validation errors as `path -> message`. Only\npresent on 400 responses caused by request validation\n(typically code=`validation_failed`). Keys are field\npaths in dotted/bracket notation; values are short\nhuman explanations safe to inline-surface next to the\noffending form field.\n"},"details":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Structured, endpoint-specific context beyond the flat\n`fields` map. Present only on the few errors that carry\nit (e.g. the `used_by` referrer list on a credential\ndelete-conflict); its shape depends on the error `code`.\nClients that don't recognise a `details` shape can ignore\nit - the `code` + `message` contract is unchanged.\n"},"docs_url":{"type":"string","format":"uri","description":"Link to the documentation that resolves this class of\nerror, when a stable page exists. Rate and concurrency\n429s link the API limits reference, which lists each\nplan's limits and how to raise them.\n"}},"required":["code","message"],"title":"ErrorDetail"},"Error":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"},"request_id":{"type":"string","description":"Server-side request identifier. Echoes the\n`Speechify-Request-Id` response header. Stable across the\nrequest's lifetime, written to structured logs, and\nuseful when reporting issues.\n"}},"required":["error"],"description":"Standard error envelope returned on every non-2xx response.\nContent-Type is `application/json`. The shape mirrors OpenAI /\nAnthropic / Stripe style: a machine-readable `error.code` for\nSDK consumers to switch on, a human `error.message` for UI,\nand an optional `error.fields` map for per-field validation\nerrors. `request_id` matches the `Speechify-Request-Id` response\nheader and is what customers quote when filing support\ntickets.\n","title":"Error"},"V1AudioStreamPostParametersAccept":{"type":"string","enum":["audio/mpeg","audio/ogg","audio/aac","audio/pcm"],"title":"V1AudioStreamPostParametersAccept"},"GetStreamRequestModel":{"type":"string","enum":["simba-3.0","simba-3.2"],"default":"simba-3.0","description":"Model used for audio synthesis. Defaults to `simba-3.0`, which is streaming-native and multilingual: it officially supports English plus `de-DE`, `es-ES`, `es-MX`, `fr-FR`, `it-IT` and `pt-BR`, and routes each request to its English or its multilingual training based on `language` (falling back to the voice's locale when `language` is omitted). `simba-3.2` is the streaming-native model with the lowest TTFB and richest expressivity, and the recommended Simba 3 model; it is English only, so a non-English voice returns 400.\n\nThe legacy Simba 1.6 models `simba-english` and `simba-multilingual` are retired from API version `2026-09-21`: naming one returns 400 `model_retired`. Pinning your API version to a date before `2026-09-21` keeps them working until **2026-11-21**, when both are switched off for every API version. Migrate to `simba-3.2` (English) or `simba-3.0` before then; call GET /v1/audio/models to see the set your workspace can select today.","title":"GetStreamRequestModel"},"GetStreamOptionsRequest":{"type":"object","properties":{"loudness_normalization":{"type":"boolean","default":false,"description":"Determines whether to normalize the audio loudness to a standard level.\nWhen enabled, loudness normalization aligns the audio output to the following standards:\nIntegrated loudness: -14 LUFS\nTrue peak: -2 dBTP\nLoudness range: 7 LU\nIf disabled, the audio loudness will match the original loudness of the selected voice, which may vary significantly and be either too quiet or too loud.\nEnabling loudness normalization can increase latency due to additional processing required for audio level adjustments."},"text_normalization":{"type":"boolean","default":false,"description":"Determines whether to normalize the text. If enabled, it will transform numbers, dates, etc. into words. For example, \"55\" is normalized into \"fifty five\".\nThis can increase latency due to additional processing required for text normalization."}},"description":"GetStreamOptionsRequest is the wrapper for request parameters to the client","title":"GetStreamOptionsRequest"},"AudioStreamOutputFormat":{"type":"string","enum":["pcm_8000","pcm_16000","pcm_22050","pcm_24000","pcm_44100","pcm_48000","mp3_22050_32","mp3_22050_64","mp3_22050_96","mp3_22050_128","mp3_22050_160","mp3_22050_192","mp3_24000_32","mp3_24000_64","mp3_24000_96","mp3_24000_128","mp3_24000_160","mp3_24000_192","ulaw_8000","ogg_24000","aac_24000"],"description":"Audio output format for the streaming endpoint (`POST /v1/audio/stream`), as a `codec_sampleRate_bitrate` string. Same as `AudioOutputFormat` minus the `wav_*` formats: wav is only available on `POST /v1/audio/speech`. `pcm_*` and `ulaw_8000` are headerless raw audio; `pcm_16000` and `ulaw_8000` are the telephony formats Twilio/LiveKit SIP expect.\n\n160 kbps is the highest bitrate an mp3 can carry at 22.05 and 24 kHz, so `mp3_22050_160` and `mp3_24000_160` are the maximum-fidelity mp3 formats; a request for `mp3_*_192` is encoded at 160 kbps. The two `mp3_*_160` formats are served by the Simba 3 models only.","title":"AudioStreamOutputFormat"},"GetStreamRequest":{"type":"object","properties":{"input":{"type":"string","maxLength":20000,"description":"Plain text or SSML to be synthesized to speech.\nRefer to https://docs.speechify.ai/docs/api-limits for the input size limits.\nEmotion, Pitch and Speed Rate are configured in the ssml input, please refer to the ssml documentation for more information: https://docs.speechify.ai/docs/ssml#prosody"},"language":{"type":"string","description":"Language of the input. Follow the format of an ISO 639-1 language code and an ISO 3166-1 region code, separated by a hyphen, e.g. en-US.\nPlease refer to the list of the supported languages and recommendations regarding this parameter: https://docs.speechify.ai/docs/language-support."},"model":{"$ref":"#/components/schemas/GetStreamRequestModel","default":"simba-3.0","description":"Model used for audio synthesis. Defaults to `simba-3.0`, which is streaming-native and multilingual: it officially supports English plus `de-DE`, `es-ES`, `es-MX`, `fr-FR`, `it-IT` and `pt-BR`, and routes each request to its English or its multilingual training based on `language` (falling back to the voice's locale when `language` is omitted). `simba-3.2` is the streaming-native model with the lowest TTFB and richest expressivity, and the recommended Simba 3 model; it is English only, so a non-English voice returns 400.\n\nThe legacy Simba 1.6 models `simba-english` and `simba-multilingual` are retired from API version `2026-09-21`: naming one returns 400 `model_retired`. Pinning your API version to a date before `2026-09-21` keeps them working until **2026-11-21**, when both are switched off for every API version. Migrate to `simba-3.2` (English) or `simba-3.0` before then; call GET /v1/audio/models to see the set your workspace can select today."},"options":{"$ref":"#/components/schemas/GetStreamOptionsRequest"},"output_format":{"$ref":"#/components/schemas/AudioStreamOutputFormat","description":"The output audio format as a `codec_sampleRate_bitrate` string. Takes precedence over the `Accept` header when set, so you can request formats the `Accept` enum does not cover (e.g. `pcm_16000`, `ulaw_8000`). `wav_*` formats are not supported on streaming - use `POST /v1/audio/speech` for wav."},"voice_id":{"type":"string","description":"Id of the voice to be used for synthesizing speech. Refer to /v1/voices endpoint for available voices"}},"required":["input","voice_id"],"description":"GetStreamRequest is the wrapper for request parameters to the client","title":"GetStreamRequest"},"V1AudioStreamWithTimestampsPostParametersAccept":{"type":"string","enum":["audio/mpeg","audio/ogg","audio/aac","audio/pcm"],"title":"V1AudioStreamWithTimestampsPostParametersAccept"},"SpeechStreamEvent":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["speech.chunk"]},"audio":{"type":"string","description":"A run of the synthesized audio, Base64-encoded, in the format the\nrequest selected (echoed on the `Speechify-Audio-Content-Type`\nresponse header). Absent on a marks-only chunk."},"speech_marks":{"type":"array","items":{"$ref":"#/components/schemas/NestedChunk"},"description":"Word timings addressing the original input text, with absolute\nmillisecond times from the start of the synthesis. Absent when the\nchunk carries only audio."}},"required":["type"],"description":"A run of synthesized audio, the speech marks that became final with it,\nor both - a chunk may carry only one of the two, and the last chunk of\na stream is often marks-only. Mark times are absolute milliseconds from\nthe start of the synthesis: concatenate the audio chunks into one\nstream and apply the marks against that single timeline. Which chunk a\nmark arrives on is a delivery detail and carries no meaning."},{"type":"object","properties":{"type":{"type":"string","enum":["speech.done"]},"billable_characters_count":{"type":"integer","description":"Number of billable characters processed."},"audio_duration_ms":{"type":"integer","description":"Duration of the synthesized audio in milliseconds."}},"required":["type","billable_characters_count","audio_duration_ms"],"description":"Terminal event: the synthesis completed and no further events follow.\nThere is no `[DONE]` sentinel."},{"type":"object","properties":{"type":{"type":"string","enum":["speech.error"]},"error":{"$ref":"#/components/schemas/ErrorDetail"},"request_id":{"type":"string","description":"Server-side request identifier. Echoes the `Speechify-Request-Id`\nresponse header."}},"required":["type","error"],"description":"Terminal event carrying the standard error envelope, emitted when a\nfailure happens after the stream has started and the status code is\nalready committed."}],"discriminator":{"propertyName":"type"},"description":"One event on the POST /v1/audio/stream/with-timestamps stream. The\n`type` field discriminates the variants and mirrors the SSE `event:`\nname, so an event is identifiable from its `data:` payload alone.","title":"SpeechStreamEvent"},"V1VoicesGetParametersType":{"type":"string","enum":["personal","shared"],"title":"V1VoicesGetParametersType"},"V1VoicesGetParametersGender":{"type":"string","enum":["male","female","not_specified"],"title":"V1VoicesGetParametersGender"},"GetVoiceGender":{"type":"string","enum":["male","female","not_specified"],"title":"GetVoiceGender"},"GetVoiceLanguage":{"type":"object","properties":{"locale":{"type":"string"},"preview_audio":{"type":["string","null"]}},"required":["locale"],"title":"GetVoiceLanguage"},"GetVoicesModelName":{"type":"string","enum":["simba-english","simba-multilingual","simba-3.0","simba-3.2"],"description":"A model this voice can be synthesized with. The set is filtered to\nwhat YOUR workspace's API version can select, so a voice never\nadvertises a model your own synthesis request would reject. The\nlegacy `simba-english` / `simba-multilingual` values appear only for\na workspace pinned before API version `2026-09-21`.\n","title":"GetVoicesModelName"},"GetVoicesModel":{"type":"object","properties":{"languages":{"type":"array","items":{"$ref":"#/components/schemas/GetVoiceLanguage"}},"name":{"$ref":"#/components/schemas/GetVoicesModelName","description":"A model this voice can be synthesized with. The set is filtered to\nwhat YOUR workspace's API version can select, so a voice never\nadvertises a model your own synthesis request would reject. The\nlegacy `simba-english` / `simba-multilingual` values appear only for\na workspace pinned before API version `2026-09-21`.\n"}},"required":["languages","name"],"title":"GetVoicesModel"},"GetVoiceType":{"type":"string","enum":["shared","personal"],"title":"GetVoiceType"},"GetVoice":{"type":"object","properties":{"avatar_image":{"type":["string","null"]},"display_name":{"type":"string"},"gender":{"$ref":"#/components/schemas/GetVoiceGender"},"locale":{"type":"string"},"id":{"type":"string"},"models":{"type":"array","items":{"$ref":"#/components/schemas/GetVoicesModel"}},"preview_audio":{"type":["string","null"]},"project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The workspace project this cloned voice is filed under, set when\na project-pinned key created it. Returned wherever a cloned voice\nis: the list, a single-voice read, and the create response.\n\nOmitted for a shared-catalog voice and for a cloned voice no\nproject filed, which is shared with the whole workspace and\nlisted for every member of it.\n"},"tags":{"type":["array","null"],"items":{"type":"string"}},"type":{"$ref":"#/components/schemas/GetVoiceType"}},"required":["display_name","gender","locale","id","models","type"],"title":"GetVoice"},"ListVoicesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"voices":{"type":"array","items":{"$ref":"#/components/schemas/GetVoice"}}},"required":["next_cursor","has_more","voices"],"description":"Payload for `GET /v1/voices` - the shared voice catalog plus the\nworkspace's personal cloned voices. Carries the shared pagination\nmetadata so the list can be paged when `limit` is supplied;\n`next_cursor` is null and `has_more` is false when the full\ncatalogue is returned in one response.\n","title":"ListVoicesResponse"},"V1VoicesPostRequestBodyContentMultipartFormDataSchemaGender":{"type":"string","enum":["male","female","not_specified"],"description":"Gender marker for the personal voice\nmale GenderMale\nfemale GenderFemale\nnot_specified GenderNotSpecified","title":"V1VoicesPostRequestBodyContentMultipartFormDataSchemaGender"},"CreateConsentChallengeRequest":{"type":"object","properties":{"full_name":{"type":"string","description":"Full name of the person consenting to have their voice cloned.\nSpeechify binds it to the challenge and stores it with the consent\nrecord, so the create that consumes the challenge does not carry it\nand cannot change it.\n\nAt most 120 bytes once UTF-8 encoded, which is 120 characters of\nLatin script but around 40 of Chinese, Japanese or Korean. Stated in\nbytes rather than as a `maxLength` because the two only agree on\nsingle-byte scripts, and a character count that never over-accepts\nwould have to refuse Latin names at 30. A name over the limit comes\nback as `validation_failed` reporting its measured length."}},"required":["full_name"],"title":"CreateConsentChallengeRequest"},"ConsentChallenge":{"type":"object","properties":{"id":{"type":"string","description":"Identifier for this challenge, sent back as `consent_challenge_id`\non the create. Treat it as an opaque string - the format is not part\nof the contract and will not stay stable."},"phrase":{"type":"string","description":"The sentence the speaker must read aloud. Show it exactly as\nreturned - the recording is transcribed and matched against this\ntext, so re-wording, re-casing or re-punctuating it will fail the\ncheck."},"expires_at":{"type":"string","format":"date-time","description":"When the challenge stops being usable. This is the only authority on\nthe window - do not hard-code a duration. Past it, create a new\nchallenge and record the new phrase."}},"required":["id","phrase","expires_at"],"title":"ConsentChallenge"},"Model":{"type":"object","properties":{"id":{"type":"string","description":"Model identifier. Pass this as the `model` parameter to\nPOST /v1/audio/speech or /v1/audio/stream.\n"},"name":{"type":"string","description":"Human-readable model name, for a model picker."},"default":{"type":"boolean","description":"Whether this is the model used when a synthesis request omits\n`model`. Exactly one model in the list is the default. Distinct\nfrom `recommended`: the default accepts every voice, while the\nrecommended model may serve a curated or English-only set.\n"},"recommended":{"type":"boolean","description":"Whether this is the model we recommend for new integrations.\nExactly one model in the list is recommended, and it may differ\nfrom the `default`.\n"},"deprecated":{"type":"boolean","description":"Whether this is a legacy model. De-emphasise it in a picker and\nsteer new integrations to a current model. Read `retired_at` for\nwhether it also has a withdrawal date.\n"},"retired_at":{"type":"string","format":"date","description":"The API version at which this model stops being selectable, as\n`YYYY-MM-DD`. Absent when the model has no withdrawal date.\n\nIt appears only while your workspace is pinned BELOW that version -\nat or after it the model is absent from this catalog entirely, and\nnaming it returns 400 `model_retired`. So a present value means \"you\ncan still use this, and this is the date you lose it\". Pinning your\nAPI version before this date keeps the model working, up to\n`sunset_at`.\n"},"sunset_at":{"type":"string","format":"date","description":"The date this model is switched off, as `YYYY-MM-DD`. Absent when no\nshutdown is scheduled.\n\nThis is the deadline `retired_at`'s version pin runs out against:\nfrom `sunset_at` the model is unreachable on EVERY API version,\nincluding a workspace pinned below its retirement. Read the two\ntogether - a pin buys time to migrate, not a permanent exemption.\n"},"description":{"type":"string","description":"One-line summary of the model, for a model picker."},"languages":{"type":"array","items":{"type":"string"},"description":"Languages the model can synthesize, as BCP-47 locale strings\nmatching the `language` request parameter (e.g. `en`, `fr-FR`).\nEnglish-only models return `[\"en\"]`. This set reflects current\ncapability and can grow over time.\n"},"endpoints":{"type":"array","items":{"type":"string"},"description":"The synthesis routes this model may be passed to. Only the\nstreaming-native models serve `/v1/audio/stream/with-timestamps`;\npassing a model this list omits is a 400 rather than a degraded\nresponse, so branch on it instead of discovering it at call time.\n"},"curated_voices":{"type":"boolean","description":"Whether the model's stock voices are restricted to the set curated\nfor it. When true, pick a stock voice whose `models` array in\nGET /v1/voices names this model; any other stock voice is rejected.\nWhen false, every stock catalogue voice works. Cloned voices are\ngoverned separately - always read each voice's own `models` array in\nGET /v1/voices, which reflects what your workspace may actually\nsynthesize.\n"},"english_voices_only":{"type":"boolean","description":"Whether the model rejects a non-English voice. Independent of\n`languages`: a model can publish English only and still accept any\nvoice.\n"}},"required":["id","name","default","recommended","deprecated","description","languages","endpoints","curated_voices","english_voices_only"],"description":"One selectable text-to-speech model.","title":"Model"},"ModelsResponse":{"type":"object","properties":{"models":{"type":"array","items":{"$ref":"#/components/schemas/Model"},"description":"The models selectable on the single-utterance synthesis endpoints.\n"},"dialogue_models":{"type":"array","items":{"$ref":"#/components/schemas/Model"},"description":"The multi-speaker models selectable on POST /v1/audio/dialogue.\nDisjoint from `models`: a dialogue model consumes a\nspeaker-attributed script rather than one utterance, so it is\nrejected on the single-utterance endpoints and vice versa. Its\n`default` marks the model that endpoint resolves to when a request\nomits `model`, independently of the `models` default.\n"}},"required":["models","dialogue_models"],"description":"The catalog of text-to-speech models available for synthesis. The list\nis scoped to your workspace's API version: a model retired at or before\nit is absent here and returns 400 `model_retired` from the synthesis\nendpoints, so what this returns is exactly what you can call. The\nexample below is the catalog at the current version - a workspace pinned\nbefore `2026-09-21` also sees `simba-english` and `simba-multilingual`,\neach carrying `retired_at` and the `sunset_at` date its pin expires\nagainst.\n","title":"ModelsResponse"},"WatermarkDetectionResponse":{"type":"object","properties":{"watermarked":{"type":"boolean","description":"Whether the clip carries the watermark, i.e. whether `confidence`\ncleared the service's configured bar."},"confidence":{"type":"number","format":"double","minimum":0,"maximum":1,"description":"The detector's score for this clip, in [0, 1]. Comparable only\nbetween checks made against the same detector version."}},"required":["watermarked","confidence"],"description":"The watermark detector's answer for one clip.\n\n`watermarked: true` is positive evidence the audio came from Speechify\nsynthesis. `watermarked: false` is the absence of that evidence, not\nproof of a negative - see the endpoint description for the three reasons\ngenuinely Speechify-generated audio can come back unmarked.\n","title":"WatermarkDetectionResponse"},"WatermarkVerificationResponse":{"type":"object","properties":{"watermarked":{"type":"boolean","description":"Whether the clip carries the Speechify watermark."}},"required":["watermarked"],"description":"The verification answer for one clip.\n\nOne field, and no confidence score: a score is a gradient, and a public\ngradient turns \"does this carry the mark\" into \"adjust the clip until\nthe number drops\". Use `POST /v1/audio/watermark/detect` if you need it.\n\n`watermarked: true` is positive evidence the audio came from Speechify\nsynthesis. `watermarked: false` is the absence of that evidence, not\nproof of a negative - see the endpoint description.\n","title":"WatermarkVerificationResponse"},"AgentChannel":{"type":"string","enum":["voice","text"],"description":"A delivery surface an agent can serve. One agent may serve several,\nso `channels` is a set. `voice` runs over telephony + web (requires a\n`tts.voice_id`); `text` runs over the chat/widget surface. An agent\nwith an empty set has no front door and is reached by its triggers\nonly. There is no agent type: what an agent is (its prompt, model,\nknowledge and tools) is one thing, and this set is how it is reached.\n","title":"AgentChannel"},"AgentAdditionalLanguage":{"type":"object","properties":{"language":{"type":"string","description":"BCP-47 language tag, e.g. 'es' or 'pt-BR'. The supported set\nis served by `multilingual_languages` on GET\n/v1/agents/voices - read it rather than hard-coding a list,\nsince coverage widens over time. Must be unique per agent and\ndifferent from the default `language`.\n"},"voice_id":{"type":["string","null"],"description":"Optional per-language voice slug from the catalog (see GET\n/v1/agents/voices). Null or omitted serves the language with\nthe agent's default voice.\n"},"first_message":{"type":["string","null"],"description":"Optional greeting used when a session starts in this\nlanguage. Null or omitted falls back to the agent's default\n`first_message`.\n"}},"required":["language"],"description":"One extra language an agent serves beyond its default `language`.\nWithout `voice_id` the agent's own voice speaks the language\n(voice continuity through a mid-call switch); with `voice_id`\nthe language is served by that voice instead, e.g. a native\nspeaker of the language. `first_message` overrides the greeting\nonly when a session starts in this language.\n","title":"AgentAdditionalLanguage"},"AgentLlmConfigProvider":{"type":"string","enum":["openai","speechify","custom"],"description":"LLM backend. Null (or omit) uses the platform default,\nresolved server-side at dispatch. `openai` and `speechify`\npair with a `model` from the allowed table. `custom` points\nthe worker at any OpenAI / vLLM-compatible endpoint - see\n`base_url`, `credential_id`, `extra_body`. Must be paired with\na non-null `model`; setting one without the other is rejected.\n","title":"AgentLlmConfigProvider"},"AgentLLMConfig":{"type":"object","properties":{"provider":{"$ref":"#/components/schemas/AgentLlmConfigProvider","description":"LLM backend. Null (or omit) uses the platform default,\nresolved server-side at dispatch. `openai` and `speechify`\npair with a `model` from the allowed table. `custom` points\nthe worker at any OpenAI / vLLM-compatible endpoint - see\n`base_url`, `credential_id`, `extra_body`. Must be paired with\na non-null `model`; setting one without the other is rejected.\n"},"model":{"type":["string","null"],"description":"Chat model slug. Null (or omit) uses the platform default.\nFor `openai` / `speechify` it must be a slug from the allowed\ntable; for `custom` it is free-form.\n"},"base_url":{"type":["string","null"],"description":"Custom OpenAI/vLLM-compatible endpoint base URL. Required\nwhen `provider` is `custom`, rejected otherwise.\n"},"credential_id":{"type":["string","null"],"description":"`cred_` id of a `bearer` credential in the\nworkspace vault holding the custom endpoint's API key. The\nsecret lives in the vault and is resolved server-side at\ndispatch, never inlined on the agent. On write, send a value\nto point at a credential, an empty string to clear it, or omit\nit to keep the stored reference unchanged. On read, the\nreferenced `cred_` id, or null when none is referenced. Create\nthe credential first via `POST /v1/credentials`. Valid only\nwhen `provider` is `custom`.\n"},"extra_body":{"type":["object","null"],"additionalProperties":{"description":"Any type"},"description":"JSON object forwarded verbatim to the custom endpoint as the\nchat.completions `extra_body` (reasoning / sampling knobs).\nValid only when `provider` is `custom`.\n"},"temperature":{"type":"string","title":"float","description":"Sampling temperature in the range 0.0-1.0. Defaults to 0.5."}},"description":"Language-model configuration. Omit the whole block on create to\nrun on the platform default model. On update (merge-patch) send\nonly the sub-fields you want to change: an explicit null clears a\nnullable field to its default, a value sets it, and anything\nomitted is left unchanged. `provider`/`model` are validated as a\npair, inheriting the omitted half from the stored value.\n","title":"AgentLLMConfig"},"AgentTTSConfig":{"type":"object","properties":{"voice_id":{"type":"string","description":"Voice slug (see GET /v1/agents/voices for the recommended set). The server rejects an empty slug, and any voice it cannot synthesize."},"speed":{"type":["string","null"],"title":"float","description":"Post-process, pitch-preserving time-stretch applied to the\nsynthesized audio before playback (0.5 = half speed, 2.0 =\ndouble, 1.0 = unchanged). Null means no time-stretch.\n"},"model":{"type":["string","null"],"description":"Pins the Simba model this agent synthesizes on. **Null is the\ndefault and the recommended setting**: the agent follows the\nmodel its voice is curated for, so a voice promoted to a newer\ntraining moves with it and no configuration goes stale.\n\nSet it to override that choice in either direction - onto an\nexperimental training, or back down off one. Call\nGET /v1/agents/tts-models for the catalog, and read the voice's\n`models` and `default_model` on GET /v1/agents/voices for what\nthis particular voice can serve on.\n\nA write is rejected when the agent could not actually be served\nwith the model: an English-only model on an agent that resolves\nthrough the multilingual serving (it declares\n`additional_languages`, or its own `language` is not English),\nor a model the chosen voice is not curated for. The pair is\nre-checked whenever `voice_id`, `language`, or\n`additional_languages` change, so a stored pin cannot be\norphaned by an edit elsewhere.\n\nSend `null` (or `\"\"`) to clear it back to automatic.\n"}},"required":["voice_id"],"description":"Text-to-speech voice and delivery configuration.","title":"AgentTTSConfig"},"AgentTurnHandlingConfigInterruptionSensitivity":{"type":"string","enum":["low","medium","high"],"description":"How easily the caller takes the floor from a speaking agent,\nand how patiently the agent waits before treating a pause as\nthe end of the caller's turn. Null follows the platform and\nbehaves identically to `medium`.\n\n`low` makes the agent hard to interrupt: background noise,\nhold music, and one- or two-word sounds no longer stop it\nmid-sentence (it needs 1.5s of speech and at least 3 words),\nand it waits 0.4s longer before taking a pause as the end of\na turn. Use it for noisy lines and for callers who pause\nmid-thought. `high` is the opposite: the agent yields after\n0.4s of caller speech and takes the turn sooner.\n\nNeither setting changes ordinary turn-taking - a one-word\nanswer still replies normally when the agent is silent; the\nword threshold only applies to interrupting speech already in\nprogress. Two limits worth knowing: the word threshold needs\ntranscription, so it does not apply to agents running the\nrealtime audio model, and setting `response_delay_seconds`\ntakes over the silence window entirely - the level no longer\nlengthens or shortens how long the agent waits, though it still\ngoverns how easily the caller interrupts.\n","title":"AgentTurnHandlingConfigInterruptionSensitivity"},"AgentTurnHandlingConfig":{"type":"object","properties":{"response_delay_seconds":{"type":["string","null"],"title":"float","description":"Minimum silence, in seconds, the agent waits for after the\ncaller stops talking before it starts replying. Applies to\nevery turn on the call. Raise it when the agent talks over a\ncaller who pauses mid-sentence, or over an IVR menu that\nbreaks between options. It is a floor on listening rather\nthan a pause before speaking, so it adds to the time every\nreply takes to arrive. Null lets turn detection decide, which\nwaits 0.3s when it is confident the caller finished and up to\n1.2s when it is not.\n"},"interruption_sensitivity":{"$ref":"#/components/schemas/AgentTurnHandlingConfigInterruptionSensitivity","description":"How easily the caller takes the floor from a speaking agent,\nand how patiently the agent waits before treating a pause as\nthe end of the caller's turn. Null follows the platform and\nbehaves identically to `medium`.\n\n`low` makes the agent hard to interrupt: background noise,\nhold music, and one- or two-word sounds no longer stop it\nmid-sentence (it needs 1.5s of speech and at least 3 words),\nand it waits 0.4s longer before taking a pause as the end of\na turn. Use it for noisy lines and for callers who pause\nmid-thought. `high` is the opposite: the agent yields after\n0.4s of caller speech and takes the turn sooner.\n\nNeither setting changes ordinary turn-taking - a one-word\nanswer still replies normally when the agent is silent; the\nword threshold only applies to interrupting speech already in\nprogress. Two limits worth knowing: the word threshold needs\ntranscription, so it does not apply to agents running the\nrealtime audio model, and setting `response_delay_seconds`\ntakes over the silence window entirely - the level no longer\nlengthens or shortens how long the agent waits, though it still\ngoverns how easily the caller interrupts.\n"},"inactivity_timeout_seconds":{"type":["integer","null"],"minimum":0,"description":"How long the agent tolerates silence before ending the call,\nin seconds. Null uses the platform default.\n"}},"description":"Turn-handling and silence-timeout configuration.","title":"AgentTurnHandlingConfig"},"AgentMemoryConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"When true, the post-call extractor writes durable facts about\neach caller and the retriever injects the top matches into the\nsystem prompt via the `{{memory}}` variable. Defaults to false.\n"},"retention_days":{"type":"integer","minimum":0,"description":"Maximum age (in days) of memories kept and surfaced. 0\ndisables the cap. Defaults to 90.\n"}},"description":"Per-caller long-term memory configuration.","title":"AgentMemoryConfig"},"AgentNavigatorConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Tunes worker turn handling for autonomous outbound IVR\nnavigation - longer endpointing and no barge-in. The\nnavigation goal itself lives in the agent's prompt; this flag\nis the behaviour switch only. Defaults to false.\n"},"ivr_cache_enabled":{"type":"boolean","description":"Controls the IVR-menu cache consulted during navigation.\nDefaults to true so a navigator agent reuses learned menu\nlayouts across calls. Set to false to force every outbound\ndial on this agent to navigate from scratch.\n"}},"description":"Autonomous IVR-navigation configuration for outbound calls.","title":"AgentNavigatorConfig"},"AgentGuardrailsConfig":{"type":"object","properties":{"observer_enabled":{"type":"boolean","description":"Arms the out-of-band injection guardrail. A second, isolated\nmodel reads the caller's turns off the live conversation and\ncorrects the agent when it detects a privileged-identity\nclaim, a prompt-extraction probe, an instruction override, or\nthe caller pulling the call off its purpose. It runs off the\nturn critical path, so it does not add caller-facing latency,\nand it never receives the agent's own instructions - so it\ncannot be talked out of running by anything the caller says.\nAdds one inference call per caller turn. Defaults to false.\n"}},"description":"Runtime safety controls that are opt-in per agent rather than\nplatform defaults, because each one bills work an ordinary call\ndoes not do.\n","title":"AgentGuardrailsConfig"},"AgentBackgroundNoiseConfigPreset":{"type":"string","enum":["office","city","forest","crowded_room","keyboard_typing","hold_music"],"description":"Pre-mixed ambient bed. Null disables background noise.","title":"AgentBackgroundNoiseConfigPreset"},"AgentBackgroundNoiseConfig":{"type":"object","properties":{"preset":{"$ref":"#/components/schemas/AgentBackgroundNoiseConfigPreset","description":"Pre-mixed ambient bed. Null disables background noise."},"volume":{"type":["string","null"],"title":"float","description":"Volume of the background-noise bed (0.0-1.0). Null disables."}},"description":"Optional ambient background-noise bed mixed into the call.","title":"AgentBackgroundNoiseConfig"},"WidgetConfigStyle":{"type":"string","enum":["pill","fab"],"title":"WidgetConfigStyle"},"WidgetConfigTheme":{"type":"string","enum":["dark","light","auto"],"title":"WidgetConfigTheme"},"WidgetConfigAvatarType":{"type":"string","enum":["orb","image"],"title":"WidgetConfigAvatarType"},"WidgetConfigAvatar":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/WidgetConfigAvatarType"},"image_url":{"type":"string"},"orb_color_1":{"type":"string"},"orb_color_2":{"type":"string"}},"title":"WidgetConfigAvatar"},"WidgetConfigText":{"type":"object","properties":{"start_call":{"type":"string"},"end_call":{"type":"string"},"listening":{"type":"string"},"thinking":{"type":"string"},"speaking":{"type":"string"}},"title":"WidgetConfigText"},"WidgetConfigTerms":{"type":"object","properties":{"enabled":{"type":"boolean"},"content":{"type":"string"}},"title":"WidgetConfigTerms"},"WidgetConfigTranscript":{"type":"object","properties":{"enabled":{"type":"boolean"}},"title":"WidgetConfigTranscript"},"WidgetConfigTelemetry":{"type":"object","properties":{"enabled":{"type":"boolean"}},"description":"Controls whether the embedded widget reports anonymous\noperational events (mounted, connected, first audio, failure\ncodes) back to Speechify. Omit the object to leave telemetry\non, which is the default: it is how browser-side failures a\nserver log cannot see - a denied microphone prompt, a blocked\nautoplay, a visitor who gave up mid-connect - become\ndiagnosable. No page URL, page content, transcript, or\ncross-site identifier is ever collected.\n","title":"WidgetConfigTelemetry"},"WidgetConfig":{"type":"object","properties":{"version":{"type":"integer"},"style":{"$ref":"#/components/schemas/WidgetConfigStyle"},"theme":{"$ref":"#/components/schemas/WidgetConfigTheme"},"avatar":{"$ref":"#/components/schemas/WidgetConfigAvatar"},"text":{"$ref":"#/components/schemas/WidgetConfigText"},"terms":{"$ref":"#/components/schemas/WidgetConfigTerms"},"transcript":{"$ref":"#/components/schemas/WidgetConfigTranscript"},"telemetry":{"$ref":"#/components/schemas/WidgetConfigTelemetry","description":"Controls whether the embedded widget reports anonymous\noperational events (mounted, connected, first audio, failure\ncodes) back to Speechify. Omit the object to leave telemetry\non, which is the default: it is how browser-side failures a\nserver log cannot see - a denied microphone prompt, a blocked\nautoplay, a visitor who gave up mid-connect - become\ndiagnosable. No page URL, page content, transcript, or\ncross-site identifier is ever collected.\n"}},"description":"Customer-editable appearance + behaviour payload for the\nembedded `` pill: button text, avatar style,\norb colours, terms-and-conditions markdown, transcript display.\nEvery field is optional - empty fields fall back to the\nwidget's compile-time defaults.\n","title":"WidgetConfig"},"AmdConfigOnVoicemailAction":{"type":"string","enum":["hangup","leave_message"],"title":"AmdConfigOnVoicemailAction"},"AmdConfigOnVoicemail":{"type":"object","properties":{"action":{"$ref":"#/components/schemas/AmdConfigOnVoicemailAction"},"message":{"type":"string","maxLength":4000,"description":"Spoken before terminating when action=leave_message.\nSupports {{variable}} substitution. Required (non-empty)\nwhen action=leave_message; rejected by the validator\notherwise.\n"}},"required":["action"],"description":"Action when AMD returns category=machine-vm.","title":"AmdConfigOnVoicemail"},"AmdConfigOnIvrAction":{"type":"string","enum":["proceed","hangup","navigate"],"description":"proceed: hand control to the agent's flow as if the\ncalled party were human. hangup: terminate immediately.\nnavigate: hand control to the IVR Navigator subagent\nwith menu-memoization-aware session config (cache hit\nseeds the agent context; cache miss triggers cold\ndiscovery and the post-call pipeline extracts the\nmenu for future calls).\n","title":"AmdConfigOnIvrAction"},"AmdConfigOnIvr":{"type":"object","properties":{"action":{"$ref":"#/components/schemas/AmdConfigOnIvrAction","description":"proceed: hand control to the agent's flow as if the\ncalled party were human. hangup: terminate immediately.\nnavigate: hand control to the IVR Navigator subagent\nwith menu-memoization-aware session config (cache hit\nseeds the agent context; cache miss triggers cold\ndiscovery and the post-call pipeline extracts the\nmenu for future calls).\n"}},"required":["action"],"description":"Action when AMD returns category=machine-ivr.","title":"AmdConfigOnIvr"},"AmdConfigOnUnavailableAction":{"type":"string","enum":["hangup"],"title":"AmdConfigOnUnavailableAction"},"AmdConfigOnUnavailable":{"type":"object","properties":{"action":{"$ref":"#/components/schemas/AmdConfigOnUnavailableAction"}},"required":["action"],"description":"Action when AMD returns category=machine-unavailable (mailbox full or disconnected).","title":"AmdConfigOnUnavailable"},"AmdConfigTuning":{"type":"object","properties":{"human_speech_threshold_seconds":{"type":"number","format":"double","minimum":0.5,"maximum":10},"no_speech_threshold_seconds":{"type":"number","format":"double","minimum":1,"maximum":30},"timeout_seconds":{"type":"number","format":"double","minimum":5,"maximum":30},"classification_prompt":{"type":"string","maxLength":8000}},"description":"Optional overrides for LiveKit's detection thresholds and\ntimeouts. Cross-field rule (enforced at the application\nvalidator): `timeout_seconds` must be greater than or equal\nto `no_speech_threshold_seconds` when both are set.\n","title":"AmdConfigTuning"},"AMDConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"When false, the worker skips AMD entirely. When true, the\nworker runs AMD on the called party's greeting before\ndelivering the agent's first message and dispatches per\nresult.category. The per-route fields below are still\nrequired by the schema regardless of `enabled` state so a\ncustomer flipping `enabled: false \u2192 true` ships coherent\nroute configuration immediately.\n"},"on_voicemail":{"$ref":"#/components/schemas/AmdConfigOnVoicemail","description":"Action when AMD returns category=machine-vm."},"on_ivr":{"$ref":"#/components/schemas/AmdConfigOnIvr","description":"Action when AMD returns category=machine-ivr."},"on_unavailable":{"$ref":"#/components/schemas/AmdConfigOnUnavailable","description":"Action when AMD returns category=machine-unavailable (mailbox full or disconnected)."},"tuning":{"$ref":"#/components/schemas/AmdConfigTuning","description":"Optional overrides for LiveKit's detection thresholds and\ntimeouts. Cross-field rule (enforced at the application\nvalidator): `timeout_seconds` must be greater than or equal\nto `no_speech_threshold_seconds` when both are set.\n"}},"required":["enabled","on_voicemail","on_ivr","on_unavailable"],"description":"Answering Machine Detection routing config for outbound voice\nagents. AMD classifies the called party's first ~3-15 seconds of\naudio into one of LiveKit's categories (human, uncertain,\nmachine-vm, machine-ivr, machine-unavailable) and dispatches per\ncategory to the configured action. Stored on the agent row;\nflowed onto outbound dispatch metadata under the `amd` key.\n","title":"AMDConfig"},"AIDisclosure":{"type":"object","properties":{"enabled":{"type":"boolean","description":"When true, `line` is spoken at the start of the call. Defaults to false."},"line":{"type":"string","maxLength":500,"description":"The disclosure spoken before the greeting (e.g. \"This call is\nhandled by an AI assistant.\"). Required when `enabled` is true.\n"}},"required":["enabled"],"description":"Opt-in AI-disclosure opening line. When `enabled` is true, `line` is\nspoken at the very start of the call, before the greeting - ahead of\njurisdictions that require callers to be told they are speaking with an\nautomated system. Off by default; existing agents and numbers are\nunaffected until they turn it on.\n","title":"AIDisclosure"},"Agent":{"type":"object","properties":{"id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`).\nThis is the sole customer-facing\nidentifier. URL paths accept only this prefixed form; legacy\nUUID path parameters are rejected with 404.\n"},"name":{"type":"string"},"slug":{"type":"string"},"channels":{"type":"array","items":{"$ref":"#/components/schemas/AgentChannel"},"description":"The delivery channels this agent serves. Empty means no front door: the agent is reached by its triggers only. Mutable via PATCH."},"prompt":{"type":"string"},"knowledge_instruction":{"type":"string","maxLength":1000,"description":"A short steer for how the agent uses its knowledge bases: what to\nlook up first, what never to quote, how to answer when nothing\nmatches. It reaches the model as guidance under the prompt and\nshapes the per-turn retrieval query on every channel: live\ncalls, text conversations and agent tests. Empty means none.\n"},"first_message":{"type":"string","description":"Spoken verbatim at session start when present in the customer's flow graph."},"language":{"type":"string","description":"ISO 639-1 code, e.g. 'en'. The agent's default language."},"additional_languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentAdditionalLanguage"},"description":"Extra languages this agent serves in the same session beyond\nits default `language`. When non-empty the agent can switch\nlanguage mid-call (triggered when the caller speaks one of\nthese languages or asks for it). Always present on reads;\nempty array for single-language agents.\n"},"llm":{"$ref":"#/components/schemas/AgentLLMConfig"},"tts":{"$ref":"#/components/schemas/AgentTTSConfig"},"turn_handling":{"$ref":"#/components/schemas/AgentTurnHandlingConfig"},"memory":{"$ref":"#/components/schemas/AgentMemoryConfig"},"navigator":{"$ref":"#/components/schemas/AgentNavigatorConfig"},"guardrails":{"$ref":"#/components/schemas/AgentGuardrailsConfig"},"background_noise":{"$ref":"#/components/schemas/AgentBackgroundNoiseConfig"},"widget_config":{"$ref":"#/components/schemas/WidgetConfig"},"is_public":{"type":"boolean","description":"When true, the `` web component can start a\nsession against this agent without an API key, subject to\nthe `allowed_origins` allowlist. When false (default), only\nauthenticated callers can start sessions.\n"},"allowed_origins":{"type":"array","items":{"type":"string"},"description":"Exact `Origin` header values (e.g. `https://example.com`)\nthat are allowed to start public sessions. Empty array\nwith `is_public = true` means any origin is accepted \u2014\nintended for open demos. No subdomain wildcards.\n"},"hostname_allowlist":{"type":["array","null"],"items":{"type":"string"},"description":"Optional per-agent hostname allowlist enforced at\nsession-creation time. When set and non-empty, the\n`Origin` header's hostname must be an exact member.\nBare hostnames only \u2014 no scheme, port, or path. Up to\n10 entries. Omit (null) or leave empty for no\nenforcement (public agents accept any hostname).\n"},"webhook_url":{"type":"string","description":"Per-agent override of the workspace webhook endpoints for this\nagent's post-call event. When non-empty, the control plane POSTs the\nstandard signed `WebhookEvent` envelope here once the conversation\ncompletes. This is the same shape workspace endpoints receive:\n`data.object` is the completed Conversation (equal to a direct GET),\nwith the full transcript under `data.messages` and evaluations +\ndata extractors under `data.evaluations`. The workspace endpoints'\n`conversation.completed` is suppressed for this agent (the override\nwins, so the two planes no longer both fire). Empty routes\n`conversation.completed` to your workspace webhook endpoints instead.\n`conversation.started` / `conversation.failed` always ride the\nworkspace endpoints regardless of this field.\n"},"webhook_secret_set":{"type":"boolean","description":"True when an HMAC-SHA256 webhook secret is configured. The\nsecret itself is write-only \u2014 supplied on PATCH and never\nechoed back on reads.\n"},"amd":{"$ref":"#/components/schemas/AMDConfig"},"max_call_duration_seconds":{"type":["integer","null"],"minimum":1,"description":"Hard cap on the wall-clock length of a single call on this\nagent, in seconds. When a call reaches it the agent ends the\ncall automatically. Voice agents only. Null means no\nper-agent cap: the call is bounded only by your plan's call\nceiling, which is also the hard upper bound for this field -\na value above it is rejected.\n"},"save_audio_recording":{"type":"boolean","description":"When true, every conversation produces a room-composite\nOGG egress uploaded to the recordings bucket. Defaults\nFALSE for new agents (privacy by default).\n"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"ai_disclosure":{"$ref":"#/components/schemas/AIDisclosure"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","channels","prompt","first_message","language","llm","tts","turn_handling","memory","navigator","guardrails","background_noise","is_public","allowed_origins","amd","save_audio_recording","ai_disclosure","created_at","updated_at"],"title":"Agent"},"ListAgentsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"agents":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}}},"required":["next_cursor","has_more","agents"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentsResponse"},"CreateAgentRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"name":{"type":"string","minLength":1,"maxLength":100},"slug":{"type":"string","maxLength":64,"description":"Optional. Server derives slug from name with a random suffix when omitted; if you supply your own, a collision returns 400 'slug already taken'."},"channels":{"type":"array","items":{"$ref":"#/components/schemas/AgentChannel"},"description":"The delivery channels the agent serves. Defaults to `[voice]` when\nomitted. Pass an explicit `[]` for an agent with no front door,\nreached by its triggers only. Serving `voice` requires a\n`tts.voice_id`.\n"},"prompt":{"type":"string"},"knowledge_instruction":{"type":"string","maxLength":1000,"description":"A short steer for how the agent uses its knowledge bases: what to\nlook up first, what never to quote, how to answer when nothing\nmatches. It reaches the model as guidance under the prompt and\nshapes the per-turn retrieval query on every channel: live\ncalls, text conversations and agent tests. Empty means none.\n"},"first_message":{"type":"string","description":"Greeting spoken verbatim at session start when included in the agent's flow graph. Omit for an agent with no channels, which opens no live turn."},"language":{"type":"string","description":"ISO 639-1 code. Defaults to 'en' when omitted. The agent's default language."},"additional_languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentAdditionalLanguage"},"description":"Extra languages this agent serves in the same session.\nEach entry's language must be supported, unique, and\ndifferent from the default `language`.\n"},"llm":{"$ref":"#/components/schemas/AgentLLMConfig"},"tts":{"$ref":"#/components/schemas/AgentTTSConfig"},"turn_handling":{"$ref":"#/components/schemas/AgentTurnHandlingConfig"},"memory":{"$ref":"#/components/schemas/AgentMemoryConfig"},"navigator":{"$ref":"#/components/schemas/AgentNavigatorConfig"},"guardrails":{"$ref":"#/components/schemas/AgentGuardrailsConfig"},"background_noise":{"$ref":"#/components/schemas/AgentBackgroundNoiseConfig"},"widget_config":{"$ref":"#/components/schemas/WidgetConfig"},"is_public":{"type":"boolean","description":"Defaults to false when omitted."},"allowed_origins":{"type":"array","items":{"type":"string"}},"hostname_allowlist":{"type":"array","items":{"type":"string"},"description":"Optional per-agent hostname allowlist (see Agent schema)."},"webhook_url":{"type":"string","description":"Customer-facing post-call webhook URL."},"webhook_secret":{"type":"string","description":"HMAC-SHA256 secret seed. Write-only \u2014 never echoed back on\nreads; clients see `webhook_secret_set: true` instead.\n"},"amd":{"$ref":"#/components/schemas/AMDConfig","description":"AMD routing config. Optional on create; omitted means AMD off. See AMDConfig schema."},"max_call_duration_seconds":{"type":["integer","null"],"minimum":1,"description":"Hard cap on the wall-clock length of a single call on this\nagent, in seconds. When a call reaches it the agent ends the\ncall automatically. Voice agents only. Null means no\nper-agent cap: the call is bounded only by your plan's call\nceiling, which is also the hard upper bound for this field -\na value above it is rejected.\n"},"save_audio_recording":{"type":"boolean","description":"When set, opts the agent into per-conversation audio recording. Defaults to false when omitted."},"ai_disclosure":{"$ref":"#/components/schemas/AIDisclosure","description":"Opt-in AI-disclosure opening line. Optional on create; omitted means off. See AIDisclosure schema."}},"required":["name","prompt"],"title":"CreateAgentRequest"},"AgentVoiceType":{"type":"string","enum":["shared","personal"],"description":"Voice provenance. `shared` is the curated catalogue every workspace\nsees; `personal` is a voice this workspace cloned itself (workspace-\nowned clones only). Both can be assigned to an agent. Treat this as an\nopen set - new values may be added, so branch on the ones you know and\nfall through for the rest.\n","title":"AgentVoiceType"},"AgentVoiceModelName":{"type":"string","enum":["simba-3.0","simba-3.2","simba-3.5-turbo"],"title":"AgentVoiceModelName"},"AgentVoiceLanguage":{"type":"object","properties":{"locale":{"type":"string","description":"BCP-47-ish locale tag (e.g. `en-US`, `de-DE`)."},"preview_audio":{"type":["string","null"],"description":"URL to a short audio preview for this locale, or null if\nno preview is available.\n"}},"required":["locale","preview_audio"],"title":"AgentVoiceLanguage"},"AgentVoiceModel":{"type":"object","properties":{"name":{"$ref":"#/components/schemas/AgentVoiceModelName"},"languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentVoiceLanguage"}}},"required":["name","languages"],"description":"One Simba model version the voice can be synthesised through, and\ntherefore one an agent on this voice may pin with `tts.model`.\nEvery agent voice supports `simba-3.0`. English-only upgrade tiers\n(`simba-3.5-turbo`, `simba-3.2`) are listed when this voice can\nselect them: a tier with `requires_voice_curation` only where the\nvoice is curated for it, a tier without it on any voice we speak\nourselves EXCEPT one built for a curated roster, whose speaker\nembedding is valid for that training alone. A partner voice lists no\ntier at all - Simba model selection does not apply to it.\n\nSo read this array rather than inferring membership from\n`requires_voice_curation`: a voice can be absent from a model that\nneeds no curation. It is the per-voice answer for every model, and a\npin outside it is a 400. Which of these `tts.model: null`\nactually resolves to is `default_model`, not the order here.\n\nLike `default_model`, the tiers listed here are the ENGLISH answer,\nand they are a fact about the voice rather than about any one agent.\nAn agent that resolves through the multilingual serving (it declares\n`additional_languages`, or its own `language` is not English) can\nselect none of them, whatever this array says - no upgrade tier has a\nmultilingual deployment. That is `english_only` on\nGET /v1/agents/tts-models, and a picker has to apply it as well as\nthis array. The voice's own `locale` is not what decides: the bucket\nfollows the AGENT's language, so an English agent may pin an\nEnglish-only tier on a voice of any locale.\nThe English-vs-multilingual split is otherwise an internal routing\ndetail the worker resolves per call, not a selectable model.\n\nTreat this as an open set - new model versions are added here as\nthey ship, so branch on the ones you know and fall through for\nthe rest.\n","title":"AgentVoiceModel"},"AgentVoiceGender":{"type":"string","enum":["male","female","not_specified"],"description":"Speaker gender classification for the voice. `not_specified` is\nused when the source dataset didn't carry the metadata; treat it\nas a neutral display label rather than a filter gap.\n","title":"AgentVoiceGender"},"AgentVoice":{"type":"object","properties":{"id":{"type":"string","description":"Voice slug. Passed verbatim as `voice_id` on agent writes."},"type":{"$ref":"#/components/schemas/AgentVoiceType"},"display_name":{"type":"string"},"models":{"type":"array","items":{"$ref":"#/components/schemas/AgentVoiceModel"}},"default_model":{"type":"string","description":"The model an agent on this voice synthesizes on when\n`tts.model` is null - what \"automatic\" resolves to here, stated\noutright rather than inferred from the order of `models`.\n\nThis is the English answer. An agent that resolves through the\nmultilingual serving (it declares `additional_languages`, or\nits own `language` is not English) always runs on `simba-3.0`,\nbecause no other model has a multilingual deployment.\n"},"gender":{"$ref":"#/components/schemas/AgentVoiceGender"},"locale":{"type":"string","description":"Default locale for the voice (BCP-47-ish, e.g. `en-US`)."},"preview_audio":{"type":["string","null"],"description":"Preferred preview clip URL, locale-matched when possible."},"avatar_image":{"type":["string","null"],"description":"Avatar URL for the picker UI. Null when no avatar is\nconfigured; the wire is intentionally `null` rather than\n`\"\"` so the picker doesn't render a broken ` `.\n"},"tags":{"type":["array","null"],"items":{"type":"string"},"description":"VMS-defined tags (e.g. `narrator`, `young`)."}},"required":["id","type","display_name","models","default_model","gender","locale","preview_audio","avatar_image"],"description":"One row in the curated voice catalogue returned by\n`GET /v1/agents/voices`, which is the recommended set to pick from.\nAgent writes may additionally accept a Speechify voice this listing\ndoes not merchandise, so a slug missing here is not necessarily\nrejected.\n","title":"AgentVoice"},"MultilingualLanguage":{"type":"object","properties":{"language":{"type":"string","description":"BCP-47 tag to declare, at the granularity that changes what a\ncaller hears: a locale where the accent is a real choice\n(`es-MX`, `pt-BR`), a plain subtag where it is not (`pl`). The\ntag reaches the speech model as-is. Only one entry per language\nmay be declared on an agent, so `es-MX` and `es-ES` are\nalternatives rather than a pair.\n"},"realtime_supported":{"type":"boolean","description":"False for languages the audio-native realtime model cannot\nserve. Declaring one on a realtime agent is rejected at write\ntime, so pickers should disable it rather than let the save\nfail.\n"},"default_voice_id":{"type":"string","description":"Voice used when the language carries no per-language\n`voice_id`. Omitted when the agent's own voice speaks the\nlanguage, which is what preserves voice identity across a\nswitch.\n"}},"required":["language","realtime_supported"],"description":"One language available to multilingual agents. Which backend\nserves a language is an internal routing decision and is not\nexposed; what callers need is whether it survives on the realtime\npipeline and which voice speaks it by default.\n","title":"MultilingualLanguage"},"ListAgentVoicesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"voices":{"type":"array","items":{"$ref":"#/components/schemas/AgentVoice"}},"multilingual_languages":{"type":"array","items":{"$ref":"#/components/schemas/MultilingualLanguage"},"description":"Every language an agent may declare in `additional_languages`.\nRender language pickers from this rather than a hard-coded\nlist: it is derived from the same routing the write path\nvalidates against, so it can never offer a language a save\nwould reject.\n"}},"required":["next_cursor","has_more","voices","multilingual_languages"],"description":"Payload for `GET /v1/agents/voices`: the curated shared set (the\n`ai-api-agents` VMS scope plus a handful of builtins) followed by the\ncalling workspace's cloned voices. Cursor-paginated - walk pages while\n`has_more` is true.\n","title":"ListAgentVoicesResponse"},"AgentTTSModelInfo":{"type":"object","properties":{"id":{"type":"string","description":"The value to send as `tts.model`."},"display_name":{"type":"string"},"description":{"type":"string"},"beta":{"type":"boolean","description":"A training still under evaluation. Fully servable and supported;\nsimply not one agents are steered onto by default.\n"},"default":{"type":"boolean","description":"True for the model an agent lands on with no pin and an\nuncurated voice. The floor of the catalog, and the only entry\nselectable for every agent and every voice.\n"},"english_only":{"type":"boolean","description":"The model has no multilingual deployment. An agent that resolves\nthrough the multilingual serving cannot select it at all.\n"},"requires_voice_curation":{"type":"boolean","description":"A voice must be registered for this model before it can be\nselected with that voice, because the model has a voice roster\nbuilt for it. False for a model offered across the catalog.\n\nEither way, the voice's `models` array on GET /v1/agents/voices is\nthe per-voice answer and the one to drive a picker from - a voice\nbuilt for a curated model is absent from the open models too. This\nflag explains WHY a voice is absent; it does not decide whether to\ncheck.\n"}},"required":["id","display_name","description","beta","default","english_only","requires_voice_curation"],"description":"One Simba model selectable with `tts.model`, plus the facts that\ndecide whether a given agent may select it.\n","title":"AgentTTSModelInfo"},"AgentTTSModelsResponse":{"type":"object","properties":{"models":{"type":"array","items":{"$ref":"#/components/schemas/AgentTTSModelInfo"}}},"required":["models"],"description":"Payload for `GET /v1/agents/tts-models`.","title":"AgentTTSModelsResponse"},"AgentPhoneNumberReferenceResourceType":{"type":"string","enum":["phone_number"],"description":"Always `phone_number`.","title":"AgentPhoneNumberReferenceResourceType"},"AgentPhoneNumberReference":{"type":"object","properties":{"resource_type":{"$ref":"#/components/schemas/AgentPhoneNumberReferenceResourceType","description":"Always `phone_number`."},"id":{"type":"string","pattern":"^[a-z][a-z_]*_[0-9a-hjkmnp-tv-z]{26}$","description":"The number's prefixed external id."},"name":{"type":"string","description":"The number in E.164."}},"required":["resource_type","id","name"],"description":"One live phone number whose binding pins the agent.","title":"AgentPhoneNumberReference"},"AgentPhoneNumberUsedBy":{"type":"object","properties":{"count":{"type":"integer","description":"Number of live phone numbers bound to this agent."},"references":{"type":"array","items":{"$ref":"#/components/schemas/AgentPhoneNumberReference"}}},"required":["count","references"],"description":"The live phone numbers routing to the agent. `count` is the total;\n`references` lists them.\n","title":"AgentPhoneNumberUsedBy"},"AgentInUseErrorDetailDetails":{"type":"object","properties":{"used_by":{"$ref":"#/components/schemas/AgentPhoneNumberUsedBy"}},"required":["used_by"],"description":"Structured, endpoint-specific context beyond the flat\n`fields` map. Present only on the few errors that carry\nit (e.g. the `used_by` referrer list on a credential\ndelete-conflict); its shape depends on the error `code`.\nClients that don't recognise a `details` shape can ignore\nit - the `code` + `message` contract is unchanged.\n","title":"AgentInUseErrorDetailDetails"},"AgentInUseErrorDetail":{"type":"object","properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable explanation of this specific occurrence.\nSafe to surface in UI banners or pass to support. The\nwording can change between releases; clients should\nmatch on `code`, not on the message string.\n"},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Per-field validation errors as `path -> message`. Only\npresent on 400 responses caused by request validation\n(typically code=`validation_failed`). Keys are field\npaths in dotted/bracket notation; values are short\nhuman explanations safe to inline-surface next to the\noffending form field.\n"},"details":{"$ref":"#/components/schemas/AgentInUseErrorDetailDetails","description":"Structured, endpoint-specific context beyond the flat\n`fields` map. Present only on the few errors that carry\nit (e.g. the `used_by` referrer list on a credential\ndelete-conflict); its shape depends on the error `code`.\nClients that don't recognise a `details` shape can ignore\nit - the `code` + `message` contract is unchanged.\n"},"docs_url":{"type":"string","format":"uri","description":"Link to the documentation that resolves this class of\nerror, when a stable page exists. Rate and concurrency\n429s link the API limits reference, which lists each\nplan's limits and how to raise them.\n"}},"required":["code","message","details"],"description":"The error block on an agent-in-use 409: the standard `ErrorDetail`\n(`code` = `agent_in_use`) whose structured `details.used_by` names\nthe numbers to unbind first.\n","title":"AgentInUseErrorDetail"},"AgentInUseError":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/AgentInUseErrorDetail"},"request_id":{"type":"string"}},"required":["error"],"description":"409 body when an agent delete is refused because a live phone number\nstill routes to it. The standard `Error` envelope (so clients read\n`error.code` = `agent_in_use` and `request_id`); the numbers to\nunbind ride the structured `error.details.used_by` extension.\n","title":"AgentInUseError"},"UpdateAgentRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"Move the resource between workspace projects: a `proj_...` id\nmoves it there, an explicit null moves it back to the implicit\nDefault project, omitted leaves it unchanged.\n"},"name":{"type":"string","minLength":1,"maxLength":100},"prompt":{"type":"string"},"knowledge_instruction":{"type":"string","maxLength":1000,"description":"A short steer for how the agent uses its knowledge bases: what to\nlook up first, what never to quote, how to answer when nothing\nmatches. It reaches the model as guidance under the prompt and\nshapes the per-turn retrieval query on every channel: live\ncalls, text conversations and agent tests. Empty means none.\n"},"first_message":{"type":"string"},"language":{"type":"string"},"channels":{"type":"array","items":{"$ref":"#/components/schemas/AgentChannel"},"description":"Replaces the agent's enabled delivery channels. Adding `voice`\nrequires the agent to have a `tts.voice_id` (already set or set in\nthe same request). Omit to leave channels unchanged.\n"},"additional_languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentAdditionalLanguage"},"description":"Replaces the stored list wholesale when present; an empty\narray clears it back to a single-language agent. Omit to\nleave unchanged.\n"},"llm":{"$ref":"#/components/schemas/AgentLLMConfig"},"tts":{"$ref":"#/components/schemas/AgentTTSConfig"},"turn_handling":{"$ref":"#/components/schemas/AgentTurnHandlingConfig"},"memory":{"$ref":"#/components/schemas/AgentMemoryConfig"},"navigator":{"$ref":"#/components/schemas/AgentNavigatorConfig"},"guardrails":{"$ref":"#/components/schemas/AgentGuardrailsConfig"},"background_noise":{"$ref":"#/components/schemas/AgentBackgroundNoiseConfig"},"widget_config":{"$ref":"#/components/schemas/WidgetConfig"},"is_public":{"type":"boolean"},"allowed_origins":{"type":"array","items":{"type":"string"}},"hostname_allowlist":{"type":"array","items":{"type":"string"},"description":"When supplied, replaces the stored list. Pass an empty\narray to clear enforcement (public agent is open again).\nOmit the field to leave the existing value unchanged.\n"},"webhook_url":{"type":"string"},"webhook_secret":{"type":"string","description":"Rotate the HMAC secret. Write-only."},"amd":{"$ref":"#/components/schemas/AMDConfig","description":"AMD routing config (PATCH-replace, wholesale). Omit to leave the stored config unchanged."},"max_call_duration_seconds":{"type":["integer","null"],"minimum":1,"description":"Hard cap on the wall-clock length of a single call on this\nagent, in seconds. When a call reaches it the agent ends the\ncall automatically. Voice agents only. Null means no\nper-agent cap: the call is bounded only by your plan's call\nceiling, which is also the hard upper bound for this field -\na value above it is rejected. On a PATCH, null clears a\npreviously set cap.\n"},"save_audio_recording":{"type":"boolean"},"ai_disclosure":{"$ref":"#/components/schemas/AIDisclosure","description":"Opt-in AI-disclosure opening line (PATCH-replace, wholesale). Omit to leave the stored config unchanged."}},"description":"Body for PATCH /v1/agents/{agent_id}. JSON merge-patch semantics: every\nfield is optional and omitting it leaves the stored value\nunchanged. Inside a nested config block (llm, tts,\nturn_handling, memory, navigator, background_noise, amd) send only\nthe sub-fields you want to change - an explicit null clears a\nnullable sub-field to its default, a value sets it, and omitted\nsub-fields are left as-is. `slug` is intentionally not patchable\n(changing it would break embed URLs).\n","title":"UpdateAgentRequest"},"agent_flow_getSchema_Response_200":{"type":"object","properties":{},"title":"agent_flow_getSchema_Response_200"},"PublishGateResultDecision":{"type":"string","enum":["passed","overridden","skipped_no_suite"],"description":"The recorded gate decision on a successful publish. A refused\npublish is a 422 (`agent_publish_gate_failed`), so `blocked` /\n`errored` never appear here.","title":"PublishGateResultDecision"},"EvaluationCaseVerdictOutcome":{"type":"string","enum":["passed","failed","errored"],"description":"`errored` = the run could not complete, so the behaviour was not judged (distinct from `failed`).","title":"EvaluationCaseVerdictOutcome"},"EvaluationCriterionVerdictStatus":{"type":"string","enum":["success","failure","unknown"],"description":"`unknown` = the condition did not apply to this run (distinct from `failure`).","title":"EvaluationCriterionVerdictStatus"},"EvaluationCriterionVerdict":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"$ref":"#/components/schemas/EvaluationCriterionVerdictStatus","description":"`unknown` = the condition did not apply to this run (distinct from `failure`)."},"score":{"type":"string","title":"float"},"rationale":{"type":"string"}},"required":["id","name","status","score","rationale"],"description":"One scored condition, uniform across case types.","title":"EvaluationCriterionVerdict"},"EvaluationCaseVerdict":{"type":"object","properties":{"case_id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"outcome":{"$ref":"#/components/schemas/EvaluationCaseVerdictOutcome","description":"`errored` = the run could not complete, so the behaviour was not judged (distinct from `failed`)."},"passed":{"type":"boolean"},"rationale":{"type":"string"},"duration_ms":{"type":"integer","format":"int64"},"criteria":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationCriterionVerdict"}},"result":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Full per-type run detail (transcript, tool calls, per-type\nresult), for a consumer that needs the synthetic conversation.\nPresent when the case ran; absent on an errored case."}},"required":["case_id","name","type","outcome","passed","rationale","duration_ms","criteria"],"description":"One case's outcome with a uniform per-criterion breakdown.","title":"EvaluationCaseVerdict"},"EvaluationVerdict":{"type":"object","properties":{"passed":{"type":"boolean"},"total":{"type":"integer"},"passed_count":{"type":"integer"},"failed_count":{"type":"integer"},"errored_count":{"type":"integer"},"cases":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationCaseVerdict"}}},"required":["passed","total","passed_count","failed_count","errored_count","cases"],"description":"The gate-branchable result: an overall pass/fail plus a per-case\nbreakdown. `passed` is true only when every case passed \u2014 a failed\nor errored case fails the verdict, so a gate fails safe when a case\ncould not be judged.","title":"EvaluationVerdict"},"PublishGateResult":{"type":"object","properties":{"decision":{"$ref":"#/components/schemas/PublishGateResultDecision","description":"The recorded gate decision on a successful publish. A refused\npublish is a 422 (`agent_publish_gate_failed`), so `blocked` /\n`errored` never appear here."},"message":{"type":"string","description":"Human-readable summary of the gate outcome."},"verdict":{"$ref":"#/components/schemas/EvaluationVerdict"},"suite_size":{"type":"integer","description":"How many behavioral cases were scored."},"duration_ms":{"type":"integer","format":"int64","description":"Wall-clock time the gate evaluation took."}},"required":["decision"],"description":"The behavioral-eval gate outcome recorded for a publish. Present only\nwhen the workspace is opted into the publish gate.","title":"PublishGateResult"},"FlowVersion":{"type":"object","properties":{"id":{"type":"string","pattern":"^fver_[0-9a-hjkmnp-tv-z]{26}$","description":"Flow version id (prefixed external id, `fver_...`)."},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"version":{"type":"integer","description":"Monotonic revision number within the agent."},"parent_version_id":{"type":["string","null"],"pattern":"^fver_[0-9a-hjkmnp-tv-z]{26}$","description":"Parent flow version id (prefixed external id, `fver_...`); null for the first version."},"is_active":{"type":"boolean"},"is_draft":{"type":"boolean"},"name":{"type":"string"},"notes":{"type":"string"},"published_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"gate":{"$ref":"#/components/schemas/PublishGateResult"}},"required":["id","agent_id","version","is_active","is_draft"],"description":"One published or draft revision of an agent's flow graph.","title":"FlowVersion"},"FlowGraphNodesItems":{"type":"object","properties":{},"title":"FlowGraphNodesItems"},"FlowGraphEdgesItems":{"type":"object","properties":{},"title":"FlowGraphEdgesItems"},"FlowGraphVariablesItems":{"type":"object","properties":{},"title":"FlowGraphVariablesItems"},"FlowGraph":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/FlowVersion"},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphNodesItems"}},"edges":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphEdgesItems"}},"variables":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphVariablesItems"}}},"required":["version","nodes","edges"],"description":"A flow graph: an ordered set of typed nodes connected by edges,\nplus flow variables. The node, edge, and variable shapes are\ngoverned by the live JSON Schema at GET /v1/agents/flow/schema\nand are intentionally opaque here so this spec cannot drift\nfrom that authoritative definition.\n","title":"FlowGraph"},"GetFlowResponse":{"type":"object","properties":{"draft":{"$ref":"#/components/schemas/FlowGraph"},"active":{"$ref":"#/components/schemas/FlowGraph"}},"description":"Response for GET /v1/agents/{agent_id}/flow.","title":"GetFlowResponse"},"PutFlowRequestNodesItems":{"type":"object","properties":{},"title":"PutFlowRequestNodesItems"},"PutFlowRequestEdgesItems":{"type":"object","properties":{},"title":"PutFlowRequestEdgesItems"},"PutFlowRequestVariablesItems":{"type":"object","properties":{},"title":"PutFlowRequestVariablesItems"},"PutFlowRequest":{"type":"object","properties":{"name":{"type":"string"},"notes":{"type":"string"},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/PutFlowRequestNodesItems"}},"edges":{"type":"array","items":{"$ref":"#/components/schemas/PutFlowRequestEdgesItems"}},"variables":{"type":"array","items":{"$ref":"#/components/schemas/PutFlowRequestVariablesItems"}}},"required":["nodes","edges"],"description":"Request body for PUT /v1/agents/{agent_id}/flow. Replaces the draft graph.","title":"PutFlowRequest"},"FlowValidationIssue":{"type":"object","properties":{"path":{"type":"string","description":"Node / edge / field path the issue applies to; drives editor highlighting."},"code":{"type":"string","description":"Stable per-issue code, e.g. `tool_call.tool_id.invalid`."},"message":{"type":"string"}},"required":["path","code","message"],"description":"One flow-graph validation problem, located by node/edge/field path.","title":"FlowValidationIssue"},"FlowValidationError":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"},"request_id":{"type":"string"},"issues":{"type":"array","items":{"$ref":"#/components/schemas/FlowValidationIssue"}}},"required":["error"],"description":"400 body for flow save / publish / template operations. The standard\n`Error` envelope (so clients read `error.code` = `validation_failed`\nand `request_id`) plus the per-issue `issues` array the flow editor\nuses for node highlighting and the Validator-tab list. `issues` is\nabsent on a plain bad request (e.g. an undecodable body, code\n`bad_request`).\n","title":"FlowValidationError"},"PublishFlowRequest":{"type":"object","properties":{"notes":{"type":"string","description":"Optional changelog note recorded on the published version."},"gate_override_reason":{"type":"string","description":"On a workspace with the behavioral-eval publish gate enabled, a\nnon-empty justification publishes past a failing behavioral suite.\nThe suite still runs and its verdict is recorded with this reason -\nan override bypasses blocking, not measurement."}},"description":"Optional body for POST /v1/agents/{agent_id}/flow/publish.","title":"PublishFlowRequest"},"RollbackFlowRequest":{"type":"object","properties":{"version_id":{"type":"string","description":"The flow version to roll back to (prefixed external id, `fver_...`)."},"gate_override_reason":{"type":"string","description":"On a workspace with the behavioral-eval publish gate enabled, a\nnon-empty justification re-activates a version past a failing\nbehavioral suite. The suite still runs and its verdict is recorded\nwith this reason - an override bypasses blocking, not measurement."}},"required":["version_id"],"title":"RollbackFlowRequest"},"ListFlowVersionsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"versions":{"type":"array","items":{"$ref":"#/components/schemas/FlowVersion"}}},"required":["next_cursor","has_more","versions"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListFlowVersionsResponse"},"FlowTemplate":{"type":"object","properties":{"id":{"type":"string","pattern":"^tmpl_[0-9a-hjkmnp-tv-z]{26}$","description":"Flow template id (prefixed external id, `tmpl_...`)."},"key":{"type":"string","description":"Stable unique key for the template."},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"graph":{"$ref":"#/components/schemas/FlowGraph"},"is_seed":{"type":"boolean","description":"True for platform-provided templates."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","key","name","category","graph","is_seed","created_at","updated_at"],"description":"A reusable flow graph that can be cloned onto an agent as a new draft.","title":"FlowTemplate"},"ListFlowTemplatesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"templates":{"type":"array","items":{"$ref":"#/components/schemas/FlowTemplate"}}},"required":["next_cursor","has_more","templates"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListFlowTemplatesResponse"},"FlowGraphInputNodesItems":{"type":"object","properties":{},"title":"FlowGraphInputNodesItems"},"FlowGraphInputEdgesItems":{"type":"object","properties":{},"title":"FlowGraphInputEdgesItems"},"FlowGraphInputVariablesItems":{"type":"object","properties":{},"title":"FlowGraphInputVariablesItems"},"FlowGraphInput":{"type":"object","properties":{"nodes":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphInputNodesItems"}},"edges":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphInputEdgesItems"}},"variables":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphInputVariablesItems"}}},"required":["nodes","edges"],"description":"Request-side flow graph: nodes, edges, and variables only.\nUnlike the response-side FlowGraph it carries no `version`\nblock - the server owns version metadata.\n","title":"FlowGraphInput"},"CreateFlowTemplateRequest":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string","description":"Defaults to \"custom\" when omitted."},"graph":{"$ref":"#/components/schemas/FlowGraphInput"}},"required":["key","name","graph"],"description":"Request body for creating (POST) or replacing (PATCH) a flow\ntemplate. PATCH replaces the whole template, it is not a\nfield-by-field patch.\n","title":"CreateFlowTemplateRequest"},"CloneFlowTemplateRequest":{"type":"object","properties":{"agent_id":{"type":"string","description":"The agent that receives the cloned graph as a new draft."}},"required":["agent_id"],"title":"CloneFlowTemplateRequest"},"EvaluationCriterion":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"}},"required":["id","name","description"],"description":"One LLM-scored assertion about the call (\"Did the agent confirm the customer's name?\").","title":"EvaluationCriterion"},"DataCollectionFieldType":{"type":"string","enum":["string","int","number","boolean"],"title":"DataCollectionFieldType"},"DataCollectionField":{"type":"object","properties":{"key":{"type":"string"},"description":{"type":"string"},"type":{"$ref":"#/components/schemas/DataCollectionFieldType"}},"required":["key","description","type"],"description":"A structured value the post-call evaluator should extract from the\ntranscript. `int` is distinct from `number` so downstream consumers\nreceive whole integers without a synthetic decimal.\n","title":"DataCollectionField"},"EvaluationConfig":{"type":"object","properties":{"criteria":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationCriterion"}},"data_collection":{"type":"array","items":{"$ref":"#/components/schemas/DataCollectionField"}}},"required":["criteria","data_collection"],"title":"EvaluationConfig"},"UpdateEvaluationConfigRequest":{"type":"object","properties":{"criteria":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationCriterion"}},"data_collection":{"type":"array","items":{"$ref":"#/components/schemas/DataCollectionField"}}},"required":["criteria","data_collection"],"title":"UpdateEvaluationConfigRequest"},"DynamicVariableType":{"type":"string","enum":["string","number","boolean","json"],"description":"Declared type of a customer-scope variable. Enforced at save time\nand again at session-start when an override value is supplied.\n- `string` - plain text value; interpolated verbatim with `{{name}}`\n- `number` - numeric value; rendered as its decimal representation\n- `boolean` - `true` or `false`\n- `json` - any valid JSON value; use `{{name|json}}` to inject\n safely inside JSON tool bodies\n","title":"DynamicVariableType"},"DynamicVariable":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-zA-Z0-9_]+$","description":"Variable name. Must match `[a-zA-Z0-9_]+`. The `system__` prefix\nis reserved for platform-populated variables and will be rejected.\n"},"type":{"$ref":"#/components/schemas/DynamicVariableType"},"default":{"description":"Optional default value used when no per-session override is\nsupplied. Must conform to the declared `type`.\n"},"description":{"type":"string","description":"Human-readable note describing the variable."}},"required":["key","type"],"description":"One customer-scope variable definition on an agent. Referenced in\nprompts, first messages, and webhook tool configs via `{{key}}` or\n`{{key|json}}`. Missing variables render as empty string at dispatch\ntime - a typo never breaks a session.\n","title":"DynamicVariable"},"SystemVariableDoc":{"type":"object","properties":{"key":{"type":"string","description":"The reserved variable key (always starts with `system__`)."},"description":{"type":"string","description":"What the variable contains and when it is populated."}},"required":["key","description"],"description":"Documents one reserved `system__*` variable that the platform\nauto-populates at session start. Customers cannot define or\noverride these keys.\n","title":"SystemVariableDoc"},"ListDynamicVariablesResponse":{"type":"object","properties":{"variables":{"type":"array","items":{"$ref":"#/components/schemas/DynamicVariable"},"description":"Customer-defined variables for this agent."},"system_variables":{"type":"array","items":{"$ref":"#/components/schemas/SystemVariableDoc"},"description":"Platform-populated `system__*` variables, provided for\nreference. This list is the same for every agent.\n"}},"required":["variables","system_variables"],"description":"Response for `GET /v1/agents/{agent_id}/variables`. Returns both the\ncustomer-scope variable catalogue and the read-only `system__*`\ncatalogue so a single source of truth is available client-side.\n","title":"ListDynamicVariablesResponse"},"UpdateDynamicVariablesRequest":{"type":"object","properties":{"variables":{"type":"array","items":{"$ref":"#/components/schemas/DynamicVariable"},"description":"The new variable list. Replaces the existing list entirely."}},"required":["variables"],"description":"PATCH body for `PATCH /v1/agents/{agent_id}/variables`. Replaces the\nstored variable list wholesale. Pass an empty array to clear all\nvariables. Up to 20 variables per agent.\n","title":"UpdateDynamicVariablesRequest"},"CreateConversationOverrides":{"type":"object","properties":{"voice_id":{"type":"string","description":"Voice to use for this entire conversation; see\n`GET /v1/agents/voices` for the recommended set.\nFor a multilingual agent, this replaces every\nconfigured per-language voice. The selected voice must support\nthe agent's complete configured language set; otherwise the\nrequest is rejected. An unavailable voice is never silently\nreplaced with the default.\n"}},"description":"Authenticated, per-conversation configuration overrides. Overrides are\nvalidated and applied before the agent is dispatched; the effective\nconfiguration is captured in the conversation's `agent_snapshot`.\n","title":"CreateConversationOverrides"},"CreateConversationRequest":{"type":"object","properties":{"transport":{"type":["string","null"],"description":"Voice transport for the session: one of `web`, `sip_inbound`, or `sip_outbound`. Omit (or null) to use the default, `web`. This endpoint dispatches a LiveKit voice session, so any other value is rejected with a 400."},"user_identity":{"type":"string","description":"Stable opaque identifier for the end-user in your application.\nPersisted verbatim as `caller_identity` for caller correlation\nand memory, but never exposed as the realtime participant\nidentity. The same value sent to an authenticated\n`POST /v1/agents/{agent_id}/sessions` is the same caller record.\nOmit to preserve `user_` as the caller\nkey.\n\nMay not begin with `embed_`, `anon_` or `user_`: those namespaces\nare reserved for identities the platform derives rather than\ntakes on trust, and claiming one is rejected with a 400 naming\n`user_identity`.\n"},"language":{"type":"string","description":"Starts the conversation in one of the agent's configured\nlanguages (the default `language` or an `additional_languages`\nentry, matched by primary subtag). Omit for the agent's default\nlanguage; an unconfigured language is rejected with 400.\n"},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-session variable overrides that merge on top of the agent's\nstored variable defaults for this one conversation. Keys in the\nreserved `system__` namespace are rejected. Values must match the\ndeclared type of the corresponding variable definition on the agent.\n"},"overrides":{"$ref":"#/components/schemas/CreateConversationOverrides"}},"description":"Optional body for `POST /v1/agents/{agent_id}/conversations`.","title":"CreateConversationRequest"},"ConversationStatus":{"type":"string","enum":["pending","active","completed","failed"],"title":"ConversationStatus"},"ConversationTransport":{"type":"string","enum":["web","phone","whatsapp","sip_inbound","sip_outbound","text"],"description":"How the caller reached the agent. `web` is the browser /\nSDK realtime path; the `sip_*` and `phone` variants come\nfrom the telephony stack; `text` is the text/chat channel\n(turn-based, roomless, no call duration).\n","title":"ConversationTransport"},"ConversationEndReason":{"type":"string","enum":["voicemail_message_left","voicemail_hangup","ivr_hangup","unavailable_hangup","agent_ended","caller_hangup","inactivity_timeout","loop_detected","max_duration_reached","flow_budget_exhausted","over_capacity","dial_no_answer","dial_busy","dial_rejected","dial_failed","transferred"],"description":"Coarse termination category. Most reasons are assigned by the\nagent runtime as the call ends; `caller_hangup` may also be\napplied server-side as a post-call catch-all. The `dial_*`\nreasons are assigned server-side on a `failed` conversation\nfor an outbound call that never connected.\n* `voicemail_message_left` \u2014 AMD machine-vm + we spoke the configured drop-message.\n* `voicemail_hangup` \u2014 AMD machine-vm + we terminated silently (action=hangup or empty-message bypass).\n* `ivr_hangup` \u2014 AMD machine-ivr + action=hangup.\n* `unavailable_hangup` \u2014 AMD machine-unavailable (mailbox full / disconnected).\n* `agent_ended` \u2014 LLM-driven end_call builtin.\n* `inactivity_timeout` \u2014 the call ended after the configured silence window elapsed with no activity.\n* `loop_detected` \u2014 a loop guard force-ended the call after several consecutive near-identical user turns (typically an IVR replaying its menu while the agent kept reacting instead of ending the call).\n* `max_duration_reached` - the max-call-duration limit force-ended the call at the platform ceiling (a safety bound on runaway calls).\n* `flow_budget_exhausted` \u2014 the agent's flow ran for longer than the total per-call flow-execution budget and was ended by the runtime. A backstop above every call-duration ceiling; a call that reaches it has been looping rather than conversing.\n* `over_capacity` \u2014 inbound call refused because the workspace was over its active-call concurrency cap; the busy message played and the call hung up. Stamped server-side and excluded from billing.\n* `caller_hangup` \u2014 the caller's leg went away. Stamped immediately when a SIP disconnect is observed; otherwise applied server-side shortly after the call ends as a catch-all (web tab close, network blip, etc.).\n* `dial_no_answer` \u2014 outbound dial: callee did not pick up (SIP 408/480/487, the ringing timeout expired).\n* `dial_busy` \u2014 outbound dial: the line was busy (SIP 486/600).\n* `dial_rejected` \u2014 outbound dial: the call was actively refused (SIP 401/403/407 carrier auth/permission, or 603/607/608 callee decline).\n* `dial_failed` \u2014 outbound dial: any other failure to connect (invalid number, carrier 5xx, malformed trunk address, TLS requirement, transport error). On a `failed` conversation with NULL `duration_ms`.\n* `transferred` \u2014 the caller's leg was handed off to a phone number via SIP REFER (`transfer_to_number`); the carrier moved the leg and the agent's side ended.\n* `null` \u2014 the termination category was not recorded. Legacy calls only; current calls always carry a reason.\n","title":"ConversationEndReason"},"SkillPin":{"type":"object","properties":{"skill_id":{"type":"string","pattern":"^skill_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"version":{"type":"integer"}},"required":["skill_id","name","version"],"description":"Which procedure ran on a call, and at which version. A body is\nimmutable per version, so the pair reconstructs exactly what the model\nwas told however far the catalog has moved since.\n","title":"SkillPin"},"AgentSnapshotInterruptionSensitivity":{"type":"string","enum":["low","medium","high"],"description":"Interruption-sensitivity level the call ran under; null = the platform default.","title":"AgentSnapshotInterruptionSensitivity"},"AgentSnapshotBackgroundNoisePreset":{"type":"string","enum":["office","city","forest","crowded_room","keyboard_typing","hold_music"],"description":"Ambient-bed preset at call time; null = no background noise.","title":"AgentSnapshotBackgroundNoisePreset"},"AgentSnapshot":{"type":"object","properties":{"schema_version":{"type":"integer"},"captured_at":{"type":"string","format":"date-time"},"name":{"type":"string"},"prompt":{"type":"string"},"knowledge_instruction":{"type":"string","description":"The agent's knowledge instruction as captured, empty when none was set."},"first_message":{"type":"string"},"language":{"type":"string"},"channels":{"type":"array","items":{"$ref":"#/components/schemas/AgentChannel"},"description":"Delivery-channel set the agent served at call time."},"skills":{"type":"array","items":{"$ref":"#/components/schemas/SkillPin"},"description":"The procedures this call ran with, each pinned to the version whose\nbody was rendered into the prompt. `prompt` above is the agent's own\ninstructions only. Empty array when none were attached; an ABSENT\nkey means the snapshot pre-dates this field.\n"},"additional_languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentAdditionalLanguage"},"description":"Multilingual config as of call start. Absent on snapshots that pre-date the field and on single-language agents."},"llm_provider":{"type":"string","description":"Resolved provider that actually ran (a \"Platform default\"\nagent freezes the concrete platform pair at call time).\n"},"llm_model":{"type":"string"},"llm_base_url":{"type":["string","null"],"description":"Custom-provider endpoint base URL; null for managed providers. The bearer key is never captured."},"llm_extra_body":{"type":["object","null"],"additionalProperties":{"description":"Any type"},"description":"Extra chat.completions body forwarded verbatim for custom-provider agents; null otherwise."},"voice_id":{"type":"string"},"tts_model":{"type":["string","null"],"description":"The TTS model pinned at call start; null means the call ran on\nthe automatic choice. Records the configured pin, not the model\ndispatch settled on - a pin that had gone stale degrades at\ndispatch, and rewriting the snapshot to the degraded value\nwould hide the drift this field is read to find.\n"},"temperature":{"type":"string","title":"float"},"memory_enabled":{"type":"boolean"},"memory_retention_days":{"type":"integer"},"tts_playback_rate":{"type":["string","null"],"title":"float","description":"Post-process time-stretch at call time; null = no time-stretch (1x)."},"response_delay_seconds":{"type":["string","null"],"title":"float","description":"Minimum silence the agent waited for before replying, at call time; null = turn detection decided."},"interruption_sensitivity":{"$ref":"#/components/schemas/AgentSnapshotInterruptionSensitivity","description":"Interruption-sensitivity level the call ran under; null = the platform default."},"amd":{"$ref":"#/components/schemas/AMDConfig"},"save_audio_recording":{"type":"boolean"},"navigator_mode":{"type":"boolean"},"ivr_memory_enabled":{"type":"boolean"},"observer_guardrail_enabled":{"type":"boolean","description":"Whether the out-of-band injection guardrail was armed when\nthis call started. A conversation is only interpretable\nagainst whether the check was running at the time.\n"},"inactivity_timeout_seconds":{"type":["integer","null"],"description":"Silence-tolerance override at call time; null = platform default."},"max_call_duration_seconds":{"type":["integer","null"],"description":"Per-agent call cap as configured at call time; null = bounded\nonly by the plan ceiling. This is the configured value, not the\neffective one - a plan downgrade can lower what was enforced.\n"},"background_noise_preset":{"$ref":"#/components/schemas/AgentSnapshotBackgroundNoisePreset","description":"Ambient-bed preset at call time; null = no background noise."},"background_noise_volume":{"type":["string","null"],"title":"float"}},"description":"Frozen copy of the agent's behavioral configuration captured at\nconversation-create time so the detail view can\nrender historical calls accurately even after the live agent\nhas been edited. Carries its own `schema_version` because the\nsnapshot shape evolves independently of the live Agent shape.\n\nField-presence contract: new snapshots emit every field\n(explicit null when unset). A key that is ABSENT from a stored\nsnapshot means the snapshot pre-dates that field's capture;\nreaders hide the value instead of guessing a default.\n","title":"AgentSnapshot"},"ConversationIvrSurrenderReason":{"type":"string","enum":["no_goal","no_cached_menu","below_threshold","fingerprint_mismatch","goal_ambiguous","child_cache_miss","dtmf_send_failure","matched_option_missing_dtmf","disabled","repeated_prompt_max_retries"],"description":"Canonical code the worker emits when the IVR\nnavigator gave up. NULL when the navigator completed\ncleanly OR never started a plan.\n* `no_goal` - the goal extractor returned empty.\n* `no_cached_menu` - AMD-time cache miss for the root fingerprint.\n* `below_threshold` - cached menu loaded but confidence < threshold.\n* `fingerprint_mismatch` - in-call prompt diverged from the cached menu fingerprint.\n* `goal_ambiguous` - cached options matched the goal more than once or not at all.\n* `child_cache_miss` - sub-menu fingerprint had no cached row.\n* `dtmf_send_failure` - DTMF press could not be delivered.\n* `matched_option_missing_dtmf` - defensive shape guard.\n* `disabled` - per-agent toggle off OR operator kill switch on.\n* `repeated_prompt_max_retries` - bounded press-retry on the same fingerprint hit its cap of 1.\n","title":"ConversationIvrSurrenderReason"},"Conversation":{"type":"object","properties":{"id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nfor the agent that answers this conversation.\n"},"status":{"$ref":"#/components/schemas/ConversationStatus"},"transport":{"$ref":"#/components/schemas/ConversationTransport"},"created_at":{"type":"string","format":"date-time","description":"When the conversation row was created (the call was\ninitiated). Always present, including for conversations\nthat never started \u2014 unlike `started_at` \u2014 so it is the\ntimestamp to display and sort pending calls by.\n"},"started_at":{"type":["string","null"],"format":"date-time","description":"Set when the first user participant joins the realtime\nvoice session. Null between CreateConversation and the\nparticipant-joined event, and stays null if no user ever\njoins.\n"},"ended_at":{"type":["string","null"],"format":"date-time"},"duration_ms":{"type":["integer","null"]},"cost_cents":{"type":["integer","null"]},"recording_url":{"type":["string","null"]},"recording_started_at":{"type":["string","null"],"format":"date-time","description":"When the recording file actually began capturing audio\n(LiveKit egress file started_at). Anchor transcript message\noffsets on this \u2014 not `started_at` \u2014 when seeking the\nrecording: the file's first frame trails the participant\njoin by the egress recorder's spin-up (~1-2s). Null when\nthere is no recording or the row pre-dates the field.\n"},"end_reason":{"$ref":"#/components/schemas/ConversationEndReason","description":"Coarse termination category. Most reasons are assigned by the\nagent runtime as the call ends; `caller_hangup` may also be\napplied server-side as a post-call catch-all. The `dial_*`\nreasons are assigned server-side on a `failed` conversation\nfor an outbound call that never connected.\n* `voicemail_message_left` \u2014 AMD machine-vm + we spoke the configured drop-message.\n* `voicemail_hangup` \u2014 AMD machine-vm + we terminated silently (action=hangup or empty-message bypass).\n* `ivr_hangup` \u2014 AMD machine-ivr + action=hangup.\n* `unavailable_hangup` \u2014 AMD machine-unavailable (mailbox full / disconnected).\n* `agent_ended` \u2014 LLM-driven end_call builtin.\n* `inactivity_timeout` \u2014 the call ended after the configured silence window elapsed with no activity.\n* `loop_detected` \u2014 a loop guard force-ended the call after several consecutive near-identical user turns (typically an IVR replaying its menu while the agent kept reacting instead of ending the call).\n* `max_duration_reached` - the max-call-duration limit force-ended the call at the platform ceiling (a safety bound on runaway calls).\n* `flow_budget_exhausted` \u2014 the agent's flow ran for longer than the total per-call flow-execution budget and was ended by the runtime. A backstop above every call-duration ceiling; a call that reaches it has been looping rather than conversing.\n* `over_capacity` \u2014 inbound call refused because the workspace was over its active-call concurrency cap; the busy message played and the call hung up. Stamped server-side and excluded from billing.\n* `caller_hangup` \u2014 the caller's leg went away. Stamped immediately when a SIP disconnect is observed; otherwise applied server-side shortly after the call ends as a catch-all (web tab close, network blip, etc.).\n* `dial_no_answer` \u2014 outbound dial: callee did not pick up (SIP 408/480/487, the ringing timeout expired).\n* `dial_busy` \u2014 outbound dial: the line was busy (SIP 486/600).\n* `dial_rejected` \u2014 outbound dial: the call was actively refused (SIP 401/403/407 carrier auth/permission, or 603/607/608 callee decline).\n* `dial_failed` \u2014 outbound dial: any other failure to connect (invalid number, carrier 5xx, malformed trunk address, TLS requirement, transport error). On a `failed` conversation with NULL `duration_ms`.\n* `transferred` \u2014 the caller's leg was handed off to a phone number via SIP REFER (`transfer_to_number`); the carrier moved the leg and the agent's side ended.\n* `null` \u2014 the termination category was not recorded. Legacy calls only; current calls always carry a reason.\n"},"metadata":{"type":"object","additionalProperties":{"description":"Any type"}},"caller_identity":{"type":"string","description":"Stable application-level caller key persisted at session start\nso the post-call memory extractor can pivot memories by\n`(agent_id, caller_identity)`. For authenticated conversation\ncreation, this is `user_identity` when supplied and otherwise\nfalls back to `user_`. It is deliberately\nindependent from the opaque realtime participant identity. Empty\nstring for anonymous widget sessions.\n"},"contact_id":{"type":"string","pattern":"^contact_[0-9a-hjkmnp-tv-z]{26}$","description":"The workspace person this conversation resolved to. Absent while\nthe conversation has no resolved contact.\n"},"from_number":{"type":["string","null"],"description":"E.164 of the phone number that placed the call. For\n`sip_outbound` this is the workspace number used as the\ncaller ID; for `sip_inbound` it is the external caller's\nnumber. Null for `web` conversations and for older\noutbound rows, where the dialing caller id was not recorded\nbefore this field existed.\n"},"to_number":{"type":["string","null"],"description":"E.164 of the phone number that received the call. For\n`sip_outbound` this is the external callee; for\n`sip_inbound` it is the workspace number the caller\ndialed. Null for `web` conversations and for legacy\ninbound rows whose room name did not embed the dialed\nnumber.\n"},"agent_snapshot":{"$ref":"#/components/schemas/AgentSnapshot","description":"Frozen snapshot of the agent's configuration at create\ntime. Populated only on detail responses; list responses\nintentionally skip the column to keep the row small.\n"},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Customer-facing dynamic variables this call ran with: the\nagent's stored variable defaults overlaid with the\nper-session `dynamic_variables` overrides, resolved to\ntheir values. Reserved `system__*` keys are excluded \u2014\nthey are runtime-derived and not part of the audit\nsnapshot. Omitted for SIP inbound calls (which take no\nper-session variables) and for legacy conversations\nrecorded before this field existed. Populated only on\ndetail responses; the\nlist endpoint skips it, mirroring `agent_snapshot`.\n"},"message_count":{"type":"integer","description":"Populated only on the list endpoint via a correlated\nsubquery. Zero on single-row reads where the join cost\nisn't paid.\n"},"ivr_menu_id":{"type":["string","null"],"pattern":"^menu_[0-9a-hjkmnp-tv-z]{26}$","description":"Audit pointer at the cached IVR menu the\nnavigator consulted on this call. NULL when the navigator\nnever engaged OR after the referenced menu was\ninvalidated (FK is ON DELETE SET NULL).\n"},"ivr_path_taken":{"type":["array","null"],"items":{"type":"object","additionalProperties":{"description":"Any type"}},"description":"Ordered log of the navigator's per-call presses:\n`[{fingerprint, dtmf, label}, ...]`. Empty array means\n\"navigator engaged but pressed nothing\" (distinct from\nNULL = \"navigator never engaged\").\n"},"ivr_surrender_reason":{"$ref":"#/components/schemas/ConversationIvrSurrenderReason","description":"Canonical code the worker emits when the IVR\nnavigator gave up. NULL when the navigator completed\ncleanly OR never started a plan.\n* `no_goal` - the goal extractor returned empty.\n* `no_cached_menu` - AMD-time cache miss for the root fingerprint.\n* `below_threshold` - cached menu loaded but confidence < threshold.\n* `fingerprint_mismatch` - in-call prompt diverged from the cached menu fingerprint.\n* `goal_ambiguous` - cached options matched the goal more than once or not at all.\n* `child_cache_miss` - sub-menu fingerprint had no cached row.\n* `dtmf_send_failure` - DTMF press could not be delivered.\n* `matched_option_missing_dtmf` - defensive shape guard.\n* `disabled` - per-agent toggle off OR operator kill switch on.\n* `repeated_prompt_max_retries` - bounded press-retry on the same fingerprint hit its cap of 1.\n"}},"required":["id","agent_id","status","transport","created_at","metadata","message_count"],"title":"Conversation"},"CreateConversationResponse":{"type":"object","properties":{"conversation":{"$ref":"#/components/schemas/Conversation"},"token":{"type":"string","description":"Short-lived realtime session access token (JWT)."},"url":{"type":"string","description":"Realtime session wss:// URL to connect to."}},"required":["conversation","token","url"],"description":"Returned when a conversation is created. The `token` + `url`\nlet the caller connect its browser/SDK directly to the\nrealtime voice session \u2014 the agent that answers is dispatched\nserver-side.\n","title":"CreateConversationResponse"},"CreateSessionRequest":{"type":"object","properties":{"user_identity":{"type":"string","description":"Opaque identifier for the end-user (e.g. your app's user ID),\npersisted as the conversation's `caller_identity` and used as the\nmemory key. Optional - an omitted identity means an anonymous\nsession, which is excluded from per-caller memory.\n\nHow it is stored depends on whether the request was\nauthenticated, because only then has anyone verified the claim:\n\n- **With an API key**: stored verbatim, and the same value on\n `POST /v1/agents/{agent_id}/conversations` is the same caller.\n- **Without credentials** (a public agent embedded in a page):\n stored in a separate `embed_`-prefixed namespace, so a\n self-asserted identity can never resolve to a caller record\n written by a verified surface such as a phone call. Mint\n sessions from your backend if you need one caller record\n across surfaces.\n"},"language":{"type":"string","description":"Starts the session in one of the agent's configured languages\n(the default `language` or an `additional_languages` entry,\nmatched by primary subtag). The greeting and any per-language\nvoice follow. Omit for the agent's default language; an\nunconfigured language is rejected with 400.\n"},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-session variable overrides that merge on top of the agent's\nstored variable defaults for this one session. Keys in the\nreserved `system__` namespace are rejected at this boundary.\nValues must match the declared type of the corresponding variable\ndefinition on the agent (a `string` type expects a JSON string,\n`number` expects a JSON number, etc.).\n"}},"description":"Optional body for `POST /v1/agents/{agent_id}/sessions`. Widget embeds usually pass nothing.","title":"CreateSessionRequest"},"ListConversationsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}},"required":["next_cursor","has_more","conversations"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListConversationsResponse"},"RecentCallee":{"type":"object","properties":{"phone":{"type":"string","description":"E.164 phone number that was dialled."},"last_called_at":{"type":"string","format":"date-time","description":"Timestamp of the most recent outbound call to this number."}},"required":["phone","last_called_at"],"description":"One distinct phone number this workspace has dialled, with the timestamp of the most recent outbound call to it.","title":"RecentCallee"},"ListRecentCalleesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"callees":{"type":"array","items":{"$ref":"#/components/schemas/RecentCallee"}}},"required":["next_cursor","has_more","callees"],"description":"Payload for GET /v1/agents/conversations/recent-callees.","title":"ListRecentCalleesResponse"},"ConversationStats":{"type":"object","properties":{"total":{"type":"integer","format":"int64"},"completed":{"type":"integer","format":"int64"},"failed":{"type":"integer","format":"int64"},"active":{"type":"integer","format":"int64"},"pending":{"type":"integer","format":"int64"},"avg_duration_ms":{"type":["number","null"],"format":"double"},"avg_cost_cents":{"type":["number","null"],"format":"double"}},"required":["total","completed","failed","active","pending","avg_duration_ms","avg_cost_cents"],"description":"Counts + averages over the caller's conversations matching the supplied filters. AVG fields are null when no rows match the FILTER predicate.","title":"ConversationStats"},"MessageRole":{"type":"string","enum":["user","assistant","system","tool","operator"],"description":"Who produced the turn: `user` is the caller, `assistant` the AI\nagent, `tool` a tool call, `system` a transcript annotation (such\nas the take-over window markers), and `operator` a human\noperator's speech while they have taken the call over.\n","title":"MessageRole"},"Message":{"type":"object","properties":{"id":{"type":"string","pattern":"^msg_[0-9a-hjkmnp-tv-z]{26}$"},"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`)\nof the parent conversation.\n"},"role":{"$ref":"#/components/schemas/MessageRole","description":"Who produced the turn: `user` is the caller, `assistant` the AI\nagent, `tool` a tool call, `system` a transcript annotation (such\nas the take-over window markers), and `operator` a human\noperator's speech while they have taken the call over.\n"},"content":{"type":"string"},"tool_name":{"type":["string","null"]},"tool_args":{"type":["object","null"],"additionalProperties":{"description":"Any type"}},"tool_result":{"description":"Arbitrary JSON value returned by the tool (object, array, string, or primitive)."},"started_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}},"required":["id","conversation_id","role","content","started_at"],"title":"Message"},"ListMessagesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}},"required":["next_cursor","has_more","messages"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListMessagesResponse"},"EvaluationKind":{"type":"string","enum":["criterion","summary","data"],"title":"EvaluationKind"},"EvaluationStatus":{"type":"string","enum":["success","failure","unknown"],"description":"Three-state criterion result. `unknown` means the criterion did not apply to this call.","title":"EvaluationStatus"},"Evaluation":{"type":"object","properties":{"id":{"type":"string","pattern":"^eval_[0-9a-hjkmnp-tv-z]{26}$"},"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`)\nof the conversation this evaluation is attached to.\n"},"kind":{"$ref":"#/components/schemas/EvaluationKind"},"criterion_id":{"type":["string","null"]},"name":{"type":"string"},"status":{"oneOf":[{"$ref":"#/components/schemas/EvaluationStatus"},{"type":"null"}],"description":"Three-state criterion result. `unknown` means the criterion did not apply to this call."},"passed":{"type":["boolean","null"]},"score":{"type":["string","null"],"title":"float"},"rationale":{"type":"string"},"data":{"description":"Structured data-collection payload (present only on `kind=data` rows)."},"created_at":{"type":"string","format":"date-time"}},"required":["id","conversation_id","kind","name","rationale","created_at"],"description":"Three flavours coexist, discriminated by `kind`:\n- `criterion` rows carry `status` + `passed` + `score` + `rationale` for one criterion\n- `summary` row carries overall sentiment + rationale in `rationale`\n- `data` row carries the structured data-collection payload in `data`\n\n`status` is the canonical three-state result. `passed` is a\nderived boolean kept for backwards compatibility with earlier\nwebhook consumers: success\u2192true, failure\u2192false, unknown\u2192null.\n","title":"Evaluation"},"ListEvaluationsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"evaluations":{"type":"array","items":{"$ref":"#/components/schemas/Evaluation"}}},"required":["next_cursor","has_more","evaluations"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListEvaluationsResponse"},"Memory":{"type":"object","properties":{"id":{"type":"string","pattern":"^memory_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`memory_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the owning agent.\n"},"caller_identity":{"type":"string","description":"Stable caller key (LiveKit participant identity) the memory is scoped to."},"fact":{"type":"string","description":"Short third-person statement about the caller."},"source_conversation_id":{"type":["string","null"],"description":"When set, the prefixed wire identifier\n(`conv_<26 char Crockford base32>`) of the conversation this\nmemory was extracted from. May be null if the source was\ndeleted.\n"},"source_run_id":{"type":["string","null"],"pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$","description":"When set, the prefixed wire identifier\n(`arun_<26 char Crockford base32>`) of the durable run this\nmemory was learned on. A memory has one source: exactly one of\n`source_conversation_id` and `source_run_id` is set, unless the\nsource was deleted.\n"},"confidence":{"type":"string","title":"float","description":"LLM self-reported 0-1 confidence in the fact's durability and relevance."},"score":{"type":"string","title":"float","description":"Populated only on retrieval hits \u2014 recency-weighted cosine similarity."},"created_at":{"type":"string","format":"date-time"}},"required":["id","agent_id","caller_identity","fact","confidence","created_at"],"description":"One salient fact extracted post-call about a specific caller on\na specific agent. Retrieved at the next conversation-start for\nthe same caller and injected into the agent's system prompt via\nthe `{{memory}}` template variable.","title":"Memory"},"ListMemoriesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"memories":{"type":"array","items":{"$ref":"#/components/schemas/Memory"}}},"required":["next_cursor","has_more","memories"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListMemoriesResponse"},"SearchHitRanking":{"type":"object","properties":{"fused_score":{"type":"string","title":"float","description":"Reciprocal-rank fusion score across the semantic and lexical lanes."},"lexical_rank":{"type":"string","title":"float","description":"Distinct query terms the passage holds, plus its full-text density; set when the lexical lane matched it."},"rerank_score":{"type":"string","title":"float","description":"The cross-encoder's relevance of the passage to the query, when a reranker ran."}},"description":"The component scores behind a hit's position, for a caller that\nwants to see why a passage ranked where it did. Each field is absent\nwhen the stage that produces it did not run. `score` on the hit keeps\nits cosine meaning regardless.\n","title":"SearchHitRanking"},"RetrievalLogResult":{"type":"object","properties":{"chunk_id":{"type":"string","pattern":"^chunk_[0-9a-hjkmnp-tv-z]{26}$"},"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`)\nof the knowledge base the matched chunk lives in.\n"},"filename":{"type":"string"},"chunk_index":{"type":"integer"},"content":{"type":"string"},"score":{"type":"string","title":"float"},"heading_path":{"type":"array","items":{"type":"string"},"description":"The headings the passage sat under when it was retrieved, outermost first."},"source_url":{"type":"string"},"ranking":{"$ref":"#/components/schemas/SearchHitRanking","description":"The component scores behind the position at the time of the search; absent when only the cosine ranked the hit."}},"required":["chunk_id","document_id","kb_id","filename","chunk_index","content","score","heading_path"],"description":"One ranked chunk inside a retrieval log row. Denormalised so\ndeleting a chunk or document after the call doesn't render\nhistorical logs unreadable.\n","title":"RetrievalLogResult"},"RetrievalCoverage":{"type":"string","enum":["sufficient","partial","insufficient"],"description":"Retrieval's verdict on whether the evidence it found answers the\nquestion. `sufficient`: answer from the hits. `partial`: something\nrelated was found; answer with care. `insufficient`: nothing found\nclears even the low bar, and the honest answer is that the knowledge\nbase does not cover this; the agent's `search_knowledge` tool then\nreports no relevant context rather than the weak hits.\n","title":"RetrievalCoverage"},"RetrievalLogEntry":{"type":"object","properties":{"id":{"type":"string","pattern":"^retlog_[0-9a-hjkmnp-tv-z]{26}$"},"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`)\nof the conversation.\n"},"query":{"type":"string"},"results":{"type":"array","items":{"$ref":"#/components/schemas/RetrievalLogResult"}},"limit":{"type":"integer"},"hit_count":{"type":"integer"},"node_key":{"type":"string","description":"Key of the flow node that was active when the agent ran this\nsearch. Empty when the call ran without a flow, or for rows\nrecorded before phase attribution existed.\n"},"retrieval_version":{"type":"string","description":"The retrieval contract that produced this row, so results can be\ncompared like with like across a rollout. Rows recorded before\nversioning carry `v1`.\n"},"coverage":{"$ref":"#/components/schemas/RetrievalCoverage","description":"The verdict the agent was given for this search. Absent on rows\nrecorded before coverage existed.\n"},"latency_ms":{"type":"integer","description":"How long the search took, in milliseconds. Absent on rows\nrecorded before it was measured.\n"},"searched_knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The knowledge bases this search actually ran against. Equal\nto the agent's full attached set unless the active subagent\nnode carried a `knowledge_base_filter`, in which case it is\nthe narrowed subset \u2014 so the log shows which sources each\nphase of the call was allowed to consult, not just which\nones returned a hit.\n"},"created_at":{"type":"string","format":"date-time"}},"required":["id","conversation_id","query","results","limit","hit_count","node_key","retrieval_version","searched_knowledge_base_ids","created_at"],"description":"One `search_knowledge` invocation recorded against a\nconversation. Powers the Retrieval panel on the conversation\ndetail view.\n","title":"RetrievalLogEntry"},"ListRetrievalLogsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"entries":{"type":"array","items":{"$ref":"#/components/schemas/RetrievalLogEntry"}}},"required":["next_cursor","has_more","entries"],"description":"Payload for `GET /v1/agents/conversations/{conversation_id}/retrieval-log`.","title":"ListRetrievalLogsResponse"},"WebhookDeliveryStatus":{"type":"string","enum":["pending","delivered","failed"],"description":"Lifecycle of a post-call webhook delivery row. The sender\nupdates the same row across retries so the UI always sees the\nlatest outcome.\n","title":"WebhookDeliveryStatus"},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","pattern":"^whd_[0-9a-hjkmnp-tv-z]{26}$"},"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`)\nof the conversation that triggered this delivery.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent.\n"},"url":{"type":"string"},"event":{"type":"string"},"status":{"$ref":"#/components/schemas/WebhookDeliveryStatus"},"attempt_count":{"type":"integer"},"last_attempt_at":{"type":"string","format":"date-time"},"last_status_code":{"type":"integer"},"last_error":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"request_body":{"type":"string","description":"The exact JSON body Speechify POSTed to your webhook URL. This\nis the verbatim payload the `Speechify-Signature` HMAC was\ncomputed over (as `.`), so you can re-verify the\nsignature against it. Absent on deliveries that pre-date this\ncapture.\n"},"request_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"The HTTP headers Speechify sent with the delivery, including\n`Speechify-Signature` (`t=,v0=`),\n`Speechify-Event`, and `Speechify-Delivery-Id`. Reflects the\nmost recent attempt.\n"},"last_response_body":{"type":"string","description":"The response body your server returned on the most recent\nattempt, truncated to 8 KiB. Useful for debugging a non-2xx\nresponse. Absent when no response was received (transport\nerror) or on pre-capture deliveries.\n"},"last_response_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"The response headers your server returned on the most recent\nattempt.\n"}},"required":["id","conversation_id","agent_id","url","event","status","attempt_count","created_at"],"description":"Post-call webhook delivery log row. One row per\n`(conversation, webhook-url)`; updated in place across retry\nattempts.\n","title":"WebhookDelivery"},"ListWebhookDeliveriesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}},"required":["next_cursor","has_more","deliveries"],"description":"Payload for `GET /v1/agents/conversations/{conversation_id}/webhook-deliveries`.","title":"ListWebhookDeliveriesResponse"},"ShadowConversationResponse":{"type":"object","properties":{"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$"},"signaling_url":{"type":"string","description":"wss://\u2026 signaling URL the realtime client connects to."},"token":{"type":"string","description":"Short-lived realtime access token. The grant is listen-only\n(cannot publish audio or data) and hidden, so the observer can\nhear the call but cannot speak and is invisible to the caller\nand the agent.\n"},"identity":{"type":"string","description":"Opaque participant identity tag (e.g. shadow_). Visible only to admin tooling."},"expires_at":{"type":"string","format":"date-time","description":"When the token stops being accepted by the realtime provider. Re-mint past this point."}},"required":["conversation_id","signaling_url","token","identity","expires_at"],"description":"Connection details for an authorized observer (workspace owner or\nadmin) joining an active conversation as a hidden, listen-only\nparticipant. A realtime client consumes `signaling_url` + `token`\nto attach to the live room and play the agent + caller audio\ntracks.\n","title":"ShadowConversationResponse"},"PublishAgentRequest":{"type":"object","properties":{"gate_override_reason":{"type":"string","minLength":10,"description":"On a gated workspace, a non-empty justification publishes past failing\nchecks. The suite still runs and its verdict is recorded with this\nreason - an override bypasses blocking, not measurement. Must be at\nleast 10 characters after trimming."}},"description":"Optional body for POST /v1/agents/{agent_id}/publish.","title":"PublishAgentRequest"},"PublishAgentResponse":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"The agent that was published."},"config_fingerprint":{"type":"string","description":"Identifies the exact configuration this decision covers. A run is\nadmitted only against a fingerprint carrying a passing (or overridden)\ndecision, so any edit that changes the agent's behavior produces a new\nfingerprint and requires publishing again."},"published_at":{"type":"string","format":"date-time","description":"When the configuration was published."},"gate":{"$ref":"#/components/schemas/PublishGateResult","description":"The gate outcome. Absent when the workspace is not gated, in which case\nnothing was scored and runs are admitted without a gate check."}},"required":["agent_id","config_fingerprint","published_at"],"description":"The outcome of publishing a task agent's current configuration.","title":"PublishAgentResponse"},"PublishGateRunDecision":{"type":"string","enum":["passed","blocked","overridden","errored","skipped_no_suite"],"description":"What the gate decided. `overridden` means the checks did NOT pass and a\nhuman published anyway with a recorded justification - it is not a pass.\n`skipped_no_suite` means nothing was checked.","title":"PublishGateRunDecision"},"PublishGateRun":{"type":"object","properties":{"decision":{"$ref":"#/components/schemas/PublishGateRunDecision","description":"What the gate decided. `overridden` means the checks did NOT pass and a\nhuman published anyway with a recorded justification - it is not a pass.\n`skipped_no_suite` means nothing was checked."},"passed_count":{"type":"integer","description":"Behavioral cases that passed."},"total":{"type":"integer","description":"Behavioral cases scored."},"redteam_passed_count":{"type":"integer","description":"Red-team probes that passed."},"redteam_total":{"type":"integer","description":"Red-team probes scored."},"created_at":{"type":"string","format":"date-time","description":"When the decision was recorded."}},"required":["decision","passed_count","total","redteam_passed_count","redteam_total","created_at"],"description":"A recorded publish-gate decision, projected for display. The counts are\nreported per suite: the behavioral suite (the workspace's own tests) and\nthe adversarial red-team suite are scored independently.","title":"PublishGateRun"},"LatestPublishGateRunResponse":{"type":"object","properties":{"gate_run":{"oneOf":[{"$ref":"#/components/schemas/PublishGateRun"},{"type":"null"}],"description":"Null when the agent has never been gated."},"published":{"type":"boolean","description":"Whether the agent's current configuration carries a passing (or overridden)\ndecision, so a run on it is admitted. A trigger whose agent reads `false`\nhere will fail every fire until the agent is published again."},"config_fingerprint":{"type":"string","description":"The identity of the current configuration - what a publish records. It moves\nwith the prompt, model, guardrails, tools and their schemas, never with the\nknowledge bases attached or with per-run variables."}},"required":["gate_run","published","config_fingerprint"],"description":"The agent's most recent publish-gate decision, and whether the configuration\nit has RIGHT NOW is published. The two differ after an edit: the last decision\nstill reads `passed` while `published` is false, because the edit re-armed the\ngate. Attaching or detaching a knowledge base is not such an edit.","title":"LatestPublishGateRunResponse"},"CreateAgentRunRequest":{"type":"object","properties":{"instruction":{"type":"string","maxLength":8000,"description":"The task or goal to give the agent. The agent runs its brain against this over a short internal conversation and returns its result. The server's limit is 8000 **bytes**, so a mostly non-ASCII instruction reaches it before 8000 characters."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-run values that seed the agent's flow variables (override its\nstored defaults). The agent's prompt renders against the result\nbefore every step: a declared variable the run does not supply\ntakes its default, one the run supplies takes the run's value, and\na placeholder nothing supplies renders empty. The reserved\n`system__caller_id`, `system__agent_id`, `system__language` and\n`system__memory` keys are bound by the platform. The `system__*` namespace and the legacy `memory`\nalias belong to the platform and are rejected with a 400 naming\n`variables`, the same rule a conversation applies: the run binds its\nown values there, including `system__caller_id` for the person it\nacts for.\n"},"max_turns":{"type":"integer","description":"Upper bound on the run's internal turn budget - one turn is one\nplan-act-observe cycle, so a run that calls three tools uses at\nleast four. Defaults to 8 when omitted.\n\n**Clamped to the workspace's per-run ceiling** (5 on Free, 10\non Starter, 20 on Pro, 30 on Scale, 50 on Enterprise; per-workspace\noverrides apply): the run's `input.max_turns` echoes the budget it\nactually got, and `GET /v1/workspaces/current/entitlements`\n(`max_run_turns`) reports the ceiling up front, so plan against\nthat rather than the value you sent. An omitted `max_turns` takes\nthe default, clamped to the ceiling. On Free the\nceiling is *below* the default, so omitting this field there yields\n5, not 8.\n\nA run that exhausts its budget settles `succeeded` with\n`incomplete_reason: max_turns_exhausted` and whatever answer it had\nreached. Schema repairs count against this budget too - see\n`output_schema`.\n"},"user_identity":{"type":"string","maxLength":256,"description":"The person this run acts for, in your own vocabulary - the same\nfield a conversation and a widget session take, so one workspace\nnever has two answers to who a person is. The agent opens the run\nknowing what it has already learned about them, and what a run\nthat succeeds learns from its instruction and its reply is\nwritten back under this value, exactly as a call writes memory.\n\nOmit it to run the agent for nobody in particular, which is how a\nrun behaves with no memory of anyone and learns nothing. Must not begin with `user_`,\n`embed_` or `anon_`, which name identities the platform derives.\n\nEvery tool the run calls is told this value: a webhook receives\nit as `user_identity` inside the signed body, an MCP server as\nthe `Speechify-User-Identity` header, and it renders in a tool's\ntemplated URL or headers as `{{system__caller_id}}`. A connector\nyou wrote can therefore look up that person's own third-party\ntoken, which is how you integrate a system Speechify holds no\ncredentials for.\n"},"output_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional JSON Schema (2020-12) the run's final answer must satisfy.\nWhen set, the agent answers with a JSON object, the platform\nvalidates it, and the conforming object is returned as\n`output.data`.\n\nThe top level must be `type: object` - an array-typed or scalar\nschema is refused at create with `400`. At most 16 KiB.\n\nOn a mismatch the platform re-asks the agent, feeding back up to 8\nof the violations. **At most two repair attempts, and each one\nspends a turn from `max_turns`** - so a schema-constrained run on a\n5-turn ceiling has little room left for tool calls. Each attempt is\njournaled as an `observation` step whose `tool` is the reserved\nname `output_schema`, which a timeline renderer should expect\nalongside real tool names.\n\nA run that never produces a conforming object settles `succeeded`\nwith `incomplete_reason: output_schema_violation` and no\n`output.data` - its prose answer is still on `output.reply`. The\nplatform never returns an object the schema refused.\n"},"metadata":{"type":"object","additionalProperties":{"type":"string"},"description":"Up to 16 arbitrary key/value pairs echoed back on the run. Your own correlation ids belong here - the platform never reads them."}},"required":["instruction"],"description":"Body for `POST /v1/agents/{agent_id}/runs`.","title":"CreateAgentRunRequest"},"AgentRunStatus":{"type":"string","enum":["queued","running","requires_action","canceling","succeeded","failed","canceled","expired"],"description":"Lifecycle: `queued` -> `running` -> `succeeded` | `failed` | `canceled` | `expired`. `requires_action` (a pending human approval) and `canceling` are transient. Terminal set: succeeded, failed, canceled, expired.","title":"AgentRunStatus"},"AgentRunInputDelegationTargetsItems":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"role":{"type":"string"},"delegation":{"type":"string"}},"title":"AgentRunInputDelegationTargetsItems"},"AgentRunInput":{"type":"object","properties":{"instruction":{"type":"string"},"variables":{"type":"object","additionalProperties":{"description":"Any type"}},"max_turns":{"type":"integer"},"user_identity":{"type":"string","description":"The person the run acts for, as supplied at creation. Absent when the run was started for nobody in particular."},"output_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The JSON Schema the run's structured output must satisfy, frozen at creation. Absent for a prose-only run."},"delegation_targets":{"type":"array","items":{"$ref":"#/components/schemas/AgentRunInputDelegationTargetsItems"},"description":"The team members this run may delegate sub-goals to, present only on a team run (started via `runTeam`). Each is a member agent with its role and hand-off note. Absent for a solo agent run."}},"required":["instruction"],"description":"The frozen request the run was created with.","title":"AgentRunInput"},"AgentRunOutputTranscriptItems":{"type":"object","properties":{},"title":"AgentRunOutputTranscriptItems"},"AgentRunOutput":{"type":"object","properties":{"reply":{"type":"string","description":"The agent's final answer."},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The structured output, present only when the run carried an\n`output_schema` and the agent produced an object satisfying it.\nNever present alongside `incomplete_reason: output_schema_violation`.\n"},"transcript":{"type":"array","items":{"$ref":"#/components/schemas/AgentRunOutputTranscriptItems"},"description":"The per-step conversation record."}},"description":"The run's result, present only when `status` is `succeeded`.","title":"AgentRunOutput"},"AgentRunError":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"description":"Present only when `status` is `failed`.","title":"AgentRunError"},"AgentRunIncompleteReason":{"type":"string","enum":["max_turns_exhausted","budget_exhausted","output_schema_violation"],"description":"Why a non-failed run stopped short: `max_turns_exhausted`, `budget_exhausted`, or `output_schema_violation` (the agent never produced an object matching `input.output_schema`; `output.reply` keeps its prose and `output.data` is absent).","title":"AgentRunIncompleteReason"},"AgentRunUsageModelsItems":{"type":"object","properties":{"model":{"type":"string","description":"The model that spent these tokens."},"input_tokens":{"type":"integer","minimum":0},"cached_input_tokens":{"type":"integer","minimum":0},"output_tokens":{"type":"integer","minimum":0},"total_tokens":{"type":"integer","minimum":0},"cost_micro_usd":{"type":"integer","format":"int64","minimum":0}},"required":["model","input_tokens","cached_input_tokens","output_tokens","total_tokens","cost_micro_usd"],"title":"AgentRunUsageModelsItems"},"AgentRunUsage":{"type":"object","properties":{"duration_ms":{"type":"integer","minimum":0,"description":"Wall-clock time the agent spent working, summed across steps. Excludes time the run spent queued or parked on a human."},"input_tokens":{"type":"integer","minimum":0,"description":"Input tokens summed across every step, cached reads included."},"cached_input_tokens":{"type":"integer","minimum":0,"description":"The subset of `input_tokens` served from provider cache reads. Absent when the provider reported none."},"output_tokens":{"type":"integer","minimum":0,"description":"Output tokens summed across every step, reasoning tokens included."},"total_tokens":{"type":"integer","minimum":0,"description":"`input_tokens` + `output_tokens`."},"cost_micro_usd":{"type":"integer","format":"int64","minimum":0,"description":"What the run cost in millionths of a US dollar, the sum of `models[].cost_micro_usd`, priced at your plan's rates on the rate card named by `rate_card_version`. Informational; the invoice is authoritative."},"rate_card_version":{"type":"string","description":"The rate card version the cost was computed against."},"models":{"type":"array","items":{"$ref":"#/components/schemas/AgentRunUsageModelsItems"},"description":"The token usage and cost split per model, in the order the run first used each; the totals above are the sum over these entries. A run that never changed model has one entry. The model named is the one that actually served the steps, which can differ from the model the agent is configured with when the platform routes a step elsewhere; it is a report, not a promise that the same model serves the next run. An entry whose `model` is empty covers steps whose provider reported no model name."}},"description":"What the run spent, present only once it has settled: wall-clock, the tokens summed across every step, the same tokens split per model, and what they cost at your plan's rates. Written once at settle and never restated; the webhook payload carries the same values. `input_tokens` includes the cached reads and `cached_input_tokens` is that subset, so the two are never added. `cost_micro_usd` is an integer in millionths of a US dollar, computed from the rate card version named alongside it, and is informational: your invoice is authoritative. A delegated child run reports its own usage; the parent never sums it.","title":"AgentRunUsage"},"PendingActionDefaultDecision":{"type":"string","enum":["approve","deny"],"description":"The decision applied automatically if nobody responds by `expires_at`. Defaults to `deny`, so an unattended action is refused rather than taken.","title":"PendingActionDefaultDecision"},"PendingAction":{"type":"object","properties":{"key":{"type":"string","description":"Stable id of this pending action; pass as `action_key` to `submitRun`."},"type":{"type":"string","description":"The action's class (e.g. `send_email`, `financial`)."},"reason":{"type":"string","description":"Why this action needs human approval."},"summary":{"type":"string","description":"Human-readable rendering of the action."},"payload":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The exact structured action awaiting approval."},"approve_effect":{"type":"string","description":"What approving does."},"deny_effect":{"type":"string","description":"What denying does."},"timeout_effect":{"type":"string","description":"What happens if the deadline passes with no response."},"default_decision":{"$ref":"#/components/schemas/PendingActionDefaultDecision","description":"The decision applied automatically if nobody responds by `expires_at`. Defaults to `deny`, so an unattended action is refused rather than taken."},"requested_at":{"type":"string","format":"date-time","description":"When the run parked and started waiting for a person."},"expires_at":{"type":"string","format":"date-time","description":"When the run stops waiting and applies `default_decision` on its own. **The window is 4 hours from `requested_at` unless the action sets its own**, clamped to between 1 minute and 24 hours - so an approval raised in the evening is decided overnight without a human. Build your approvals inbox around that, or shorten the window on the actions that warrant it."}},"required":["key","type","reason","default_decision","requested_at","expires_at"],"description":"A human approval a run is durably parked on (present on `AgentRun` only while `status` is `requires_action`). Rendered VERBATIM for the approver - never a summary the agent wrote - so an injected agent cannot misrepresent what it is about to do. Resolve it with `submitRun`.","title":"PendingAction"},"AgentRun":{"type":"object","properties":{"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project this run belongs to; null when it belongs to none.\nCaptured when the run was created, so it does not move if the agent later does.\n"},"id":{"type":"string","description":"Run id (prefixed external id, `arun_...`)."},"agent_id":{"type":"string","description":"The agent that ran. On a delegated child this is the member agent, not the manager."},"status":{"$ref":"#/components/schemas/AgentRunStatus","description":"Lifecycle: `queued` -> `running` -> `succeeded` | `failed` | `canceled` | `expired`. `requires_action` (a pending human approval) and `canceling` are transient. Terminal set: succeeded, failed, canceled, expired."},"input":{"$ref":"#/components/schemas/AgentRunInput","description":"The frozen request the run was created with."},"output":{"$ref":"#/components/schemas/AgentRunOutput","description":"The run's result, present only when `status` is `succeeded`."},"error":{"$ref":"#/components/schemas/AgentRunError","description":"Present only when `status` is `failed`."},"incomplete_reason":{"$ref":"#/components/schemas/AgentRunIncompleteReason","description":"Why a non-failed run stopped short: `max_turns_exhausted`, `budget_exhausted`, or `output_schema_violation` (the agent never produced an object matching `input.output_schema`; `output.reply` keeps its prose and `output.data` is absent)."},"usage":{"$ref":"#/components/schemas/AgentRunUsage","description":"What the run spent, present only once it has settled: wall-clock, the tokens summed across every step, the same tokens split per model, and what they cost at your plan's rates. Written once at settle and never restated; the webhook payload carries the same values. `input_tokens` includes the cached reads and `cached_input_tokens` is that subset, so the two are never added. `cost_micro_usd` is an integer in millionths of a US dollar, computed from the rate card version named alongside it, and is informational: your invoice is authoritative. A delegated child run reports its own usage; the parent never sums it."},"pending_action":{"$ref":"#/components/schemas/PendingAction"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"parent_run_id":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$","description":"The run that delegated this one a sub-goal, absent on a root run. It is the upward half of lineage - what makes a child run of a member agent attributable to the team run that caused it (`listRunChildren` is the downward half)."},"created_at":{"type":"string","format":"date-time","description":"When the run was accepted and queued."},"started_at":{"type":"string","format":"date-time","description":"When a worker picked the run up. Absent while `queued`; the gap between this and `created_at` is queue wait, not agent time."},"ended_at":{"type":"string","format":"date-time","description":"When the run settled. Present for every terminal status - succeeded, failed, canceled and expired alike - and absent otherwise."}},"required":["id","agent_id","status","input","created_at"],"description":"A durable async agent run. Enqueue it, get this handle, follow it with the run event stream (or poll `status`) until terminal. Backed by a durable job, so it survives a deploy.","title":"AgentRun"},"V1AgentsAgentIdRunsGetParametersStatus":{"type":"string","enum":["queued","running","requires_action","succeeded","failed","canceled"],"title":"V1AgentsAgentIdRunsGetParametersStatus"},"ListAgentRunsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/AgentRun"}}},"required":["next_cursor","has_more","runs"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentRunsResponse"},"V1AgentsRunsGetParametersStatus":{"type":"string","enum":["queued","running","requires_action","succeeded","failed","canceled"],"title":"V1AgentsRunsGetParametersStatus"},"SubmitAgentRunRequestDecision":{"type":"string","enum":["approve","deny"],"description":"The approval disposition. `approve` resumes the run to execute the pending action; `deny` resumes it down its could-not-complete path.","title":"SubmitAgentRunRequestDecision"},"SubmitAgentRunRequest":{"type":"object","properties":{"decision":{"$ref":"#/components/schemas/SubmitAgentRunRequestDecision","description":"The approval disposition. `approve` resumes the run to execute the pending action; `deny` resumes it down its could-not-complete path."},"action_key":{"type":"string","description":"The pending action being decided (the run's `pending_action.key`). Required, so a decision always targets the specific action the human saw: it guards against resolving a stale action - a `409 agent_run_action_stale` if it no longer matches the run's current pending action."},"note":{"type":"string","maxLength":2000,"description":"Optional free-text rationale recorded on the immutable approval audit entry."}},"required":["decision","action_key"],"description":"Body for `POST /v1/agents/{agent_id}/runs/{run_id}/submit`.","title":"SubmitAgentRunRequest"},"AgentRunStepKind":{"type":"string","enum":["plan","tool_call","observation","delegation","output"],"description":"The step type. `output` is reserved for a future step kind - a run's final answer lives on the run object today, not in the journal.","title":"AgentRunStepKind"},"RunStepDelegationChildStatus":{"type":"string","enum":["queued","running","requires_action","canceling","succeeded","failed","canceled","expired"],"description":"The state the child settled in, present on the `observation` step that folded its result back. Absent on the `delegation` step, where the child has not settled yet.","title":"RunStepDelegationChildStatus"},"RunStepDelegation":{"type":"object","properties":{"child_run_id":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$","description":"The child run that executed the sub-goal."},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"The team member the sub-goal went to. Absent when a run delegated to ITSELF (a same-agent sub-goal), which involves no member hand-off."},"depth":{"type":"integer","description":"The child's depth in the delegation tree (a root run is 0, so its children are 1). The runtime refuses a delegation past a fixed depth, bounding recursion. Present on the `delegation` step."},"child_status":{"$ref":"#/components/schemas/RunStepDelegationChildStatus","description":"The state the child settled in, present on the `observation` step that folded its result back. Absent on the `delegation` step, where the child has not settled yet."},"batch_size":{"type":"integer","description":"How many sub-goals the manager delegated in the SAME turn. A manager may hand out several at once; they run concurrently and only read as peers of that one turn if you can tell which of the crew each is. Absent on a single-target delegation."},"batch_index":{"type":"integer","description":"0-based position of this hand-off within its concurrent batch."}},"required":["child_run_id"],"description":"The hand-off a step describes: which team member took a sub-goal, and the child run that pursued it.\nIt appears on BOTH halves of a hand-off, keyed by the same `child_run_id`: the `delegation` step that made it (carrying `depth`), and the later `observation` step that folded the child's result back (carrying `child_status`). Pair them on `child_run_id` to read a sub-goal and its outcome together. The sub-goal and the result themselves are those steps' `content`.\n`child_run_id` is a real run - fetch it with `getRun` (under `agent_id`) for the member's own status, output, and token usage. That is what makes a team run traversable: the parent's timeline links to each member's actual work rather than summarizing it.","title":"RunStepDelegation"},"AgentRunStep":{"type":"object","properties":{"seq":{"type":"integer","description":"1-based position in the run's append-only journal (ascending = chronological)."},"kind":{"$ref":"#/components/schemas/AgentRunStepKind","description":"The step type. `output` is reserved for a future step kind - a run's final answer lives on the run object today, not in the journal."},"content":{"type":"string","description":"The step's payload, per kind: a `plan`'s rationale, an `observation`'s result, or a `delegation`'s sub-goal (redacted)."},"tool":{"type":"string","description":"The tool a `tool_call` / `observation` step is about.\n\nIt is not always one of your own tools. Platform tools appear under\ntheir reserved names - `search_knowledge`, `fetch_url`, `run_code`,\n`store_query`, `store_get`, `store_put`, `store_delete`,\n`store_read_asset` - an MCP tool appears as\n`__`, and a schema-repair observation\ncarries the reserved pseudo-tool `output_schema`. Match against\nyour own toolbelt expecting misses.\n"},"args":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The rendered arguments of a `tool_call`, with credential-shaped values redacted."},"delegation":{"$ref":"#/components/schemas/RunStepDelegation"},"duration_ms":{"type":"integer","description":"Wall-clock the brain spent producing this step."},"model":{"type":"string","description":"The model that served this step, as the provider reported it; absent when it reported none. The run's `usage.models` is the sum over its steps by this value."},"input_tokens":{"type":"integer","description":"Input tokens this step spent, cached reads included. The run's `usage` is the sum over its steps."},"cached_input_tokens":{"type":"integer","description":"The subset of `input_tokens` served from provider cache reads. Absent when the provider reported none."},"output_tokens":{"type":"integer","description":"Output tokens this step spent."},"total_tokens":{"type":"integer","description":"This step's input plus output tokens."},"created_at":{"type":"string","format":"date-time","description":"When the step was committed to the journal."}},"required":["seq","kind","created_at"],"description":"One iteration of a durable run's plan-act-observe loop, journaled in order. A `plan` step carries the agent's rationale (`content`); a `tool_call` step carries the tool it invoked (`tool`) and its rendered arguments (`args`); an `observation` step carries the tool's result (`content`); a `delegation` step carries the sub-goal it handed to a team member (`content`) and the member + child run that took it (`delegation`). The run's final answer is not a step - read it from the run object. Credential-shaped values in `args` and `content` are redacted.","title":"AgentRunStep"},"ListAgentRunStepsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"steps":{"type":"array","items":{"$ref":"#/components/schemas/AgentRunStep"}}},"required":["next_cursor","has_more","steps"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentRunStepsResponse"},"AgentRunStepAddedEventKind":{"type":"string","enum":["plan","tool_call","observation","delegation","output"],"description":"The step type. `output` is reserved for a future step kind - a run's final answer lives on the run object today, not in the journal.","title":"AgentRunStepAddedEventKind"},"AgentRunStatusChangedEventStatus":{"type":"string","enum":["queued","running","requires_action","canceling","succeeded","failed","canceled","expired"],"title":"AgentRunStatusChangedEventStatus"},"AgentRunEndedEventStatus":{"type":"string","enum":["succeeded","failed","canceled","expired"],"title":"AgentRunEndedEventStatus"},"AgentRunEndedEventOutput":{"type":"object","properties":{"reply":{"type":"string","description":"The agent's final answer."},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The structured output, present only when the run carried an\n`output_schema` and the agent produced an object satisfying it.\n"}},"description":"The run's answer, as Get Agent Run returns it but without\n`transcript` - the stream has already delivered every step\nindividually, so repeating them here would send the run twice.\nAbsent on a run that produced no answer (failed, cancelled, or\nstopped short).","title":"AgentRunEndedEventOutput"},"AgentRunStreamEvent":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["run.step.added"]},"seq":{"type":"integer","description":"1-based position in the run's append-only journal (ascending = chronological)."},"kind":{"$ref":"#/components/schemas/AgentRunStepAddedEventKind","description":"The step type. `output` is reserved for a future step kind - a run's final answer lives on the run object today, not in the journal."},"content":{"type":"string","description":"The step's payload, per kind: a `plan`'s rationale, an `observation`'s result, or a `delegation`'s sub-goal (redacted)."},"tool":{"type":"string","description":"The tool a `tool_call` / `observation` step is about.\n\nIt is not always one of your own tools. Platform tools appear under\ntheir reserved names - `search_knowledge`, `fetch_url`, `run_code`,\n`store_query`, `store_get`, `store_put`, `store_delete`,\n`store_read_asset` - an MCP tool appears as\n`__`, and a schema-repair observation\ncarries the reserved pseudo-tool `output_schema`. Match against\nyour own toolbelt expecting misses.\n"},"args":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The rendered arguments of a `tool_call`, with credential-shaped values redacted."},"delegation":{"$ref":"#/components/schemas/RunStepDelegation"},"duration_ms":{"type":"integer","description":"Wall-clock the brain spent producing this step."},"model":{"type":"string","description":"The model that served this step, as the provider reported it; absent when it reported none. The run's `usage.models` is the sum over its steps by this value."},"input_tokens":{"type":"integer","description":"Input tokens this step spent, cached reads included. The run's `usage` is the sum over its steps."},"cached_input_tokens":{"type":"integer","description":"The subset of `input_tokens` served from provider cache reads. Absent when the provider reported none."},"output_tokens":{"type":"integer","description":"Output tokens this step spent."},"total_tokens":{"type":"integer","description":"This step's input plus output tokens."},"created_at":{"type":"string","format":"date-time","description":"When the step was committed to the journal."}},"required":["type","seq","kind","created_at"],"description":"A journal step was committed. Everything an AgentRunStep carries is\ninlined here, so this is the same shape List Run Steps returns with the\ndiscriminator added. The SSE `id:` is the step's `seq`, which is what a\nreconnect resumes from through `Last-Event-ID`."},{"type":"object","properties":{"type":{"type":"string","enum":["run.status.changed"]},"status":{"$ref":"#/components/schemas/AgentRunStatusChangedEventStatus"}},"required":["type","status"],"description":"The run moved to a new status. Not terminal: a run reporting\n`requires_action` is waiting on a human and the stream keeps tailing,\nwhich is precisely when a client most needs to be told."},{"type":"object","properties":{"type":{"type":"string","enum":["run.ended"]},"status":{"$ref":"#/components/schemas/AgentRunEndedEventStatus"},"incomplete_reason":{"type":"string","description":"Why the run stopped short. Absent when it finished its work."},"output":{"$ref":"#/components/schemas/AgentRunEndedEventOutput","description":"The run's answer, as Get Agent Run returns it but without\n`transcript` - the stream has already delivered every step\nindividually, so repeating them here would send the run twice.\nAbsent on a run that produced no answer (failed, cancelled, or\nstopped short)."}},"required":["type","status"],"description":"The run settled, and this event carries its answer. It is the last event\non the stream - there is no `[DONE]` sentinel - and it is everything a\nclient needs, so following a run to completion takes no follow-up\nrequest."}],"discriminator":{"propertyName":"type"},"description":"One event on the run event stream. The `type` field discriminates the\nvariants and mirrors the SSE `event:` name, so an event is identifiable\nfrom its `data:` payload alone - which matters most for\n`run.status.changed` and `run.ended`, whose bodies are otherwise\nidentical. Ignore event types you do not recognize.","title":"AgentRunStreamEvent"},"KnowledgeBaseInjection":{"type":"object","properties":{"auto_tokens":{"type":"integer"},"pin_budget_tokens":{"type":"integer"},"pinned_tokens":{"type":"integer"},"fits_prompt":{"type":"boolean","description":"This knowledge base on its own counts at most `auto_tokens`\ntokens, so an agent whose attached knowledge stays under the\nbudget carries it in the prompt whole and logs no searches\nfor it. Always false while the automatic tier is off.\n"},"corpus_tokens":{"type":"integer","description":"The exact token count when `fits_prompt` is true; absent otherwise."}},"required":["auto_tokens","pin_budget_tokens","pinned_tokens","fits_prompt"],"description":"The prompt budget in force for this knowledge base, present on the\nby-id read. `auto_tokens` is the automatic tier's budget (0 when the\ntier is off): a knowledge base whose documents count at most that\nmany tokens is placed in the prompt whole. `pin_budget_tokens`\nbounds the documents pinned with `injection_mode: always`, and\n`pinned_tokens` is what they count today.\n","title":"KnowledgeBaseInjection"},"KnowledgeBase":{"type":"object","properties":{"id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`).\n"},"name":{"type":"string","description":"Human-readable label for the knowledge base."},"description":{"type":"string","description":"Optional description."},"document_count":{"type":"integer","description":"Number of ingested documents."},"injection":{"$ref":"#/components/schemas/KnowledgeBaseInjection","description":"The prompt budget in force for this knowledge base, present on the\nby-id read. `auto_tokens` is the automatic tier's budget (0 when the\ntier is off): a knowledge base whose documents count at most that\nmany tokens is placed in the prompt whole. `pin_budget_tokens`\nbounds the documents pinned with `injection_mode: always`, and\n`pinned_tokens` is what they count today.\n"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","document_count","created_at","updated_at"],"description":"A bundle of documents that can be attached to one or more voice\nagents. Chunks across every document in the knowledge base are\nembedded and searched together.","title":"KnowledgeBase"},"ListKnowledgeBasesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"knowledge_bases":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBase"}}},"required":["next_cursor","has_more","knowledge_bases"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListKnowledgeBasesResponse"},"CreateKnowledgeBaseRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"name":{"type":"string","description":"Human-readable label."},"description":{"type":"string","description":"Optional description."}},"required":["name"],"title":"CreateKnowledgeBaseRequest"},"SearchKnowledgeBasesRequest":{"type":"object","properties":{"query":{"type":"string","description":"Natural-language search query."},"kb_ids":{"type":"array","items":{"type":"string"},"description":"Knowledge bases to search across. Results scoped to caller-owned entries; unknown IDs are silently ignored."},"limit":{"type":"integer","default":5,"description":"Max hits to return (default 5, capped at 50)."}},"required":["query","kb_ids"],"title":"SearchKnowledgeBasesRequest"},"KnowledgeBaseSearchHit":{"type":"object","properties":{"chunk_id":{"type":"string","pattern":"^chunk_[0-9a-hjkmnp-tv-z]{26}$"},"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"filename":{"type":"string"},"chunk_index":{"type":"integer"},"content":{"type":"string"},"score":{"type":"string","title":"float","description":"Cosine similarity (higher = more relevant)."},"heading_path":{"type":"array","items":{"type":"string"},"description":"The headings the passage sits under, outermost first. Empty for a\ndocument without headings, or one indexed before headings were kept.\n"},"source_url":{"type":"string","description":"The page the document was imported from, when it came from a URL."},"ranking":{"$ref":"#/components/schemas/SearchHitRanking"}},"required":["chunk_id","document_id","kb_id","filename","chunk_index","content","score","heading_path"],"title":"KnowledgeBaseSearchHit"},"SearchKnowledgeBasesResponse":{"type":"object","properties":{"hits":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseSearchHit"}},"coverage":{"$ref":"#/components/schemas/RetrievalCoverage"},"retrieval_version":{"type":"string","description":"The retrieval contract that produced this result, so results can\nbe compared like with like across a rollout.\n"}},"required":["hits","coverage","retrieval_version"],"title":"SearchKnowledgeBasesResponse"},"UpdateKnowledgeBaseRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"Move the resource between workspace projects: a `proj_...` id\nmoves it there, an explicit null moves it back to the implicit\nDefault project, omitted leaves it unchanged.\n"},"name":{"type":"string"},"description":{"type":"string"}},"title":"UpdateKnowledgeBaseRequest"},"KnowledgeBaseCoverageHealth":{"type":"object","properties":{"searches":{"type":"integer","description":"Searches in the window whose scope included this knowledge base."},"conversations":{"type":"integer","description":"Distinct conversations those searches came from."},"sufficient":{"type":"integer"},"partial":{"type":"integer"},"insufficient":{"type":"integer"},"unscored":{"type":"integer","description":"Searches recorded before retrieval gave a verdict."},"median_latency_ms":{"type":["integer","null"],"description":"Median search time in milliseconds; null until a search in the window recorded it."},"documents":{"type":"integer","description":"Ready documents with at least one passage."},"documents_retrieved":{"type":"integer","description":"Those among them a search in the window surfaced."}},"required":["searches","conversations","sufficient","partial","insufficient","unscored","median_latency_ms","documents","documents_retrieved"],"description":"The strip above the report.","title":"KnowledgeBaseCoverageHealth"},"KnowledgeBaseCoverageGap":{"type":"object","properties":{"query":{"type":"string","description":"The phrasing callers used most often."},"variants":{"type":"array","items":{"type":"string"},"description":"Other phrasings in the cluster, newest first (at most 5)."},"searches":{"type":"integer"},"partial":{"type":"integer"},"insufficient":{"type":"integer"},"last_seen_at":{"type":"string","format":"date-time"},"conversation_ids":{"type":"array","items":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The most recent conversations that hit the gap (at most 5)."}},"required":["query","variants","searches","partial","insufficient","last_seen_at","conversation_ids"],"description":"One cluster of questions the knowledge base did not cover.","title":"KnowledgeBaseCoverageGap"},"KnowledgeBaseDocumentSourceKind":{"type":"string","enum":["file","url","text"],"description":"How the document entered the KB. `file` is the upload path,\n`text` is inline pasted content, `url` is fetched via\nFirecrawl. Sitemap and crawl imports also produce `url` rows.\n","title":"KnowledgeBaseDocumentSourceKind"},"KnowledgeBaseCoverageDocument":{"type":"object","properties":{"id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"filename":{"type":"string"},"source_kind":{"$ref":"#/components/schemas/KnowledgeBaseDocumentSourceKind"},"source_url":{"type":"string"},"chunk_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}},"required":["id","filename","source_kind","chunk_count","created_at"],"description":"A document as the coverage report lists it.","title":"KnowledgeBaseCoverageDocument"},"KnowledgeBaseCoverage":{"type":"object","properties":{"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"window_days":{"type":"integer"},"since":{"type":"string","format":"date-time","description":"Start of the window; searches at or after this instant count."},"health":{"$ref":"#/components/schemas/KnowledgeBaseCoverageHealth"},"gaps":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseCoverageGap"},"description":"The partial and insufficient searches, clustered by the content\nwords they share, most frequent first. At most 50 clusters, built\nfrom the newest 2,000 such searches.\n"},"unused_documents":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseCoverageDocument"},"description":"Ready documents no search in the window retrieved a passage\nfrom, oldest first. The oldest 100; `unused_document_count` is\nthe exact total.\n"},"unused_document_count":{"type":"integer"}},"required":["kb_id","window_days","since","health","gaps","unused_documents","unused_document_count"],"description":"What one knowledge base was asked over a trailing window, what it\ncould not answer, and what in it never gets used. Built from the\nsearches agents ran against it.\n","title":"KnowledgeBaseCoverage"},"KnowledgeBaseEvalCase":{"type":"object","properties":{"id":{"type":"string","pattern":"^kbcase_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"query":{"type":"string"},"expected_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The documents whose passages answer the question. Empty for an unanswerable case."},"unanswerable":{"type":"boolean","description":"Nothing in the knowledge base should answer this; retrieval is expected to say so."},"note":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","kb_id","query","expected_document_ids","unanswerable","note","created_at","updated_at"],"description":"A labelled question for one knowledge base: the documents that\nanswer it, or `unanswerable` when nothing should.\n","title":"KnowledgeBaseEvalCase"},"ListKnowledgeBaseEvalCasesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"cases":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseEvalCase"}}},"required":["next_cursor","has_more","cases"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/eval-cases`.","title":"ListKnowledgeBaseEvalCasesResponse"},"CreateKnowledgeBaseEvalCaseRequest":{"type":"object","properties":{"query":{"type":"string","maxLength":500},"expected_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Required unless `unanswerable`; every id must belong to this knowledge base."},"unanswerable":{"type":"boolean","default":false},"note":{"type":"string","maxLength":1000}},"required":["query"],"title":"CreateKnowledgeBaseEvalCaseRequest"},"UpdateKnowledgeBaseEvalCaseRequest":{"type":"object","properties":{"query":{"type":"string","maxLength":500},"expected_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"}},"unanswerable":{"type":"boolean"},"note":{"type":"string","maxLength":1000}},"description":"Omitted fields keep their value.","title":"UpdateKnowledgeBaseEvalCaseRequest"},"JobStatus":{"type":"string","enum":["pending","running","completed","failed","cancelled"],"description":"The one lifecycle vocabulary shared by every async job (batch\ncalls, knowledge-base imports, agent-test runs, suite runs):\n`pending` \u2192 `running` \u2192 a terminal state.\n\n- `pending` - accepted but not yet executing (queued for a worker,\n or deferred to a future scheduled time).\n- `running` - actively executing.\n- `completed` - ran to conclusion. The single terminal-success\n verb. For a job that produces a pass/fail judgment (an agent-test\n run), this means it produced a verdict - read the separate\n `verdict` field for the judgment, not this status.\n- `failed` - could not complete (an infrastructure or input\n failure), distinct from a `completed` job whose `verdict` is\n `failed`.\n- `cancelled` - cancelled before reaching a natural terminal state.\n","title":"JobStatus"},"KnowledgeBaseEvaluationResults":{"type":"object","properties":{"passed":{"type":"integer"},"failed":{"type":"integer"}},"required":["passed","failed"],"description":"The verdict breakdown over the cases evaluated so far, apart from the processing counts.","title":"KnowledgeBaseEvaluationResults"},"KnowledgeBaseEvaluationMetrics":{"type":"object","properties":{"cases":{"type":"integer"},"answerable":{"type":"integer"},"unanswerable":{"type":"integer"},"recall_at_k":{"type":"number","format":"double"},"hit_rate_at_k":{"type":"number","format":"double"},"mrr":{"type":"number","format":"double"},"answerable_gated":{"type":"number","format":"double"},"unanswerable_gated":{"type":"number","format":"double"},"unanswerable_zero_hits":{"type":"number","format":"double"},"duplicate_hits":{"type":"integer"}},"required":["cases","answerable","unanswerable","recall_at_k","hit_rate_at_k","mrr","answerable_gated","unanswerable_gated","unanswerable_zero_hits","duplicate_hits"],"description":"The summary over the cases that were searched. Recall, hit rate and\nMRR cover the answerable cases; the gated shares say how often\nretrieval refused an answerable question (a false refusal) and how\noften it refused an unanswerable one (abstention recall).\n","title":"KnowledgeBaseEvaluationMetrics"},"KnowledgeBaseEvaluation":{"type":"object","properties":{"id":{"type":"string","pattern":"^kbeval_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"status":{"$ref":"#/components/schemas/JobStatus"},"retrieval_version":{"type":"string","description":"The retrieval pipeline the run was started under, so two runs can be compared like with like."},"top_k":{"type":"integer"},"total":{"type":"integer","description":"Cases to evaluate (the progress denominator)."},"completed":{"type":"integer","description":"Cases evaluated."},"failed":{"type":"integer","description":"Cases whose search failed."},"results":{"$ref":"#/components/schemas/KnowledgeBaseEvaluationResults"},"metrics":{"oneOf":[{"$ref":"#/components/schemas/KnowledgeBaseEvaluationMetrics"},{"type":"null"}],"description":"The summary, null until the evaluation completes."},"error":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}},"required":["id","kb_id","status","retrieval_version","top_k","total","completed","failed","results","metrics","created_at","updated_at","started_at","ended_at"],"description":"One run of every case against the live retrieval pipeline. Poll it\nwhile `status` is `pending` or `running`; `metrics` fills in when it\ncompletes.\n","title":"KnowledgeBaseEvaluation"},"ListKnowledgeBaseEvaluationsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseEvaluation"}}},"required":["next_cursor","has_more","runs"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/evaluations`.","title":"ListKnowledgeBaseEvaluationsResponse"},"StartKnowledgeBaseEvaluationRequest":{"type":"object","properties":{"top_k":{"type":"integer","minimum":1,"maximum":20,"default":5,"description":"How many passages each search returns; the metrics are computed at this depth."}},"title":"StartKnowledgeBaseEvaluationRequest"},"KnowledgeBaseEvaluationHit":{"type":"object","properties":{"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"filename":{"type":"string"},"chunk_index":{"type":"integer"},"score":{"type":"number","format":"double"},"heading_path":{"type":"array","items":{"type":"string"}},"excerpt":{"type":"string","description":"The first 300 characters of the passage."},"content_hash":{"type":"string","description":"A fingerprint of the whole passage, so two hits can be told apart past the excerpt."},"ranking":{"$ref":"#/components/schemas/SearchHitRanking","description":"The component scores behind the position; absent when only the cosine ranked the hit."}},"required":["document_id","filename","chunk_index","score","heading_path","excerpt","content_hash"],"title":"KnowledgeBaseEvaluationHit"},"KnowledgeBaseEvaluationResult":{"type":"object","properties":{"case_id":{"type":"string","pattern":"^kbcase_[0-9a-hjkmnp-tv-z]{26}$"},"query":{"type":"string"},"expected_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"}},"unanswerable":{"type":"boolean"},"passed":{"type":"boolean"},"found_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The expected documents that appeared in the result set."},"first_rank":{"type":"integer","description":"1-based rank of the first expected document, 0 when none appeared."},"top_score":{"type":["number","null"],"format":"double","description":"The best hit's score, null when nothing was retrieved."},"coverage":{"$ref":"#/components/schemas/RetrievalCoverage","description":"The verdict retrieval gave; absent when the search failed."},"latency_ms":{"type":["integer","null"]},"error":{"type":"string"},"hits":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseEvaluationHit"}},"created_at":{"type":"string","format":"date-time"}},"required":["case_id","query","expected_document_ids","unanswerable","passed","found_document_ids","first_rank","top_score","latency_ms","hits","created_at"],"description":"One case's outcome inside one evaluation. An answerable case passes\nwhen an expected document is in the result set and retrieval did not\nrefuse; an unanswerable one passes when retrieval refused or found\nnothing.\n","title":"KnowledgeBaseEvaluationResult"},"ListKnowledgeBaseEvaluationResultsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"results":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseEvaluationResult"}}},"required":["next_cursor","has_more","results"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/evaluations/{evaluation_id}/results`.","title":"ListKnowledgeBaseEvaluationResultsResponse"},"KnowledgeBaseDocumentInjectionMode":{"type":"string","enum":["on_demand","always"],"description":"`always` places the document in every prompt of the agents its\nknowledge base is attached to; `on_demand` (the default) leaves it\non retrieval. A knowledge base small enough to fit the automatic\nbudget is placed in the prompt whole regardless of this setting.\n","title":"KnowledgeBaseDocumentInjectionMode"},"KnowledgeBaseDocumentStatus":{"type":"string","enum":["fetching","embedding","ready","failed"],"description":"Document lifecycle. `fetching` is the pre-scrape state used\nonly by url-sourced rows; file and text docs skip straight\nto `embedding` because their content is available\nsynchronously. Terminal states are `ready` and `failed`.\n","title":"KnowledgeBaseDocumentStatus"},"KnowledgeBaseDocument":{"type":"object","properties":{"id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`) of\nthe knowledge base the document belongs to.\n"},"source_kind":{"$ref":"#/components/schemas/KnowledgeBaseDocumentSourceKind"},"source_url":{"type":"string","description":"Source URL for url-sourced documents (and the sitemap /\ncrawl imports that produce them). Empty string for file\nand text rows.\n"},"folder_id":{"type":["string","null"],"pattern":"^kfolder_[0-9a-hjkmnp-tv-z]{26}$","description":"Folder this document lives in. Null for root-level\n(unfiled) documents. Mutated via the move endpoint.\n"},"filename":{"type":"string"},"content_type":{"type":"string"},"byte_size":{"type":"integer","format":"int64"},"char_count":{"type":"integer"},"chunk_count":{"type":"integer"},"injection_mode":{"$ref":"#/components/schemas/KnowledgeBaseDocumentInjectionMode","description":"`always` places the document in every prompt of the agents its\nknowledge base is attached to; `on_demand` (the default) leaves it\non retrieval. A knowledge base small enough to fit the automatic\nbudget is placed in the prompt whole regardless of this setting.\n"},"status":{"$ref":"#/components/schemas/KnowledgeBaseDocumentStatus"},"error":{"type":"string","description":"Populated when status is failed."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","kb_id","source_kind","folder_id","filename","content_type","byte_size","char_count","chunk_count","injection_mode","status","created_at","updated_at"],"title":"KnowledgeBaseDocument"},"ListKnowledgeBaseDocumentsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"documents":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}},"required":["next_cursor","has_more","documents"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListKnowledgeBaseDocumentsResponse"},"KnowledgeBaseDocumentDetailInjectionMode":{"type":"string","enum":["on_demand","always"],"description":"`always` places the document in every prompt of the agents its\nknowledge base is attached to; `on_demand` (the default) leaves it\non retrieval. A knowledge base small enough to fit the automatic\nbudget is placed in the prompt whole regardless of this setting.\n","title":"KnowledgeBaseDocumentDetailInjectionMode"},"DependentAgent":{"type":"object","properties":{"id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"}},"required":["id","name"],"description":"Minimal agent pointer (id + name) used by the document\ndetail view to render a clickable link to each agent that\nhas the document's KB attached.\n","title":"DependentAgent"},"RefreshConfig":{"type":"object","properties":{"enabled":{"type":"boolean"},"interval_days":{"type":"integer"},"auto_remove_enabled":{"type":"boolean","description":"Delete the document once the source itself has answered \"page gone\"\n(HTTP 404 or 410) on 5 consecutive refreshes. A transient failure\n(outage, timeout, block) never removes a document: the last\nsuccessfully fetched version keeps serving and the failure streak\nis reported in `consecutive_fetch_failures`.\n"},"last_refreshed_at":{"type":["string","null"],"format":"date-time"},"consecutive_fetch_failures":{"type":"integer","description":"Refreshes in a row that failed to fetch the source. Reset to zero\nby the next successful fetch. While non-zero, the document still\nserves its last successfully fetched version.\n"}},"required":["enabled","interval_days","auto_remove_enabled","last_refreshed_at","consecutive_fetch_failures"],"description":"Per-document auto-refresh state. Only populated\nfor url-sourced documents; file and text rows omit this and\nclients hide auto-refresh affordances accordingly.\n","title":"RefreshConfig"},"KnowledgeBaseDocumentDetail":{"type":"object","properties":{"id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`) of\nthe knowledge base the document belongs to.\n"},"source_kind":{"$ref":"#/components/schemas/KnowledgeBaseDocumentSourceKind"},"source_url":{"type":"string","description":"Source URL for url-sourced documents (and the sitemap /\ncrawl imports that produce them). Empty string for file\nand text rows.\n"},"folder_id":{"type":["string","null"],"pattern":"^kfolder_[0-9a-hjkmnp-tv-z]{26}$","description":"Folder this document lives in. Null for root-level\n(unfiled) documents. Mutated via the move endpoint.\n"},"filename":{"type":"string"},"content_type":{"type":"string"},"byte_size":{"type":"integer","format":"int64"},"char_count":{"type":"integer"},"chunk_count":{"type":"integer"},"injection_mode":{"$ref":"#/components/schemas/KnowledgeBaseDocumentDetailInjectionMode","description":"`always` places the document in every prompt of the agents its\nknowledge base is attached to; `on_demand` (the default) leaves it\non retrieval. A knowledge base small enough to fit the automatic\nbudget is placed in the prompt whole regardless of this setting.\n"},"status":{"$ref":"#/components/schemas/KnowledgeBaseDocumentStatus"},"error":{"type":"string","description":"Populated when status is failed."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"content_preview":{"type":"string"},"token_count":{"type":"integer","description":"The document's size in prompt tokens, the number the prompt budget counts."},"preview_truncated":{"type":"boolean"},"dependent_agents":{"type":"array","items":{"$ref":"#/components/schemas/DependentAgent"}},"refresh":{"$ref":"#/components/schemas/RefreshConfig"}},"required":["id","kb_id","source_kind","folder_id","filename","content_type","byte_size","char_count","chunk_count","injection_mode","status","created_at","updated_at","content_preview","token_count","preview_truncated","dependent_agents"],"description":"Payload of GET /v1/agents/knowledge-bases/documents/{document_id}. Extends\nthe list-view document with a bounded content preview, the\nlist of dependent agents, and (for url-sourced docs) the\nauto-refresh state.\n","title":"KnowledgeBaseDocumentDetail"},"V1AgentsKnowledgeBasesKbIdDocumentsDocumentIdPatchRequestBodyContentApplicationJsonSchemaInjectionMode":{"type":"string","enum":["on_demand","always"],"description":"`always` keeps the document in every prompt; `on_demand`\nreturns it to retrieval.\n","title":"V1AgentsKnowledgeBasesKbIdDocumentsDocumentIdPatchRequestBodyContentApplicationJsonSchemaInjectionMode"},"KnowledgeBaseChunk":{"type":"object","properties":{"id":{"type":"string","pattern":"^chunk_[0-9a-hjkmnp-tv-z]{26}$"},"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`) of\nthe knowledge base the chunk belongs to.\n"},"chunk_index":{"type":"integer"},"content":{"type":"string"},"context":{"type":"string","description":"A sentence or two that situates the passage within its document,\nwritten at ingest when contextual chunks are on. Absent otherwise.\n"},"heading_path":{"type":"array","items":{"type":"string"},"description":"The headings the chunk sits under, outermost first; empty when the document has none."}},"required":["id","document_id","kb_id","chunk_index","content","heading_path"],"title":"KnowledgeBaseChunk"},"ListKnowledgeBaseChunksResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"chunks":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseChunk"}}},"required":["next_cursor","has_more","chunks"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListKnowledgeBaseChunksResponse"},"CreateCrawlImportRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"max_pages":{"type":"integer"},"max_depth":{"type":"integer"},"folder_id":{"type":["string","null"],"description":"Folder to import the documents into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["url"],"title":"CreateCrawlImportRequest"},"ImportJobKind":{"type":"string","enum":["sitemap","crawl","refresh","urls"],"title":"ImportJobKind"},"ImportJob":{"type":"object","properties":{"id":{"type":"string","pattern":"^import_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"kind":{"$ref":"#/components/schemas/ImportJobKind"},"status":{"$ref":"#/components/schemas/JobStatus"},"total":{"type":"integer","description":"Total URLs to process (the progress denominator)."},"completed":{"type":"integer","description":"URLs imported successfully."},"failed":{"type":"integer","description":"URLs that failed to import."},"params":{"type":"object","additionalProperties":{"description":"Any type"},"description":"JSON blob whose shape depends on `kind` \u2014 typically `url`,\n`max_pages`, `max_depth`. Read it for display\nonly.\n"},"error":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}},"required":["id","kb_id","kind","status","total","completed","failed","params","created_at","updated_at","started_at","ended_at"],"description":"Async URL import job (sitemap, crawl, plus\nthe auto-refresh path). Poll\n`GET /v1/agents/knowledge-bases/{kb_id}/imports` while the job is\nnon-terminal.\n","title":"ImportJob"},"CreateSitemapImportRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"folder_id":{"type":["string","null"],"description":"Folder to import the documents into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["url"],"title":"CreateSitemapImportRequest"},"CreateTextDocumentRequest":{"type":"object","properties":{"name":{"type":"string"},"content":{"type":"string"},"folder_id":{"type":["string","null"],"description":"Folder to drop the document into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["name","content"],"description":"Body for POST /v1/agents/knowledge-bases/{kb_id}/documents/text.","title":"CreateTextDocumentRequest"},"CreateURLDocumentRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"folder_id":{"type":["string","null"],"description":"Folder to drop the document into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["url"],"description":"Body for POST /v1/agents/knowledge-bases/{kb_id}/documents/url.","title":"CreateURLDocumentRequest"},"CreateURLBatchImportRequest":{"type":"object","properties":{"urls":{"type":"array","items":{"type":"string","format":"uri"}},"folder_id":{"type":["string","null"],"description":"Folder to import the documents into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["urls"],"description":"Body for POST /v1/agents/knowledge-bases/{kb_id}/documents/urls. Submit\n1..N URLs in a single async import. The server dedupes and\nvalidates each entry before queueing. The per-import cap is\noperator-tunable (default 250) via kbUrlBatchMaxUrls; the\nserver returns 400 when the resolved list exceeds the cap, so\nno maxItems is encoded in the schema to avoid SDK-side false\nrejections when an operator raises the limit.\n","title":"CreateURLBatchImportRequest"},"BatchDeleteDocumentsRequest":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}}},"required":["ids"],"description":"Body for DELETE /v1/agents/knowledge-bases/{kb_id}/documents/batch. All\nids must belong to the supplied KB; capped at 200 ids per\ncall.\n","title":"BatchDeleteDocumentsRequest"},"BatchMoveDocumentsRequest":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}},"folder_id":{"type":["string","null"],"description":"Destination folder. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null moves every\ndocument to the knowledge base root.\n"}},"required":["ids","folder_id"],"description":"Body for PATCH /v1/agents/knowledge-bases/{kb_id}/documents/batch.\nCapped at 200 ids per call. Pass `folder_id: null` to move to\nroot.\n","title":"BatchMoveDocumentsRequest"},"BatchMoveDocumentsResponse":{"type":"object","properties":{"documents":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseDocumentDetail"}}},"required":["documents"],"description":"The documents moved by a batch-move request, each in the same detail\nshape the single-document move\n(`PATCH /v1/agents/knowledge-bases/{kb_id}/documents/{document_id}`)\nreturns.\n","title":"BatchMoveDocumentsResponse"},"UpdateRefreshConfigRequest":{"type":"object","properties":{"enabled":{"type":"boolean"},"interval_days":{"type":"integer"},"auto_remove_enabled":{"type":"boolean","description":"Delete the document once the source itself has answered \"page gone\"\n(HTTP 404 or 410) on 5 consecutive refreshes. Transient fetch\nfailures never remove a document.\n"}},"description":"PATCH body \u2014 every field optional.","title":"UpdateRefreshConfigRequest"},"RefreshHistoryEntryStatus":{"type":"string","enum":["running","changed","unchanged","failed","removed"],"title":"RefreshHistoryEntryStatus"},"RefreshHistoryEntry":{"type":"object","properties":{"id":{"type":"string","pattern":"^refresh_[0-9a-hjkmnp-tv-z]{26}$"},"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"started_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"},"status":{"$ref":"#/components/schemas/RefreshHistoryEntryStatus"},"error":{"type":"string"},"previous_hash":{"type":"string"},"new_hash":{"type":"string"}},"required":["id","document_id","started_at","ended_at","status"],"description":"One auto-refresh attempt. `running` only appears mid-tick;\nterminal values are the ones the drawer renders.\n","title":"RefreshHistoryEntry"},"ListRefreshHistoryResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"entries":{"type":"array","items":{"$ref":"#/components/schemas/RefreshHistoryEntry"}}},"required":["next_cursor","has_more","entries"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/documents/{document_id}/refresh-history`.","title":"ListRefreshHistoryResponse"},"KnowledgeBaseFolder":{"type":"object","properties":{"id":{"type":"string","pattern":"^kfolder_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`) of\nthe owning knowledge base.\n"},"parent_folder_id":{"type":["string","null"],"pattern":"^kfolder_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"document_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","kb_id","parent_folder_id","name","document_count","created_at","updated_at"],"description":"Folder inside a knowledge base. Root-level folders have\n`parent_folder_id: null`. `document_count` is populated only\non the list endpoint.\n","title":"KnowledgeBaseFolder"},"ListKnowledgeBaseFoldersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"folders":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseFolder"}}},"required":["next_cursor","has_more","folders"],"description":"Flat list of folders for a knowledge base. Build the folder\ntree from `parent_folder_id` references, so callers should\nwalk every page before rendering.\n","title":"ListKnowledgeBaseFoldersResponse"},"CreateFolderRequest":{"type":"object","properties":{"name":{"type":"string"},"parent_folder_id":{"type":["string","null"],"description":"Parent folder. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted creates a\nroot-level folder.\n"}},"required":["name"],"title":"CreateFolderRequest"},"UpdateFolderRequest":{"type":"object","properties":{"name":{"type":"string"},"parent_folder_id":{"type":["string","null"],"description":"Folder to reparent under (prefixed wire identifier\n`kfolder_<26 char Crockford base32>`), or `null` to move the\nfolder to the knowledge base root. Omit to leave unchanged.\n"}},"description":"PATCH body (JSON merge-patch). All fields optional; omit a field\nto leave it unchanged. Set `parent_folder_id` to reparent into\nthat folder, or send `parent_folder_id: null` to move the folder\nto the knowledge base root.\n","title":"UpdateFolderRequest"},"ListImportJobsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"jobs":{"type":"array","items":{"$ref":"#/components/schemas/ImportJob"}}},"required":["next_cursor","has_more","jobs"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/imports`.","title":"ListImportJobsResponse"},"AttachedKnowledgeBasesResponseInjection":{"type":"object","properties":{"whole":{"type":"boolean"},"corpus_tokens":{"type":"integer"},"auto_tokens":{"type":"integer"},"pinned_documents":{"type":"integer"}},"required":["whole","auto_tokens","pinned_documents"],"description":"How the agent's attached knowledge reaches the model, on the\nfirst page only. `whole` means the entire attached corpus rides\nin the prompt and the agent never searches (`corpus_tokens` is\nits size); otherwise retrieval is on and `pinned_documents`\ncounts the documents pinned into every prompt beside it.\n`auto_tokens` is the automatic tier's budget, 0 when off.\n","title":"AttachedKnowledgeBasesResponseInjection"},"AttachedKnowledgeBasesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"knowledge_bases":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBase"}},"injection":{"$ref":"#/components/schemas/AttachedKnowledgeBasesResponseInjection","description":"How the agent's attached knowledge reaches the model, on the\nfirst page only. `whole` means the entire attached corpus rides\nin the prompt and the agent never searches (`corpus_tokens` is\nits size); otherwise retrieval is on and `pinned_documents`\ncounts the documents pinned into every prompt beside it.\n`auto_tokens` is the automatic tier's budget, 0 when off.\n"}},"required":["next_cursor","has_more","knowledge_bases"],"description":"Cursor-paginated list of the knowledge bases attached to an agent.\n","title":"AttachedKnowledgeBasesResponse"},"ToolKind":{"type":"string","enum":["builtin","webhook","client","mcp"],"description":"What kind of tool this is, and where it executes.\n- `builtin`: a worker-resident platform capability (e.g. end_call, play_audio), configured per-agent\n- `webhook`: worker signs a payload and POSTs it to your URL\n- `client`: worker dispatches to the caller's browser/SDK via data channel\n- `mcp`: worker connects to a customer-hosted MCP server and proxies tool calls\n","title":"ToolKind"},"SystemBuiltin":{"type":"string","description":"Identifier of a built-in system tool. New builtins are added by\nSpeechify across releases. Read the catalogue from\n`GET /v1/agents/tool-capabilities` rather than depending on this\nstring set staying stable across releases.\n","title":"SystemBuiltin"},"ToolParamType":{"type":"string","enum":["string","number","integer","boolean"],"description":"Permitted JSON-Schema primitive types for tool params.","title":"ToolParamType"},"ToolParam":{"type":"object","properties":{"name":{"type":"string"},"type":{"$ref":"#/components/schemas/ToolParamType"},"description":{"type":"string"},"required":{"type":"boolean"},"enum":{"type":"array","items":{"type":"string"}}},"required":["name","type","description","required"],"description":"One argument the LLM can pass when calling the tool. Mirrors the JSON-Schema subset standard function-calling schemas support.","title":"ToolParam"},"BuiltinToolConfig":{"type":"object","properties":{"builtin":{"$ref":"#/components/schemas/SystemBuiltin"},"params":{"type":"array","items":{"$ref":"#/components/schemas/ToolParam"}},"builtin_config":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-builtin extras, keyed by the `builtin` above.\n\n`transfer_to_number` takes `mode` (`cold`, the default, hands the\ncaller's own line to the carrier via SIP REFER; `warm` calls the\ndestination first, reads them a summary of the conversation while\nthe caller holds, and bridges the two once they accept),\n`hold_message` (warm only; the line spoken before the caller goes\non hold, `{{variable}}` tokens resolved, platform default when\nempty), and `allowed_numbers` (destinations the LLM may pass, in\neither mode; omit to allow any).\n\nWarm mode takes two more, both optional, and with neither set the\nperson hears the brief the platform writes today.\n`briefing_instructions` steers how that brief is written - what to\nlead with, what to leave out, how long to be - and is applied\nUNDER the platform's own brief-writing rules rather than in place\nof them, so the call transcript stays quoted evidence no\ninstruction inside it can escape (max 2000 characters).\n`briefing_template` replaces the generated brief with a fixed\nline, for a handoff that must state the same facts every time:\n`{{variable}}` tokens resolve as they do in `hold_message`, and\n`{{system__summary}}` resolves to the generated brief, so a\ntemplate can pin the facts first and still hand over the rest of\nthe call. That token sits in the reserved `system__` namespace, so\nit can never shadow a variable of your own. A template that never\nnames it skips generating one (max\n600 characters, short enough that the line and the \"press 1 to\ntake the call\" after it fit one playout).\n\nWarm mode also takes `ringing_timeout_ms`, how long the\ndestination may ring before the transfer is given up on. Omit it\nto ring for 30 seconds. The accepted range is the one an outbound call\nalready publishes, 1000-80000 ms; a whole number of milliseconds\noutside it is rejected here, and on the flow node, rather than\nquietly shortened.\nTransferring into a queue usually needs longer than 30 seconds,\nbecause the queue's own rollover never gets the chance to fire.\n\n`play_audio` takes `audio_asset_id`.\n"}},"required":["builtin"],"description":"Config shape for `kind=builtin`. The `builtin` value names the\nworker-resident capability; the catalogue served by\n`GET /v1/agents/tool-capabilities` is the runtime source of truth\nfor valid names plus their human-readable labels. `builtin_config`\ncarries per-instance extras (e.g. mode + hold_message +\nallowed_numbers for transfer_to_number, audio_asset_id for\nplay_audio).\n","title":"BuiltinToolConfig"},"WebhookToolConfigMethod":{"type":"string","enum":["POST","GET"],"title":"WebhookToolConfigMethod"},"LongRunningToolConfigWaitAudioPreset":{"type":"string","enum":["office","city","forest","crowded_room","keyboard_typing","hold_music"],"description":"Which bundled clip to loop. One roster and one mix level\nshared with the agent-level background noise, so a preset\nsounds the same whichever surface picks it.\n","title":"LongRunningToolConfigWaitAudioPreset"},"LongRunningToolConfigWaitAudio":{"type":"object","properties":{"preset":{"$ref":"#/components/schemas/LongRunningToolConfigWaitAudioPreset","description":"Which bundled clip to loop. One roster and one mix level\nshared with the agent-level background noise, so a preset\nsounds the same whichever surface picks it.\n"}},"required":["preset"],"description":"A sound looped under the wait, instead of - or beneath - the\nspoken phrase, for the caller who expects hold music rather\nthan an agent repeating itself. It starts on the same dwell as\n`filler_message` (`filler_delay_ms` with nobody speaking) and\nstops the moment the tool settles, so the real answer never\nqueues behind it. Omit to leave the wait silent; the phrase and\nthe sound are independent, so set either, both, or neither.\n","title":"LongRunningToolConfigWaitAudio"},"LongRunningToolConfigOnDuplicate":{"type":"string","enum":["reject","allow"],"description":"What happens when the model calls this tool while a previous\ncall of the SAME tool is still running. `reject` (the default\nwhenever `long_running` is set) refuses the second call and\ntells the model the first is still in flight, so a slow\nside-effecting webhook cannot be fired twice in one turn.\n`allow` restores the unguarded behaviour for a read-only tool\nthe model legitimately fans out over several arguments \u2014\nduplicates are matched by tool name alone, never by\narguments.\n","title":"LongRunningToolConfigOnDuplicate"},"LongRunningToolConfig":{"type":"object","properties":{"filler_message":{"type":"string","minLength":1,"maxLength":200,"description":"Phrase spoken verbatim once the tool has been running for\n`filler_delay_ms`, e.g. \"one sec, pulling that up\". Write it\nin your agent's own language and voice \u2014 Speechify ships no\ndefault phrase, so nothing we invented can reach your caller\nin the wrong language. Omit to arm the duplicate policy\nwithout any spoken filler.\n\nOn a multilingual agent the phrase is spoken only while the\ncall is still in the agent's configured `language`; after a\nmid-call language switch the filler is suppressed rather than\nspoken in the language you wrote it in.\n"},"filler_delay_ms":{"type":"integer","minimum":500,"maximum":30000,"description":"How long the tool must have been running, with nobody\nspeaking, before the filler is spoken. Defaults to 2000. The\nwait restarts whenever the agent or the caller speaks, so the\nfiller never lands on top of live speech.\n"},"additional_filler_messages":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"description":"Phrases for the second and later times the agent fills, in\norder, so a tool that outlives one phrase does not repeat it\nword for word - which is what makes an agent sound broken\nrather than busy. The last entry repeats once the list runs\nout. Requires `filler_message` and `filler_interval_ms`.\n"},"filler_interval_ms":{"type":"integer","minimum":2000,"maximum":30000,"description":"Cooldown before the filler may be spoken again while the tool\nis still running. Omit to speak it at most once, which covers\nthe common few-second tool; set it for a tool whose timeout\nruns to tens of seconds. Capped at 10 utterances per call\nregardless. Requires `filler_message`.\n"},"wait_audio":{"$ref":"#/components/schemas/LongRunningToolConfigWaitAudio","description":"A sound looped under the wait, instead of - or beneath - the\nspoken phrase, for the caller who expects hold music rather\nthan an agent repeating itself. It starts on the same dwell as\n`filler_message` (`filler_delay_ms` with nobody speaking) and\nstops the moment the tool settles, so the real answer never\nqueues behind it. Omit to leave the wait silent; the phrase and\nthe sound are independent, so set either, both, or neither.\n"},"on_duplicate":{"$ref":"#/components/schemas/LongRunningToolConfigOnDuplicate","description":"What happens when the model calls this tool while a previous\ncall of the SAME tool is still running. `reject` (the default\nwhenever `long_running` is set) refuses the second call and\ntells the model the first is still in flight, so a slow\nside-effecting webhook cannot be fired twice in one turn.\n`allow` restores the unguarded behaviour for a read-only tool\nthe model legitimately fans out over several arguments \u2014\nduplicates are matched by tool name alone, never by\narguments.\n"}},"description":"How the agent keeps a live call sane while a slow tool runs.\nPresent means the tool is declared slow; absent keeps the tool's\nprevious behaviour exactly.\n\nThe tool still blocks the turn either way: the agent never\nreceives a synthetic result while your endpoint is still working,\nso it can only ever speak about a result it actually got.\n","title":"LongRunningToolConfig"},"WebhookToolConfig":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"method":{"$ref":"#/components/schemas/WebhookToolConfigMethod"},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Static headers sent with every call. `Authorization` and `Speechify-Signature` are reserved."},"timeout_ms":{"type":"integer","description":"Per-call timeout in milliseconds. Defaults to 10000 server-side when omitted."},"params":{"type":"array","items":{"$ref":"#/components/schemas/ToolParam"}},"fire_and_forget":{"type":"boolean","description":"When true the worker dispatches the HTTP request and returns\nimmediately to the LLM with a synthetic \"queued\" result\ninstead of waiting for the response body. The customer's\nendpoint is expected to enqueue the work and return any\nnon-error status quickly; errors raised after dispatch are\nlogged but never surfaced to the LLM. Use for long-running\ncustomer-side work (job triggers, async ticket creation,\netc.) where blocking the call on the response would hurt\nthe conversation. Defaults to false.\n"},"long_running":{"$ref":"#/components/schemas/LongRunningToolConfig","description":"Declares the endpoint slow, so the agent speaks a filler\nphrase instead of leaving the caller in silence and refuses\na duplicate call while one is in flight.\n\nCannot be combined with `fire_and_forget`. That path returns\nto the agent before your endpoint answers, so there is no\nwait to fill; saving both is refused rather than storing a\nholding phrase that can never be spoken.\n"},"requires_approval":{"type":"boolean","description":"Legacy spelling of `approval: require_approval` on the tool\ndefinition. When true a durable run obtains human approval before\nthis tool runs. Prefer the definition-level `approval` field.\n"}},"required":["url"],"description":"Config shape for `kind=webhook`.\n\nOn a durable run (`POST /v1/agents/{agent_id}/runs`) your endpoint is\ntold `user_identity`: the person the agent is acting for, exactly as\nyour application supplied it when it started the run. The field is\nabsent when the run is acting for nobody in particular, so treat an\nabsent value as \"no person\", never as a default one. Voice\nconversations and sessions do not carry it yet.\n\nOn `method: POST` it rides inside the **signed** JSON body rather than\na header, so an endpoint deciding whose data to touch can verify the\nanswer with the same HMAC it already checks. On `method: GET` there is\nno body to sign, so it arrives as a **`user_identity` query\nparameter**, unverifiable exactly as the arguments beside it are: a\nGET's signature covers an envelope that is not on the wire. Use POST\nfor any endpoint that authorizes on who the call is for. A tool\nargument of the same name never overrides it.\n","title":"WebhookToolConfig"},"ClientToolConfig":{"type":"object","properties":{"params":{"type":"array","items":{"$ref":"#/components/schemas/ToolParam"}},"timeout_ms":{"type":"integer","description":"Per-call timeout in milliseconds. Defaults to 10000 server-side when omitted."}},"description":"Config shape for `kind=client`. Execution happens in the caller's browser / SDK.","title":"ClientToolConfig"},"MCPTransport":{"type":"string","enum":["http_streamable","sse"],"description":"MCP transport. `http_streamable` is the default; `sse` is the\nlegacy fallback for servers that haven't migrated yet.\n","title":"MCPTransport"},"MCPAuth":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["none"],"description":"Discriminator value: none"}},"required":["type"],"description":"none variant"},{"type":"object","properties":{"type":{"type":"string","enum":["bearer"],"description":"Discriminator value: bearer"},"credential_id":{"type":"string","description":"`cred_` id of a `bearer` credential in the workspace\nvault. Create the credential first via `POST /v1/credentials`,\nthen reference it here.\n"}},"required":["type","credential_id"],"description":"Bearer auth for an MCP server. References a workspace credential of\nkind `bearer` by id; the secret lives in the credentials vault and is\nresolved server-side at dispatch, never inlined on the tool.\n"},{"type":"object","properties":{"type":{"type":"string","enum":["oauth2_client_credentials"],"description":"Discriminator value: oauth2_client_credentials"},"credential_id":{"type":"string","description":"`cred_` id of an `oauth2_client_credentials` credential\nin the workspace vault.\n"}},"required":["type","credential_id"],"description":"OAuth2 client-credentials auth for an MCP server. References a\nworkspace credential of kind `oauth2_client_credentials` by id; the\ntoken_url / client_id / client_secret / scopes all live in the vault\ncredential and are resolved server-side at dispatch.\n"}],"discriminator":{"propertyName":"type"},"description":"Discriminated union over `type`.","title":"MCPAuth"},"ToolActionClass":{"type":"string","enum":["read","communicate_internal","communicate_external","data_export","financial","irreversible_other"],"description":"The impact class a tool declares: what kind of consequence a call has,\nindependent of which transport executes it. It decides the approval an\nautonomous run applies by default.\n\n- `read`: a lookup with no side effect.\n- `communicate_internal`: writes inside your own estate (a CRM note, a\n post to your own Slack, a row in a store).\n- `communicate_external`: reaches a third party (an email, an SMS).\n- `data_export`: moves a data set out.\n- `financial`: moves money or commits you to a charge.\n- `irreversible_other`: any other action that cannot be undone.\n","title":"ToolActionClass"},"MCPToolConfig":{"type":"object","properties":{"endpoint":{"type":"string","format":"uri"},"transport":{"$ref":"#/components/schemas/MCPTransport"},"auth":{"$ref":"#/components/schemas/MCPAuth"},"long_running":{"$ref":"#/components/schemas/LongRunningToolConfig","description":"Declares the SERVER slow. Applies to every remote tool the\n`list_tools` handshake discovers on it \u2014 MCP publishes no\nper-tool latency signal at config time, so this describes the\nserver rather than one of its verbs.\n"},"action_classes":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ToolActionClass"},"description":"What each of the server's own tools does, keyed by the remote tool\nname. One MCP tool is a whole server of mixed verbs, so without\nthis the server carries a single impact and an autonomous run\neither treats its writes as safe or stops for a human on its\nlookups. Classify the lookups `read` and they run unattended.\n\nA tool you do not name here keeps the server-level class, which is\ndeliberately the cautious one. An explicit server-level `approval`\nstill wins over everything here, so gating a whole server stays a\nsingle switch you can trust.\n\nYour server can also classify its own tools: a `tools/list` entry\ncarrying `_meta: {\"speechify/action_class\": \"communicate_external\"}`\ndeclares that tool's impact. The declaration is honoured under the\nceiling set by the tool definition's `action_class`: a declared\nclass at least as strong as the definition's is used, a weaker one\nis held to the definition's, and an unclassified definition\n(`irreversible_other`) makes every declaration moot. So one server\nwith `action_class: read` runs its reads unattended and parks a run\non the tool it declared `communicate_external`, with no second\nserver and no entry here. What you declare here beats what the\nserver declares.\n"}},"required":["endpoint","auth"],"description":"Config shape for `kind=mcp`. The worker opens the\nconfigured transport at session start, runs `initialize` +\n`list_tools`, and registers each discovered remote tool as a\nlivekit-agents function_tool proxying through the long-lived\nClientSession.\n\nOn a durable run (`POST /v1/agents/{agent_id}/runs`) every request to\nyour server carries a `Speechify-User-Identity` header holding the\nperson the agent is acting for, exactly as your application supplied\nit when it started the run. This is what lets a server you wrote hold\nyour users' third-party credentials and act for the right one; the\nheader is absent when the run is acting for nobody. It is a header\nrather than a body field because MCP owns its own JSON-RPC envelope,\nso trust it exactly as far as you trust the connection your server\nalready authenticated. Voice conversations and sessions do not carry\nit yet.\n\nEvery request also carries `Speechify-Idempotency-Key`, stable for one\nstep of one run and identical to the `Idempotency-Key` a webhook tool\nreceives. A durable run is retried if the platform redelivers it, so a\ntool that sends mail or takes payment can be called more than once for\nthe same decision: key on this header and refuse to act twice. One\nconnector serving both transports dedups on one identifier. The key\nis `:`, and `GET /v1/agents/runs/{run_id}` resolves the\nrun, its agent and the person it acts for from the id alone.\n","title":"MCPToolConfig"},"AgentToolConfig":{"oneOf":[{"$ref":"#/components/schemas/BuiltinToolConfig"},{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"description":"Resolved config - `BuiltinToolConfig`, `WebhookToolConfig`, `ClientToolConfig`, or `MCPToolConfig` depending on `kind`.","title":"AgentToolConfig"},"ToolApprovalClass":{"type":"string","enum":["auto","require_approval","disabled"],"description":"What governs a tool on the autonomous path (durable runs), where no\nhuman is on the line: `auto` runs it, `require_approval` parks the run\nin `requires_action` until a human decides, `disabled` refuses it. The\nrealtime voice and text runtimes have no suspend primitive and ignore\nit; use the per-agent tool's `enabled` flag to switch a tool off there.\n","title":"ToolApprovalClass"},"AgentTool":{"type":"object","properties":{"id":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`tool_<26 char Crockford base32>`), stable across kinds."},"kind":{"$ref":"#/components/schemas/ToolKind"},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean","description":"When false the tool is configured but skipped at dispatch."},"definition_id":{"type":["string","null"],"description":"Provenance: the backing workspace tool definition id for\nshared kinds (webhook/client/mcp), or `null` for a built-in.\nNon-null \u27fa the tool's config is shared and edited at\n`/v1/agents/tool-definitions/{tool_definition_id}`.\n"},"config":{"$ref":"#/components/schemas/AgentToolConfig","description":"Resolved config - `BuiltinToolConfig`, `WebhookToolConfig`, `ClientToolConfig`, or `MCPToolConfig` depending on `kind`."},"webhook_secret":{"type":"string","description":"HMAC signing secret for a `webhook` tool. Returned in full\n**only** on the create response; subsequent reads return a\nmasked placeholder.\n"},"action_class":{"oneOf":[{"$ref":"#/components/schemas/ToolActionClass"},{"type":"null"}],"description":"The impact class you declared on the shared definition; the platform\nfixes it for a built-in.\n"},"effective_action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact the tool actually carries, derived from its shape when none was declared."},"approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"The explicit override on the shared definition. Absent for a built-in or when derived."},"effective_approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"What governs the tool on the autonomous path. Present for the\nshared kinds a durable run can execute; absent for a built-in,\nwhich only ever runs in-process on a live session.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","kind","name","description","enabled","definition_id","config","action_class","effective_action_class","created_at","updated_at"],"description":"One thing an agent can do, in the unified per-agent view. Kind\ndiscriminates a per-agent built-in instance (`builtin`) from an\nattached workspace tool definition (`webhook`/`client`/`mcp`).\nThe config is resolved + embedded so consumers never have to fetch\nthe backing definition. `id` is a `tool_<...>` for every kind.\n","title":"AgentTool"},"ListAgentToolsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"tools":{"type":"array","items":{"$ref":"#/components/schemas/AgentTool"}}},"required":["next_cursor","has_more","tools"],"description":"The agent's full, mixed-kind toolbelt, cursor-paginated.\n","title":"ListAgentToolsResponse"},"CreateAgentToolRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/BuiltinToolConfig"},{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"title":"CreateAgentToolRequestConfig"},"CreateAgentToolRequest":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/ToolKind"},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean","description":"Defaults to true on the server when omitted."},"config":{"$ref":"#/components/schemas/CreateAgentToolRequestConfig"},"action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact class for a shared kind (`webhook`/`client`/`mcp`),\nwhose definition is created here. Omitted, the server stamps the\nverb default. Ignored for a built-in, whose class the platform fixes.\n"},"approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"Overrides the approval derived from the class, for a shared kind."}},"required":["kind","name","config"],"description":"Add a tool to an agent. With `kind=builtin`, `config` carries a\n`BuiltinToolConfig` and a per-agent built-in instance is created.\nWith `kind=webhook`/`client`/`mcp`, `config` carries that kind's\nconfig and a workspace definition is created AND attached in one\ncall.\n","title":"CreateAgentToolRequest"},"UpdateAgentToolRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/BuiltinToolConfig"}],"description":"For a built-in, the full `BuiltinToolConfig`. Rejected for shared kinds.","title":"UpdateAgentToolRequestConfig"},"UpdateAgentToolRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"config":{"$ref":"#/components/schemas/UpdateAgentToolRequestConfig","description":"For a built-in, the full `BuiltinToolConfig`. Rejected for shared kinds."},"params":{"type":"array","items":{"$ref":"#/components/schemas/ToolParam"}},"enabled":{"type":"boolean"}},"description":"PATCH body. For a built-in, all fields apply. For an attached\nexternal tool only `enabled` is honoured - editing the shared\nconfig/name from here returns `tool_config_shared`.\n","title":"UpdateAgentToolRequest"},"ToolConfig":{"oneOf":[{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"description":"One of `WebhookToolConfig`, `ClientToolConfig`, or `MCPToolConfig` depending on `kind`.","title":"ToolConfig"},"Tool":{"type":"object","properties":{"id":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`tool_<26 char Crockford base32>`).\n"},"name":{"type":"string"},"description":{"type":"string"},"kind":{"$ref":"#/components/schemas/ToolKind"},"config":{"$ref":"#/components/schemas/ToolConfig","description":"One of `WebhookToolConfig`, `ClientToolConfig`, or `MCPToolConfig` depending on `kind`."},"webhook_secret":{"type":"string","description":"HMAC signing secret for `kind=webhook`. Returned in full **only** on the create\nresponse; all subsequent reads return a masked placeholder. Store it on first\ncreate \u2014 there is no way to retrieve it later.\n"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"action_class":{"oneOf":[{"$ref":"#/components/schemas/ToolActionClass"},{"type":"null"}],"description":"The impact class you declared. Null means you declared none and the\ntool's impact is read off its shape instead - see\n`effective_action_class`.\n"},"effective_action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact the tool actually carries: `action_class` when you set\none, otherwise the class its shape implies (a `GET` webhook reads, a\n`POST` webhook reaches outside your team, an MCP or client tool can\ndo anything its author wired). Derived on every read, so changing a\nwebhook's method changes this with it. Read-only.\n"},"approval":{"oneOf":[{"$ref":"#/components/schemas/ToolApprovalClass"},{"type":"null"}],"description":"Your explicit override of the approval derived from the class.\nNull means derived.\n"},"effective_approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"What governs the tool on the autonomous path: `approval` when set,\nelse a webhook config's legacy `requires_approval`, else the class\ndefault (`read` and `communicate_internal` run; every other class,\nand an unclassified tool, requires approval). Read-only.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","kind","config","action_class","effective_action_class","approval","effective_approval","created_at","updated_at"],"description":"A workspace tool definition: a reusable webhook / client / MCP\nintegration created once and attached to many agents. Built-ins\nare NOT definitions - they are per-agent (see `AgentTool`).\n\n**A tool row belongs to exactly one project and is reused only inside\nit.** An attach whose agent and tool sit in different projects is\nrefused with `409 cross_project_reference`, and there is no\nworkspace-shared tier for a tool the way there is for a vault\ncredential: a tool with no `project_id` lives in the implicit Default\nproject and attaches only to agents that also live there. So an\napplication running one project per business customer duplicates its\nconnector tool per customer, and that is deliberate rather than a\nlimitation to route around. A tool row carries the endpoint, the\nheaders and the credential reference the agent will call with, and\nthose are exactly what an application wants to be able to vary, revoke\nor rotate for ONE of its customers without touching the others. Its\n`project_id` is also what makes a tool follow its agents through a\nmove, a promote and a project teardown; a shared row would have to be\nleft behind by all three.\n\nWhat does NOT need duplicating is the person: the customer's end-user\ntravels per call as `user_identity` in the signed webhook body or the\n`Speechify-User-Identity` header, so one connector row per project\nserves every person under that customer.\n","title":"Tool"},"ListToolsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"tools":{"type":"array","items":{"$ref":"#/components/schemas/Tool"}}},"required":["next_cursor","has_more","tools"],"description":"Payload for `GET /v1/agents/tool-definitions` \u2014 the workspace-level\ntool-definition catalog. Cursor-paginated; the per-agent toolbelt\nendpoint uses a different (bare, mixed-kind) shape \u2014 see\nListAgentToolsResponse.\n","title":"ListToolsResponse"},"CreateToolRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"title":"CreateToolRequestConfig"},"CreateToolRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"name":{"type":"string"},"description":{"type":"string"},"kind":{"$ref":"#/components/schemas/ToolKind"},"config":{"$ref":"#/components/schemas/CreateToolRequestConfig"},"action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact class. Omit it and the tool's impact is read off its\nshape instead, and keeps tracking it: a `GET` webhook reads, a\n`POST` webhook reaches outside your team, an MCP or client tool can\ndo anything its author wired.\n\nOn an MCP tool it is also the ceiling for what the server may claim\nabout its own tools: a class a `tools/list` entry declares under\n`_meta[\"speechify/action_class\"]` is honoured when it is at least\nthis strong and held to this class when weaker (see\n`MCPToolConfig.action_classes`).\n"},"approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"Overrides the approval derived from the class. Omitted means derived."}},"required":["name","description","kind","config"],"description":"Create a workspace tool definition (webhook / client / mcp only).","title":"CreateToolRequest"},"UpdateToolRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"title":"UpdateToolRequestConfig"},"UpdateToolRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"Move the resource between workspace projects: a `proj_...` id\nmoves it there, an explicit null moves it back to the implicit\nDefault project, omitted leaves it unchanged.\n"},"name":{"type":"string"},"description":{"type":"string"},"config":{"$ref":"#/components/schemas/UpdateToolRequestConfig"},"action_class":{"oneOf":[{"$ref":"#/components/schemas/ToolActionClass"},{"type":"null"}],"description":"A class sets it, an explicit null clears it to unclassified, omitted leaves it unchanged."},"approval":{"oneOf":[{"$ref":"#/components/schemas/ToolApprovalClass"},{"type":"null"}],"description":"A class sets the override, an explicit null returns to derived, omitted leaves it unchanged."}},"description":"All fields optional. `kind` is immutable \u2014 create a new definition to change it.","title":"UpdateToolRequest"},"ToolAttachedAgent":{"type":"object","properties":{"id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Opaque agent ID."},"name":{"type":"string","description":"Human-readable agent name."}},"required":["id","name"],"description":"Minimal agent identity returned alongside a tool so a client\ncan render \"this tool is attached to: X, Y\" before a\ndestructive action runs.\n","title":"ToolAttachedAgent"},"ListToolAttachedAgentsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"agents":{"type":"array","items":{"$ref":"#/components/schemas/ToolAttachedAgent"}}},"required":["next_cursor","has_more","agents"],"description":"Response shape for GET /v1/agents/tool-definitions/{tool_definition_id}/attached-agents.\nAgents are tenant-scoped and ordered by name ASC.\n","title":"ListToolAttachedAgentsResponse"},"TestMCPConnectionRequest":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MCPToolConfig"}},"required":["config"],"description":"Body for `POST /v1/agents/tool-definitions/test-mcp-connection`. `config` is the\nsame MCPToolConfig shape `POST /v1/agents/tool-definitions` would persist; nothing\nis persisted by the probe itself. For a non-none auth, the server\nresolves `auth.credential_id` to the vault secret before probing \u2014\nthe credential is created up front via `POST /v1/credentials`, so it\nresolves independently of any tool.\n","title":"TestMCPConnectionRequest"},"MCPProbeTool":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"read_only":{"type":"boolean","description":"The server's own account of this tool, read from the MCP\n`annotations.readOnlyHint` it published. Offered as a starting\npoint when you classify the server's tools in `action_classes`;\nit is never a policy input on its own, because a server declaring\nitself harmless is not the same as you deciding it is.\n"}},"required":["name"],"description":"One discovered tool in a probe result.","title":"MCPProbeTool"},"McpProbeErrorDetailsStage":{"type":"string","enum":["validation","oauth2_token","mcp_connect","mcp_initialize","mcp_notify","mcp_list_tools"],"title":"McpProbeErrorDetailsStage"},"MCPProbeErrorDetails":{"type":"object","properties":{"stage":{"$ref":"#/components/schemas/McpProbeErrorDetailsStage"},"http_status":{"type":"integer"},"oauth2_error":{"type":"string"},"oauth2_error_description":{"type":"string"},"upstream_body":{"type":"string"},"field_hint":{"type":"string"}},"description":"Structured upstream signal for an MCP probe failure. All fields\nare optional; a client renders what's present. `stage` names\nthe phase the probe was in (`validation`, `oauth2_token`,\n`mcp_connect`, `mcp_initialize`, `mcp_notify`, `mcp_list_tools`).\n`oauth2_error` / `oauth2_error_description` mirror RFC 6749 \u00a75.2\nwhen the customer's auth server returned the standard error\nshape. `http_status` is the upstream status code for transport\nfailures. `upstream_body` is a truncated prefix (max ~1 KiB) of\nthe upstream response body when the failure isn't structured.\n`field_hint` names a form field (`endpoint`, `transport`,\n`token`, `token_url`, `client_id`, `client_secret`, `scope`)\na client should highlight so the customer knows what to fix.\n","title":"MCPProbeErrorDetails"},"MCPProbeResult":{"type":"object","properties":{"tools":{"type":["array","null"],"items":{"$ref":"#/components/schemas/MCPProbeTool"}},"error":{"type":"string"},"details":{"$ref":"#/components/schemas/MCPProbeErrorDetails"}},"required":["tools"],"description":"Result of an MCP probe. On success, `tools` is the discovered\ncatalogue and `error` is absent. On failure, `tools` is `null`\nand `error` carries a human-readable reason a client can render\ninline next to the form. `details` is optional structured\nsignal from the upstream (OAuth2 RFC 6749 fields, HTTP status,\ntruncated upstream body, form field hint) a client can use to\nexpand the inline banner and highlight the offending input.\nOlder clients ignore `details` and fall back to `error`. Both\nvalidation and network failures land in `error` rather than\nnon-2xx responses, so consumers must check `error` before\nreading `tools`.\n","title":"MCPProbeResult"},"TestWebhookConnectionRequest":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/WebhookToolConfig"},"tool_id":{"type":"string","description":"Optional `tool_` id of the existing tool to sign\nthe probe with. Raw UUIDs and other-resource prefixes are\nrejected.\n"}},"required":["config"],"description":"Body for `POST /v1/agents/tool-definitions/test-webhook-connection`.\n`config` is the same WebhookToolConfig shape `POST /v1/agents/tool-definitions`\nwould persist; nothing is persisted by the probe. `tool_id` is\nonly meaningful in the edit-form flow \u2014 when set, the server\nsigns the probe request with the tool's stored HMAC secret so\nthe test exercises the real signature path.\n","title":"TestWebhookConnectionRequest"},"WebhookProbeResult":{"type":"object","properties":{"ok":{"type":"boolean"},"status_code":{"type":"integer","description":"HTTP status the endpoint returned. Absent on a transport failure."},"latency_ms":{"type":"integer","format":"int64","description":"Wall-clock round-trip time in milliseconds."},"response_body":{"type":"string","description":"Truncated prefix (max ~2 KiB) of the endpoint's response body."},"signed":{"type":"boolean","description":"Whether the probe request carried an HMAC signature header."},"error":{"type":"string","description":"Human-readable transport-level failure reason. Absent when any response was received."}},"required":["ok","signed"],"description":"Result of a webhook probe. `ok` is true only when the endpoint\nreturned a 2xx. A non-2xx response still populates `status_code`\nand `response_body` with `ok=false` \u2014 the request reached the\nendpoint, the endpoint just declined it. `error` is set only for\ntransport-level failures (DNS, connect, TLS, timeout, blocked\naddress range) where no response was received; `status_code` is\nabsent in that case. `signed` reports whether the probe carried\na `Speechify-Signature` header \u2014 false on the create-form\nflow, which has no stored secret yet. Both success and failure\nuse the 200 envelope so a client can render them inline.\n","title":"WebhookProbeResult"},"SystemBuiltinInfo":{"type":"object","properties":{"name":{"$ref":"#/components/schemas/SystemBuiltin"},"label":{"type":"string","description":"Human-readable display label for the builtin."},"description":{"type":"string","description":"One-line summary of what the builtin does."},"action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact class the platform fixes for this builtin."}},"required":["name","label","description","action_class"],"description":"One entry in the system-builtin catalogue.","title":"SystemBuiltinInfo"},"ListSystemBuiltinsResponse":{"type":"object","properties":{"builtins":{"type":"array","items":{"$ref":"#/components/schemas/SystemBuiltinInfo"}}},"required":["builtins"],"title":"ListSystemBuiltinsResponse"},"TestType":{"type":"string","enum":["reply","tool","simulation"],"description":"Discriminates the shape of `AgentTest.config`.\n- `reply` - send one message to the agent and judge the response with an LLM.\n- `tool` - assert that the agent calls a specific tool given a context.\n- `simulation` - run a multi-turn conversation between the agent and an AI caller.\n","title":"TestType"},"SimulationMessageRole":{"type":"string","enum":["user","assistant"],"title":"SimulationMessageRole"},"SimulationMessage":{"type":"object","properties":{"role":{"$ref":"#/components/schemas/SimulationMessageRole"},"content":{"type":"string"}},"required":["role","content"],"description":"One turn in a simulation conversation. `role` is `user` (the AI caller) or `assistant` (the agent).","title":"SimulationMessage"},"ReplyConfig":{"type":"object","properties":{"context":{"type":"string","description":"User message sent to the agent to trigger the behaviour under test. Optional when `initial_chat_history` already ends with a user message."},"success_criteria":{"type":"string","description":"Natural-language description of what a passing agent response looks like."},"success_examples":{"type":"array","items":{"type":"string"},"description":"Concrete examples of passing responses (few-shot for the judge)."},"failure_examples":{"type":"array","items":{"type":"string"},"description":"Concrete examples of failing responses (few-shot for the judge)."},"initial_chat_history":{"type":"array","items":{"$ref":"#/components/schemas/SimulationMessage"},"description":"Optional seed conversation prepended before `context`. Lets you test the agent's reply mid-conversation rather than on a cold single-turn prompt."},"system_prompt_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`, which applies a proposed\nprompt to every test in the run without editing each one.\nStill honoured; the run-level override wins when both are set.\nReplaces the agent's system prompt for this run only.","deprecated":true},"first_message_override":{"type":"string","description":"Replaces the agent's first message for this run only."},"model_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Overrides the LLM\nmodel used by the agent for this run only. Must be a model the\nworkspace's plan includes; an over-tier or unknown model fails\nthe run.","deprecated":true}},"required":["success_criteria"],"description":"Configuration for a `reply` test. The runner sends `context` as\na user message and asks an LLM judge to evaluate the agent response\nagainst `success_criteria`. Optional few-shot examples sharpen the\njudge's calibration. Use `initial_chat_history` to prepend prior\nturns before `context`; when the history already ends with a user\nmessage, `context` may be omitted and the agent is evaluated on\nits reply to that last history turn.","title":"ReplyConfig"},"ParameterCheckMode":{"type":"string","enum":["exact","regex","llm"],"description":"How a `ParameterCheck` validates a tool argument.\n- `exact` - JSON equality.\n- `regex` - the argument stringified is matched against the pattern.\n- `llm` - an LLM judge decides whether the value semantically satisfies\n the criteria (e.g. \"is a plausible email address\").\n","title":"ParameterCheckMode"},"ParameterCheck":{"type":"object","properties":{"path":{"type":"string","description":"Dotted JSON path to the argument being checked. Empty means the whole args object."},"mode":{"$ref":"#/components/schemas/ParameterCheckMode"},"expected":{"type":"string","description":"Expected value string for `exact` and `regex` modes."},"criteria":{"type":"string","description":"Natural-language criteria for `llm` mode (e.g. \"is a valid email address\")."}},"required":["path","mode"],"description":"Validates one argument of an expected tool call. `path` is a\ndotted JSON path (e.g. `customer.email`); use zero-indexed\nnotation for arrays (`items.0.sku`). An empty path checks the\nwhole args object.","title":"ParameterCheck"},"ToolCallConfig":{"type":"object","properties":{"context":{"type":"string","description":"User message that should cause the agent to invoke the expected tool. Optional when `initial_chat_history` already ends with a user message."},"expected_tool":{"type":"string","description":"Name of the tool the agent is expected to call. Leave empty to\ninvert the assertion: the test passes only when the agent calls\nno tool at all."},"parameter_checks":{"type":"array","items":{"$ref":"#/components/schemas/ParameterCheck"},"description":"Assertions on specific arguments of the tool call."},"initial_chat_history":{"type":"array","items":{"$ref":"#/components/schemas/SimulationMessage"},"description":"Optional seed conversation prepended before `context`."},"system_prompt_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Replaces the\nagent's system prompt for this run only.","deprecated":true},"model_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Overrides the LLM\nmodel used by the agent for this run only. Must be a model the\nworkspace's plan includes; an over-tier or unknown model fails\nthe run.","deprecated":true}},"required":["expected_tool"],"description":"Configuration for a `tool` test. The runner sends `context` as a\nuser message and asserts that the agent calls `expected_tool` with\narguments matching all `parameter_checks`. Use\n`initial_chat_history` to test tool invocations that only make\nsense mid-conversation.","title":"ToolCallConfig"},"DataAssertionMode":{"type":"string","enum":["exact","regex","llm"],"description":"How the assertion validates the extracted value.","title":"DataAssertionMode"},"DataAssertion":{"type":"object","properties":{"key":{"type":"string","description":"Name of the data-collection field on the agent's evaluation config. The assertion fails when this key is missing from the extracted data."},"mode":{"$ref":"#/components/schemas/DataAssertionMode","description":"How the assertion validates the extracted value."},"expected":{"type":"string","description":"Expected value string for `exact` and `regex` modes."},"criteria":{"type":"string","description":"Natural-language criteria for `llm` mode."}},"required":["key","mode"],"description":"Asserts on one entry in the LLM-extracted data-collection map\nproduced by the unified evaluator. `key` matches a\ndata-collection field configured on the agent; the assertion\nruns against the value the judge wrote under that key. Same\nexact / regex / llm modes as `ParameterCheck` so the tool-call\nand data-collection assertion surfaces are uniform.","title":"DataAssertion"},"SimulationConfig":{"type":"object","properties":{"scenario":{"type":"string","description":"Instructions for the AI caller describing who they are and what they want."},"max_turns":{"type":"integer","minimum":1,"maximum":50,"default":5,"description":"Maximum agent turns before the simulation is cut off and judged."},"initial_chat_history":{"type":"array","items":{"$ref":"#/components/schemas/SimulationMessage"},"description":"Optional seed conversation that precedes the AI caller's first generated message."},"data_assertions":{"type":"array","items":{"$ref":"#/components/schemas/DataAssertion"},"description":"Optional assertions on the LLM-extracted data-collection\nmap. Each entry references a key from the agent's\ndata_collection config and validates the extracted value.\nThe test fails if any assertion fails."},"system_prompt_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Replaces the\nagent's system prompt for this run only.","deprecated":true},"model_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Overrides the LLM\nmodel used by the agent for this run only. Must be a model the\nworkspace's plan includes; an over-tier or unknown model fails\nthe run.","deprecated":true}},"required":["scenario"],"description":"Configuration for a `simulation` test. An AI caller drives a\nmulti-turn conversation with the agent according to `scenario`.\nAfter `max_turns` exchanges (or when the agent ends the call),\nthe unified post-call evaluator scores the synthetic transcript\nagainst the agent's configured evaluation criteria + data\ncollection fields. A test passes when no configured criterion\nfails and every `data_assertions` entry passes.","title":"SimulationConfig"},"CreateAgentTestRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/ReplyConfig"},{"$ref":"#/components/schemas/ToolCallConfig"},{"$ref":"#/components/schemas/SimulationConfig"}],"description":"Type-specific configuration. Must match the shape for the given `type`.","title":"CreateAgentTestRequestConfig"},"MockingStrategy":{"type":"string","enum":["none","all","selected"],"description":"Controls which tool calls the runner intercepts during a run.\nSystem tools (`end_call`, `transfer_to_number`, etc.) are never\nmocked regardless of strategy.\n- `none` - no interception; all tools are called normally.\n- `all` - every non-system tool call is intercepted and matched\n against the `mocks` list.\n- `selected` - only tools explicitly listed in `mocks` are\n intercepted; others are called normally.\n","title":"MockingStrategy"},"ToolMock":{"type":"object","properties":{"tool_name":{"type":"string","description":"Name of the tool to intercept."},"args_match":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional structured argument matcher. When set, the mock fires\nonly if the tool call's arguments deep-contain every key/value\nin this object: nested objects match recursively as subsets,\narrays and scalar leaves match by deep equality. An empty\nobject matches unconditionally. When absent the mock matches\nunconditionally for this tool."},"response":{"description":"JSON value returned to the agent as the tool result."}},"required":["tool_name","response"],"description":"A canned response returned when the agent calls `tool_name`. If\n`args_match` is set the mock only triggers when the call arguments\ndeep-contain it (a structured subset match). A mock without\n`args_match` always matches for its tool.","title":"ToolMock"},"NoMatchBehavior":{"type":"string","enum":["call_real_tool","finish_with_error","skip"],"description":"Fallback when a mockable tool is called but no configured mock\nmatches the call arguments.\n- `call_real_tool` - pass-through: actually invoke the underlying\n tool (a webhook tool POSTs to the customer endpoint). Use only\n when the real call is safe to make from a test.\n- `finish_with_error` - fail: the run finishes as a `failed`\n verdict. Useful when a test wants to assert that a specific\n mocked response path is taken - any unmocked tool call fails the\n test.\n- `skip` - return an empty stub (`{\"skipped\":true}`) to the agent so\n the simulation proceeds without treating the call as a failure.\n Useful when a tool's output is irrelevant to the behaviour under\n test but the model may still decide to call it. This is the\n default for a test with no mock configuration.\n","title":"NoMatchBehavior"},"ToolMockConfig":{"type":"object","properties":{"strategy":{"$ref":"#/components/schemas/MockingStrategy"},"mocks":{"type":"array","items":{"$ref":"#/components/schemas/ToolMock"},"description":"Canned responses for specific tools (order matters - first match wins)."},"no_match_behavior":{"$ref":"#/components/schemas/NoMatchBehavior"}},"required":["strategy","no_match_behavior"],"description":"Controls tool-call interception during a test run.","title":"ToolMockConfig"},"CreateAgentTestRequest":{"type":"object","properties":{"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Optional authoring agent (`agent_<26 char Crockford base32>`).\nWhen supplied it must be an agent in the caller's workspace; it\nseeds the test's tool schemas and variables and is recorded as\nauthored-from provenance. Omit it to create a bare workspace\ntest with no authoring agent. It is never a scope, and the run\ntarget is bound at run time independently of it.\n"},"name":{"type":"string","maxLength":200,"description":"Short human-readable label for the test."},"description":{"type":"string","maxLength":2000,"description":"Optional longer description of what this test verifies."},"type":{"$ref":"#/components/schemas/TestType"},"config":{"$ref":"#/components/schemas/CreateAgentTestRequestConfig","description":"Type-specific configuration. Must match the shape for the given `type`."},"tool_mock_config":{"$ref":"#/components/schemas/ToolMockConfig","description":"Optional tool-mocking config applied during every run of this test."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-test variable values substituted into string fields of the\nconfig at run-start. Keys use the same rules as agent-level\n`DynamicVariable` keys.\n"},"folder_id":{"type":["string","null"],"description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`)\nof the folder to place the test in. Omit / null for root.\n"}},"required":["name","type","config"],"description":"Payload for `POST /v1/agents/tests`.","title":"CreateAgentTestRequest"},"AgentTestConfig":{"oneOf":[{"$ref":"#/components/schemas/ReplyConfig"},{"$ref":"#/components/schemas/ToolCallConfig"},{"$ref":"#/components/schemas/SimulationConfig"}],"description":"Type-specific configuration document.","title":"AgentTestConfig"},"AgentTest":{"type":"object","properties":{"id":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent this test was authored against, or `null` for a\nbare workspace test with no authoring agent. This is\nauthored-from provenance - it records which agent's tool schemas\nand variables the test was built from, plus an audit of where\nthe test came from - not an ownership or access handle: every\nread, list, and run is scoped by workspace, and the run target\nis bound at run time independently of this field. It also stays\nput (a dangling provenance pointer) if the authoring agent is\nlater deleted.\n"},"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"type":{"$ref":"#/components/schemas/TestType"},"config":{"$ref":"#/components/schemas/AgentTestConfig","description":"Type-specific configuration document."},"tool_mock_config":{"$ref":"#/components/schemas/ToolMockConfig","description":"Optional tool-mocking config applied during runs of this test."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-test dynamic-variable overrides. Keys substitute `{{key}}`\nplaceholders inside the test config at run-start. Unknown keys\nrender as empty string, matching session dispatch behaviour.\n"},"folder_id":{"type":["string","null"],"pattern":"^folder_[0-9a-hjkmnp-tv-z]{26}$","description":"When set, prefixed wire identifier\n(`folder_<26 char Crockford base32>`) of the containing folder.\nNull means root (unfiled).\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","agent_id","name","description","type","config","created_at","updated_at"],"description":"A configured test against a voice agent. `config` is a\ntype-specific document - see `ReplyConfig`, `ToolCallConfig`,\nand `SimulationConfig` for the per-type shapes (discriminated by `type`).","title":"AgentTest"},"TestVerdict":{"type":"string","enum":["passed","failed"],"description":"The domain pass/fail judgment of an agent-test run or suite run,\nseparate from the lifecycle `status`. Present only once the run\nreached a verdict (`status` is `completed`); absent for a run that\ncould not execute (`status` `failed`), was `cancelled`, or is still\nin flight.\n","title":"TestVerdict"},"ReplyResult":{"type":"object","properties":{"agent_response":{"type":"string","description":"The raw text response the agent produced."},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"LLM judge's explanation of the verdict."},"score":{"type":"string","title":"float","description":"0-1 judge confidence score."},"duration_ms":{"type":"integer","format":"int64","description":"Wall-clock time for the run in milliseconds."}},"required":["agent_response","passed","rationale","score","duration_ms"],"description":"Result details for a `reply` test run.","title":"ReplyResult"},"ParameterCheckResult":{"type":"object","properties":{"path":{"type":"string"},"mode":{"$ref":"#/components/schemas/ParameterCheckMode"},"actual_json":{"type":"string","description":"JSON-serialised actual value at `path`."},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"LLM rationale (populated for `llm` mode checks)."}},"required":["path","mode","actual_json","passed"],"description":"Result of one `ParameterCheck` within a tool-call test run.","title":"ParameterCheckResult"},"ToolCallResult":{"type":"object","properties":{"tool_called":{"type":"string","description":"Name of the tool the agent actually called (may differ from `expected_tool`)."},"tool_args":{"description":"Arguments the agent passed to the tool, as a JSON object."},"expected_tool":{"type":"string","description":"Name of the tool the test expected the agent to call."},"tool_matched":{"type":"boolean","description":"True when `tool_called` equals `expected_tool`."},"parameter_results":{"type":"array","items":{"$ref":"#/components/schemas/ParameterCheckResult"}},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"Explanation of the overall verdict."},"duration_ms":{"type":"integer","format":"int64"}},"required":["tool_called","expected_tool","tool_matched","parameter_results","passed","rationale","duration_ms"],"description":"Result details for a `tool` test run.","title":"ToolCallResult"},"SimulationToolCall":{"type":"object","properties":{"turn_index":{"type":"integer","description":"Zero-based index of the conversation turn in which this call occurred."},"tool_name":{"type":"string"},"args":{"description":"Arguments passed to the tool, as a JSON object."},"response":{"description":"Response returned to the agent (absent for system tools that end the call)."},"mocked":{"type":"boolean"}},"required":["turn_index","tool_name","args","mocked"],"description":"One tool invocation that occurred during a simulation run.\n`mocked` is true when the call was intercepted by the run's\nmock config; false when the real tool was called or when the\ntool is a system tool.","title":"SimulationToolCall"},"SimulationResultSentiment":{"type":"string","enum":["positive","neutral","negative"],"description":"Overall sentiment classification.","title":"SimulationResultSentiment"},"SimulationCriterionResultStatus":{"type":"string","enum":["success","failure","unknown"],"description":"Three-state outcome. `unknown` means the criterion did not\napply on this run (the topic never came up); `failure`\nmeans it did apply and the agent did not satisfy it.","title":"SimulationCriterionResultStatus"},"SimulationCriterionResult":{"type":"object","properties":{"criterion_id":{"type":"string"},"name":{"type":"string"},"status":{"$ref":"#/components/schemas/SimulationCriterionResultStatus","description":"Three-state outcome. `unknown` means the criterion did not\napply on this run (the topic never came up); `failure`\nmeans it did apply and the agent did not satisfy it."},"score":{"type":"string","title":"float","description":"0.0..1.0 continuous estimate of how well the criterion was met."},"rationale":{"type":"string"}},"required":["criterion_id","name","status","rationale"],"description":"One scored entry of an agent's configured evaluation criterion\nagainst a simulation transcript. Mirrors the per-criterion row\nthe post-call evaluator persists, so test runs and live\nconversations carry identical per-criterion shapes.","title":"SimulationCriterionResult"},"DataAssertionResultMode":{"type":"string","enum":["exact","regex","llm"],"title":"DataAssertionResultMode"},"DataAssertionResult":{"type":"object","properties":{"key":{"type":"string"},"mode":{"$ref":"#/components/schemas/DataAssertionResultMode"},"actual_json":{"type":"string","description":"The extracted value rendered as JSON (`null` when the key was missing from the data map)."},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"Empty on pass; reason for failure otherwise."}},"required":["key","mode","actual_json","passed"],"description":"Outcome of one `data_assertions` entry: did the value the\nevaluator extracted under `key` pass the configured exact /\nregex / llm check.","title":"DataAssertionResult"},"SimulationResult":{"type":"object","properties":{"transcript":{"type":"array","items":{"$ref":"#/components/schemas/SimulationMessage"},"description":"Full synthetic conversation in order."},"tool_calls":{"type":"array","items":{"$ref":"#/components/schemas/SimulationToolCall"},"description":"Every tool invocation across all turns."},"turns_used":{"type":"integer","description":"Number of agent turns that ran before the simulation ended."},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"Top-level verdict explanation (run summary on pass; first failing criterion or assertion on fail)."},"duration_ms":{"type":"integer","format":"int64"},"summary":{"type":"string","description":"One-sentence narrative summary of what happened in the conversation."},"sentiment":{"$ref":"#/components/schemas/SimulationResultSentiment","description":"Overall sentiment classification."},"criteria":{"type":"array","items":{"$ref":"#/components/schemas/SimulationCriterionResult"},"description":"One result row per configured EvaluationCriterion on the\nagent. Same shape as the per-criterion rows persisted on\nthe post-call evaluations table."},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"LLM-extracted values for the agent's configured\ndata-collection fields. Keys mirror the agent's\ndata_collection field keys; values are typed per the\ndeclared field type."},"data_assertions":{"type":"array","items":{"$ref":"#/components/schemas/DataAssertionResult"},"description":"One result row per `data_assertions` entry on the simulation config."}},"required":["transcript","turns_used","passed","rationale","duration_ms"],"description":"Result details for a `simulation` test run. Scoring is unified\nwith the post-call evaluator: the synthetic transcript\nis scored against the agent's configured evaluation criteria\nand data-collection fields, then per-test `data_assertions`\ncheck the extracted values. The top-level `passed` is derived\n\u2014 every criterion must resolve to `success` or `unknown` and\nevery assertion must pass.","title":"SimulationResult"},"TestRunResult":{"type":"object","properties":{"test_type":{"$ref":"#/components/schemas/TestType"},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"Top-level verdict explanation duplicated from the inner result for quick rendering."},"duration_ms":{"type":"integer","format":"int64"},"reply":{"oneOf":[{"$ref":"#/components/schemas/ReplyResult"},{"type":"null"}]},"tool_call":{"oneOf":[{"$ref":"#/components/schemas/ToolCallResult"},{"type":"null"}]},"simulation":{"oneOf":[{"$ref":"#/components/schemas/SimulationResult"},{"type":"null"}]}},"required":["test_type","passed","rationale","duration_ms"],"description":"Union-like result of a completed test run. Exactly one of\n`reply`, `tool_call`, or `simulation` is populated, matching\nthe `test_type`.","title":"TestRunResult"},"AgentTestRun":{"type":"object","properties":{"id":{"type":"string","pattern":"^run_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`run_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"test_id":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`)\nof the parent test.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent this run executed against.\n"},"status":{"$ref":"#/components/schemas/JobStatus"},"verdict":{"oneOf":[{"$ref":"#/components/schemas/TestVerdict"},{"type":"null"}],"description":"The pass/fail judgment, present only once the run reached a\nverdict (`status` is `completed`).\n"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"},"result":{"oneOf":[{"$ref":"#/components/schemas/TestRunResult"},{"type":"null"}],"description":"Populated once the run reached a verdict."},"error":{"type":"string","description":"Human-readable error message when `status` is `failed`."},"created_at":{"type":"string","format":"date-time"}},"required":["id","test_id","agent_id","status","created_at"],"description":"One execution of a test. The lifecycle `status` is the unified\nasync-job vocabulary; the pass/fail judgment lives in the separate\n`verdict` field. `result` is populated when the run reached a\nverdict (`status` is `completed`); see `TestRunResult` for the\nshape. A run that could not execute is `status` `failed` with no\n`verdict`, and `error` carries the reason.","title":"AgentTestRun"},"AgentTestWithLastRun":{"type":"object","properties":{"id":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent this test was authored against, or `null` for a\nbare workspace test with no authoring agent. This is\nauthored-from provenance - it records which agent's tool schemas\nand variables the test was built from, plus an audit of where\nthe test came from - not an ownership or access handle: every\nread, list, and run is scoped by workspace, and the run target\nis bound at run time independently of this field. It also stays\nput (a dangling provenance pointer) if the authoring agent is\nlater deleted.\n"},"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"type":{"$ref":"#/components/schemas/TestType"},"config":{"$ref":"#/components/schemas/AgentTestConfig","description":"Type-specific configuration document."},"tool_mock_config":{"$ref":"#/components/schemas/ToolMockConfig","description":"Optional tool-mocking config applied during runs of this test."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-test dynamic-variable overrides. Keys substitute `{{key}}`\nplaceholders inside the test config at run-start. Unknown keys\nrender as empty string, matching session dispatch behaviour.\n"},"folder_id":{"type":["string","null"],"pattern":"^folder_[0-9a-hjkmnp-tv-z]{26}$","description":"When set, prefixed wire identifier\n(`folder_<26 char Crockford base32>`) of the containing folder.\nNull means root (unfiled).\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"last_run":{"oneOf":[{"$ref":"#/components/schemas/AgentTestRun"},{"type":"null"}],"description":"The most recent run, or null if the test has never been run."}},"required":["id","agent_id","name","description","type","config","created_at","updated_at"],"description":"List-view projection of a test that includes the most recent run\nso a client can display pass/fail badges without an extra\nround-trip.","title":"AgentTestWithLastRun"},"ListTestsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"tests":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestWithLastRun"}}},"required":["next_cursor","has_more","tests"],"description":"Workspace-wide paginated list of tests. Walk pages while\n`has_more` is true; pass `next_cursor` back as the request\n`cursor` parameter.","title":"ListTestsResponse"},"UpdateAgentTestRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/ReplyConfig"},{"$ref":"#/components/schemas/ToolCallConfig"},{"$ref":"#/components/schemas/SimulationConfig"}],"description":"Replaces the test config when present.","title":"UpdateAgentTestRequestConfig"},"UpdateAgentTestRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"config":{"$ref":"#/components/schemas/UpdateAgentTestRequestConfig","description":"Replaces the test config when present."},"tool_mock_config":{"$ref":"#/components/schemas/ToolMockConfig","description":"Replaces the tool-mock config when present."},"folder_id":{"type":["string","null"],"description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`)\nof the folder to move the test into, or `null` to move the\ntest back to root. Omit to leave unchanged.\n"}},"description":"Payload for `PATCH /v1/agents/tests/{test_id}` (JSON merge-patch).\nAll fields are optional; omitting a field leaves it unchanged. Set\n`folder_id` to a target folder id to move the test into that\nfolder, or send `folder_id: null` to move the test back to root.","title":"UpdateAgentTestRequest"},"AgentTestFolder":{"type":"object","properties":{"id":{"type":"string","pattern":"^folder_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"parent_folder_id":{"type":["string","null"],"pattern":"^folder_[0-9a-hjkmnp-tv-z]{26}$","description":"When set, prefixed wire identifier\n(`folder_<26 char Crockford base32>`) of the parent folder.\nNull means root.\n"},"name":{"type":"string"},"test_count":{"type":"integer","description":"Number of tests in this folder the caller's current project\nscope admits: the credential's project pin and the member's\nproject grants, narrowed further by the `project_id` filter\nwhen given. It is the row count\n`GET /v1/agents/tests?folder_id=` returns under the same\nscope.\n"},"total_test_count":{"type":"integer","description":"Number of tests in this folder the caller could see with no\n`project_id` filter: every project for an unscoped caller,\nonly the granted projects for a scoped member. It never counts\na test the caller cannot list, so under a `project_id` filter\nthe difference to `test_count` is how many of the folder's\nvisible tests that filter hides, never how many sit outside\nthe caller's grants.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","test_count","total_test_count","created_at","updated_at"],"description":"One organisational node in the per-owner tests tree. A folder is\nworkspace-wide: it groups tests across agents and so across\nprojects, and no project scope ever hides a folder. Every folder\nresponse carries `test_count` and `total_test_count`; neither ever\ncounts a test the caller could not list. A single-folder response\ntakes no `project_id` filter, so there the two are equal.\n","title":"AgentTestFolder"},"ListAgentTestFoldersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"folders":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestFolder"}}},"required":["next_cursor","has_more","folders"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentTestFoldersResponse"},"CreateAgentTestFolderRequest":{"type":"object","properties":{"name":{"type":"string"},"parent_folder_id":{"type":["string","null"],"description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`)\nof the parent folder. Omit / null for a root-level folder.\n"}},"required":["name"],"title":"CreateAgentTestFolderRequest"},"UpdateAgentTestFolderRequest":{"type":"object","properties":{"name":{"type":"string"},"parent_folder_id":{"type":["string","null"],"description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`)\nof the folder to reparent this folder under, or `null` to\nreparent it to root. Omit to leave unchanged.\n"}},"description":"PATCH body (JSON merge-patch). All fields optional; omit a field\nto leave it unchanged. Set `parent_folder_id` to a target folder\nid to reparent into that folder, or send `parent_folder_id: null`\nto reparent this folder to root.","title":"UpdateAgentTestFolderRequest"},"ListAgentTestRunsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestRun"}}},"required":["next_cursor","has_more","runs"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentTestRunsResponse"},"RunTarget":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent to run every test against: the run target, bound\nat run time, independent of any test's authored-from agent.\n"},"version":{"type":"string","description":"Reserved for version-pinned targeting once agent versioning\nships. Not supported yet; supplying it returns 400."},"tag":{"type":"string","description":"Reserved for tag targeting once agent versioning ships. Not\nsupported yet; supplying it returns 400."}},"required":["agent_id"],"description":"The agent a targeted run binds to at run time. `agent_id` is\nrequired. `version` and `tag` pin a specific agent version once\nagent versioning ships; supplying either today returns 400, so\nagent-id targeting works now and version/tag light up later without\na wire change.","title":"RunTarget"},"TestRunConfigOverride":{"type":"object","properties":{"prompt":{"type":"string","description":"Replaces the agent's system prompt for every test in the run."},"model":{"type":"string","description":"Overrides the LLM model for every test in the run. The model\nid rides on the agent's configured provider \u2014 a\ncross-provider switch is not supported."},"tool_ids":{"type":"array","items":{"type":"string"},"description":"Replaces the agent's attached external tools for the run with\nexactly this set. Each entry is a prefixed `tool_`\nid; `builtin_` ids are rejected. An empty array runs with no\ntools; omit the field to keep the agent's attachments."}},"description":"A run-level config override applied to every test in a Run All.\nLayered on top of the agent's stored config for the duration of\nthe suite run, so the whole suite can be validated against a\nproposed prompt / model / toolbelt without editing any test. An\nabsent field leaves the agent's value untouched; a run-level\noverride wins over a deprecated per-test `system_prompt_override`\n/ `model_override`.","title":"TestRunConfigOverride"},"RunTargetedTestsRequest":{"type":"object","properties":{"test_ids":{"type":"array","items":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Prefixed `test_` ids to run. De-duplicated, so a\nrepeated id runs once.\n"},"target":{"$ref":"#/components/schemas/RunTarget"},"config_override":{"$ref":"#/components/schemas/TestRunConfigOverride"}},"required":["test_ids","target"],"description":"Run a chosen set of tests against a target agent, bound at run\ntime. The tests are not attached to the agent, so the same set can\nrun against another agent in a second call (A/B), and against a\npinned agent version once versioning ships (version-pinned\nregression). Total runs are capped at 100 per call.","title":"RunTargetedTestsRequest"},"SuiteRunTrigger":{"type":"string","enum":["run_all","batch","resubmit","target"],"description":"Which entry point created a suite run.\n- `run_all` - POST /v1/agents/{agent_id}/tests/runs.\n- `batch` - POST /v1/agents/tests/runs/batch.\n- `resubmit` - POST /v1/agents/tests/suite-runs/{suite_run_id}/resubmit.\n- `target` - POST /v1/agents/tests/runs (a chosen set of tests\n bound to a target agent at run time).\n","title":"SuiteRunTrigger"},"AgentTestSuiteRunResults":{"type":"object","properties":{"passed":{"type":"integer"},"failed":{"type":"integer"}},"required":["passed","failed"],"description":"Per-verdict breakdown among the `completed` child runs.","title":"AgentTestSuiteRunResults"},"AgentTestSuiteRun":{"type":"object","properties":{"id":{"type":"string","pattern":"^srun_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`srun_<26 char Crockford base32>`)."},"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed `agent_` id of the agent whose suite\nwas run. Set for the `run_all` trigger; null for `batch`,\nwhich can span many agents.\n"},"agent_name":{"type":["string","null"],"description":"Display name of `agent_id`'s agent, resolved at read time.\nNull whenever `agent_id` is null, and on the suite run\nembedded in run/resubmit creation responses.\n"},"trigger":{"$ref":"#/components/schemas/SuiteRunTrigger"},"parent_suite_run_id":{"type":["string","null"],"description":"Set on a `resubmit`: the prefixed `srun_` id of\nthe suite run whose failed/errored tests this one re-ran.\nNull for `run_all` and `batch`.\n"},"status":{"$ref":"#/components/schemas/JobStatus"},"verdict":{"oneOf":[{"$ref":"#/components/schemas/TestVerdict"},{"type":"null"}],"description":"The suite's pass/fail judgment, present only when `status` is\n`completed`.\n"},"total":{"type":"integer","description":"Number of child runs in the suite."},"completed":{"type":"integer","description":"Child runs that produced a verdict."},"failed":{"type":"integer","description":"Child runs that could not execute (an infrastructure failure)."},"running":{"type":"integer","description":"Child runs actively executing."},"pending":{"type":"integer","description":"Child runs queued, not yet executing."},"cancelled":{"type":"integer","description":"Child runs cancelled."},"results":{"$ref":"#/components/schemas/AgentTestSuiteRunResults","description":"Per-verdict breakdown among the `completed` child runs."},"created_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time","description":"Newest child-run completion; null until every child run is terminal."},"config_override":{"oneOf":[{"$ref":"#/components/schemas/TestRunConfigOverride"},{"type":"null"}],"description":"The run-level config override this suite was run\nwith, or null for an ordinary Run All / batch."},"flow_version_id":{"type":["string","null"],"pattern":"^fver_[0-9a-hjkmnp-tv-z]{26}$","description":"The flow version (`agent_versions` row) this suite targeted\n(prefixed external id, `fver_...`), or null for the agent's\nactive / synthesized flow."},"flow_version_number":{"type":["integer","null"],"description":"Human-facing version number of `flow_version_id`; null when no version was targeted."}},"required":["id","trigger","status","total","completed","failed","running","pending","cancelled","results","created_at"],"description":"A suite run (test invocation): the grouping object over every\ntest run dispatched by one Run All, batch, or resubmit call.\n`status`, `verdict`, and the count fields are derived from the\nchild runs. `status` is the unified async-job lifecycle: `running`\nwhile any child is pending/running, then `cancelled` (any child\ncancelled), `failed` (any child could not execute), or `completed`\n(every child produced a verdict). When `completed`, `verdict` is\n`passed` if every child passed, else `failed`. `results` carries\nthe per-verdict breakdown.","title":"AgentTestSuiteRun"},"RunAgentTestsResponse":{"type":"object","properties":{"runs":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestRun"}},"suite_run":{"oneOf":[{"$ref":"#/components/schemas/AgentTestSuiteRun"},{"type":"null"}],"description":"The suite run grouping the queued runs."}},"required":["runs"],"description":"Response from `POST /v1/agents/{agent_id}/tests/runs` and the suite-run\nresubmit endpoint. Contains every newly-queued run so the client\ncan poll each for completion, plus the `suite_run` that groups\nthem. `suite_run` is null only when a Run All found no tests.","title":"RunAgentTestsResponse"},"BatchRunEntry":{"type":"object","properties":{"test_id":{"type":"string","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`)\nof the test to run.\n"},"agent_id":{"type":"string","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent to run the test against (any agent in the\nworkspace; the run target is bound here at run time). Omit to\nrun against the agent the test was authored against.\n"}},"required":["test_id"],"description":"One entry in a batch-run request. Omit `agent_id` to run the test\nagainst the agent it was authored against; a test with no authoring\nagent requires an explicit `agent_id`.","title":"BatchRunEntry"},"RunBatchRequest":{"type":"object","properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/BatchRunEntry"}}},"required":["entries"],"description":"Batch-run payload. Total expanded runs across all entries are\ncapped at 100 per call.","title":"RunBatchRequest"},"ListSuiteRunsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"suite_runs":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestSuiteRun"}}},"required":["next_cursor","has_more","suite_runs"],"description":"One page of suite runs, newest first. Walk pages while\n`has_more` is true; pass `next_cursor` back as the request\n`cursor` parameter.","title":"ListSuiteRunsResponse"},"AgentTestSuiteRunWithRunsResults":{"type":"object","properties":{"passed":{"type":"integer"},"failed":{"type":"integer"}},"required":["passed","failed"],"description":"Per-verdict breakdown among the `completed` child runs.","title":"AgentTestSuiteRunWithRunsResults"},"SuiteChildRun":{"type":"object","properties":{"id":{"type":"string","pattern":"^run_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`run_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"test_id":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`)\nof the parent test.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent this run executed against.\n"},"status":{"$ref":"#/components/schemas/JobStatus"},"verdict":{"oneOf":[{"$ref":"#/components/schemas/TestVerdict"},{"type":"null"}],"description":"The pass/fail judgment, present only once the run reached a\nverdict (`status` is `completed`).\n"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"},"result":{"oneOf":[{"$ref":"#/components/schemas/TestRunResult"},{"type":"null"}],"description":"Populated once the run reached a verdict."},"error":{"type":"string","description":"Human-readable error message when `status` is `failed`."},"created_at":{"type":"string","format":"date-time"},"test_name":{"type":"string","description":"Name of the test this run executed."},"agent_name":{"type":"string","description":"Display name of the agent this run executed against."}},"required":["id","test_id","agent_id","status","created_at","test_name","agent_name"],"description":"One child run inside a suite run, carrying the parent test's\nname and the target agent's name so the grouped result view can\nlabel each row. The agent name disambiguates results when the\nsuite spans multiple agents.","title":"SuiteChildRun"},"AgentTestSuiteRunWithRuns":{"type":"object","properties":{"id":{"type":"string","pattern":"^srun_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`srun_<26 char Crockford base32>`)."},"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed `agent_` id of the agent whose suite\nwas run. Set for the `run_all` trigger; null for `batch`,\nwhich can span many agents.\n"},"agent_name":{"type":["string","null"],"description":"Display name of `agent_id`'s agent, resolved at read time.\nNull whenever `agent_id` is null, and on the suite run\nembedded in run/resubmit creation responses.\n"},"trigger":{"$ref":"#/components/schemas/SuiteRunTrigger"},"parent_suite_run_id":{"type":["string","null"],"description":"Set on a `resubmit`: the prefixed `srun_` id of\nthe suite run whose failed/errored tests this one re-ran.\nNull for `run_all` and `batch`.\n"},"status":{"$ref":"#/components/schemas/JobStatus"},"verdict":{"oneOf":[{"$ref":"#/components/schemas/TestVerdict"},{"type":"null"}],"description":"The suite's pass/fail judgment, present only when `status` is\n`completed`.\n"},"total":{"type":"integer","description":"Number of child runs in the suite."},"completed":{"type":"integer","description":"Child runs that produced a verdict."},"failed":{"type":"integer","description":"Child runs that could not execute (an infrastructure failure)."},"running":{"type":"integer","description":"Child runs actively executing."},"pending":{"type":"integer","description":"Child runs queued, not yet executing."},"cancelled":{"type":"integer","description":"Child runs cancelled."},"results":{"$ref":"#/components/schemas/AgentTestSuiteRunWithRunsResults","description":"Per-verdict breakdown among the `completed` child runs."},"created_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time","description":"Newest child-run completion; null until every child run is terminal."},"config_override":{"oneOf":[{"$ref":"#/components/schemas/TestRunConfigOverride"},{"type":"null"}],"description":"The run-level config override this suite was run\nwith, or null for an ordinary Run All / batch."},"flow_version_id":{"type":["string","null"],"pattern":"^fver_[0-9a-hjkmnp-tv-z]{26}$","description":"The flow version (`agent_versions` row) this suite targeted\n(prefixed external id, `fver_...`), or null for the agent's\nactive / synthesized flow."},"flow_version_number":{"type":["integer","null"],"description":"Human-facing version number of `flow_version_id`; null when no version was targeted."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/SuiteChildRun"}}},"required":["id","trigger","status","total","completed","failed","running","pending","cancelled","results","created_at","runs"],"description":"A suite run plus every child run, for the grouped detail view.","title":"AgentTestSuiteRunWithRuns"},"RunAllTestsRequest":{"type":"object","properties":{"config_override":{"$ref":"#/components/schemas/TestRunConfigOverride"},"flow_version_id":{"type":"string","description":"Targets a specific flow version (an `agent_versions` row)\ninstead of the agent's active flow \u2014 version-targeted\nregression. Must be a flow version of the agent under test.\nPrefixed external id (`fver_...`)."}},"description":"Optional body of `POST /v1/agents/{agent_id}/tests/runs`. Omit it\nentirely to run every test against the agent's live config and\nactive flow.","title":"RunAllTestsRequest"},"TestStatsBucket":{"type":"object","properties":{"day":{"type":"string","description":"ISO date (YYYY-MM-DD)."},"passed":{"type":"integer"},"failed":{"type":"integer"},"errored":{"type":"integer"}},"required":["day","passed","failed","errored"],"description":"One daily point on the aggregate pass-rate chart.","title":"TestStatsBucket"},"TestStats":{"type":"object","properties":{"window_days":{"type":"integer"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/TestStatsBucket"}},"total_runs":{"type":"integer"},"passed_runs":{"type":"integer"},"failed_runs":{"type":"integer"},"errored_runs":{"type":"integer"},"avg_duration_ms":{"type":"integer"},"by_type":{"type":"object","additionalProperties":{"type":"integer"}}},"required":["window_days","buckets","total_runs","passed_runs","failed_runs","errored_runs","avg_duration_ms"],"description":"Aggregate run metrics over the requested window. `buckets` is\ndense - one entry per day in the window, zero-filled, so a chart\nnever has gaps. `by_type` counts runs per test type across the\nwhole window.","title":"TestStats"},"ShareLinkStatus":{"type":"string","enum":["active","revoked","expired","exhausted"],"description":"The link's resolved lifecycle state, computed server-side so\nevery client agrees on what \"expired\" means.","title":"ShareLinkStatus"},"ShareLink":{"type":"object","properties":{"id":{"type":"string","pattern":"^share_[0-9a-hjkmnp-tv-z]{26}$"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"label":{"type":"string","description":"The owner's own note on the link, for telling several apart."},"token_prefix":{"type":"string","description":"The token's leading characters, kept so a list can show a\nrecognisable masked label. Useless as a guess on its own."},"status":{"$ref":"#/components/schemas/ShareLinkStatus","description":"The link's resolved lifecycle state, computed server-side so\nevery client agrees on what \"expired\" means."},"expires_at":{"type":"string","format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"},"budget_seconds":{"type":"integer","description":"Total conversation time this link may ever authorise."},"seconds_used":{"type":"integer","description":"Conversation time settled against the link."},"seconds_reserved":{"type":"integer","description":"Conversation time held by calls currently in flight."},"seconds_remaining":{"type":"integer","description":"What a new call can still draw on: the budget minus settled and\nin-flight time. Reservations count against it, so two\nsimultaneous starts cannot both spend the tail."},"max_call_duration_seconds":{"type":"integer","description":"Ceiling on any single call opened through this link. Clamped at\ndispatch against the agent's own limit and the workspace plan,\nso a link can shorten a call but never lengthen one."},"max_concurrent":{"type":"integer","description":"How many calls this link may run at once. Bounded separately\nfrom the workspace's own concurrency cap, so a widely-shared\nlink cannot consume the capacity the owner's real callers need."},"allowed_tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Tools the owner opted this link into, by id \u2014 the same\n`tool_\u2026` ids `listAgentTools` returns. Empty means the\ndefault posture: the agent's built-in call controls and\nknowledge search only, with no webhook, MCP or transfer tool \u2014\na link recipient is a stranger, not the owner's customer."},"last_used_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"required":["id","agent_id","label","token_prefix","status","expires_at","revoked_at","budget_seconds","seconds_used","seconds_reserved","seconds_remaining","max_call_duration_seconds","max_concurrent","allowed_tool_ids","last_used_at","created_at"],"description":"A share link: a revocable, capped URL that lets anyone hold a voice\nconversation with an agent without signing in.\n\nThe bearer token is not part of this shape. A list read is fired on\nevery page mount and the token is a credential that spends money, so\nit is fetched one link at a time from `revealShareLinkToken` instead.\n`token_prefix` is what a list can show. Every link's URL can be read\nback, so there is no state in which a copy action is unavailable.","title":"ShareLink"},"ListShareLinksResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"share_links":{"type":"array","items":{"$ref":"#/components/schemas/ShareLink"}}},"required":["next_cursor","has_more","share_links"],"description":"Payload for `GET /v1/agents/{agent_id}/share-links`. Cursor-paginated:\nwalk pages while `has_more` is true.","title":"ListShareLinksResponse"},"CreateShareLinkRequest":{"type":"object","properties":{"label":{"type":"string","maxLength":120,"description":"An optional note so several links can be told apart."},"expires_in_seconds":{"type":"integer","description":"How long the link stays usable. A duration rather than a\ntimestamp, so a client with a skewed clock cannot mint a link\nthat is already expired. Defaults to 7 days, maximum 30."},"budget_seconds":{"type":"integer","description":"Total conversation time the link may authorise, across all its\ncalls. This is the control that bounds what a leaked link can\ncost. Defaults to 1 hour, maximum 4."},"max_call_duration_seconds":{"type":"integer","description":"Ceiling on a single call. Defaults to 5 minutes, maximum 30, and\nis clamped again to the agent's own limit."},"max_concurrent":{"type":"integer","description":"Simultaneous calls allowed on this link. Defaults to 3, maximum 10."},"allowed_tool_ids":{"type":"array","items":{"type":"string"},"description":"Tools to opt this link into, by id \u2014 the same `tool_\u2026` ids\n`listAgentTools` returns. Omit for the default posture (call\ncontrols and knowledge search only). Opting a webhook or MCP\ntool in lets a stranger drive it, so it is an explicit act\nrather than something inherited from the agent. Every id must\nname a tool attached to this agent; anything else is a 400."}},"description":"Body for `POST /v1/agents/{agent_id}/share-links`. Every field is\noptional and falls back to a bounded default.","title":"CreateShareLinkRequest"},"CreateShareLinkResponseStatus":{"type":"string","enum":["active","revoked","expired","exhausted"],"description":"The link's resolved lifecycle state, computed server-side so\nevery client agrees on what \"expired\" means.","title":"CreateShareLinkResponseStatus"},"CreateShareLinkResponse":{"type":"object","properties":{"id":{"type":"string","pattern":"^share_[0-9a-hjkmnp-tv-z]{26}$"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"label":{"type":"string","description":"The owner's own note on the link, for telling several apart."},"token_prefix":{"type":"string","description":"The token's leading characters, kept so a list can show a\nrecognisable masked label. Useless as a guess on its own."},"status":{"$ref":"#/components/schemas/CreateShareLinkResponseStatus","description":"The link's resolved lifecycle state, computed server-side so\nevery client agrees on what \"expired\" means."},"expires_at":{"type":"string","format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"},"budget_seconds":{"type":"integer","description":"Total conversation time this link may ever authorise."},"seconds_used":{"type":"integer","description":"Conversation time settled against the link."},"seconds_reserved":{"type":"integer","description":"Conversation time held by calls currently in flight."},"seconds_remaining":{"type":"integer","description":"What a new call can still draw on: the budget minus settled and\nin-flight time. Reservations count against it, so two\nsimultaneous starts cannot both spend the tail."},"max_call_duration_seconds":{"type":"integer","description":"Ceiling on any single call opened through this link. Clamped at\ndispatch against the agent's own limit and the workspace plan,\nso a link can shorten a call but never lengthen one."},"max_concurrent":{"type":"integer","description":"How many calls this link may run at once. Bounded separately\nfrom the workspace's own concurrency cap, so a widely-shared\nlink cannot consume the capacity the owner's real callers need."},"allowed_tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Tools the owner opted this link into, by id \u2014 the same\n`tool_\u2026` ids `listAgentTools` returns. Empty means the\ndefault posture: the agent's built-in call controls and\nknowledge search only, with no webhook, MCP or transfer tool \u2014\na link recipient is a stranger, not the owner's customer."},"last_used_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"token":{"type":"string","description":"The link's opaque bearer token."}},"required":["id","agent_id","label","token_prefix","status","expires_at","revoked_at","budget_seconds","seconds_used","seconds_reserved","seconds_remaining","max_call_duration_seconds","max_concurrent","allowed_tool_ids","last_used_at","created_at","token"],"description":"The created link, plus its bearer token. The token is also stored, so\nit can be read back later with `revealShareLinkToken` rather than by\nrevoking a link the recipient may already hold.\n\nThe response returns the raw token rather than a URL, so the surface\nthat shows it owns the origin.","title":"CreateShareLinkResponse"},"RevealShareLinkTokenResponse":{"type":"object","properties":{"token":{"type":"string","description":"The link's opaque bearer token."}},"required":["token"],"description":"A share link's bearer token, so the owner can copy the URL again.","title":"RevealShareLinkTokenResponse"},"ShareLinkPreview":{"type":"object","properties":{"agent_name":{"type":"string","description":"The agent's display name. Present because a visitor needs to\nknow who they are about to talk to, and it is not part of the\nembed's visual configuration."},"widget_config":{"$ref":"#/components/schemas/WidgetConfig","description":"The embed's resolved visual configuration \u2014 avatar, colours,\ntheme, button copy \u2014 so the page renders exactly like the real\nembed without ever naming the agent."},"terms_enabled":{"type":"boolean","description":"Whether the owner configured a pre-call consent gate. Surfaced\nhere so the PAGE can render it: the embed's own gate reads the\nagent's configuration, which a share-link page deliberately\nnever loads."},"terms_content":{"type":"string","description":"The consent text to display, as markdown."},"max_call_duration_seconds":{"type":"integer"},"seconds_remaining":{"type":"integer","description":"Call time left on the link, so the page can say \"nearly used\nup\" rather than failing at the click."}},"required":["agent_name","widget_config","terms_enabled","max_call_duration_seconds","seconds_remaining"],"description":"What a share-link page may know before anyone speaks: enough to\nrender \"who is this, and can I talk to them\".\n\nDeliberately thin. No agent id (which would let a caller bypass the\nlink's limits by calling the public session endpoint directly), no\nworkspace id, no prompt, no model, no tool or knowledge-base names.","title":"ShareLinkPreview"},"CreateShareLinkSessionRequest":{"type":"object","properties":{"token":{"type":"string","description":"The share link's opaque bearer token."}},"required":["token"],"description":"Body for `POST /v1/agents/share-links/sessions`. The token travels in\nthe body rather than the URL so it does not reach request logs.","title":"CreateShareLinkSessionRequest"},"ShareLinkSession":{"type":"object","properties":{"token":{"type":"string","description":"The realtime access token for the session."},"url":{"type":"string","description":"The realtime server URL to connect to."}},"required":["token","url"],"description":"The realtime handles for a share-link call \u2014 exactly what the\n`` element's `sessionProvider` returns.","title":"ShareLinkSession"},"PhoneNumberProvider":{"type":"string","enum":["livekit","twilio","telnyx","byoc","twilio_purchased","telnyx_purchased","verified_caller_id"],"description":"Which provider the number came from. Determines the provisioning\nand portability path.\n\n- `livekit` - LiveKit owns the carrier relationship; US inbound only.\n- `twilio` - Customer's own Twilio number bridged via Elastic SIP Trunk.\n- `telnyx` - Customer's own Telnyx number bridged via a Telnyx FQDN connection.\n- `byoc` - Any SIP provider using a customer-supplied trunk.\n- `twilio_purchased` - Bought through `POST /v1/agents/phone-numbers/purchase` on Speechify's master Twilio account; billed to Speechify.\n- `telnyx_purchased` - Bought through `POST /v1/agents/phone-numbers/purchase` (with `provider=telnyx`) on Speechify's master Telnyx account; billed to Speechify.\n- `verified_caller_id` - Customer-verified outbound caller ID on\n their own Twilio account (Twilio's OutgoingCallerIds resource).\n Server-determined at import time: when an `e164` submitted with\n `provider=twilio` is not a full DID on the customer's account but\n IS a verified caller ID, the resulting row gets this provider.\n Outbound-only, never agent-bindable, rides the customer's\n existing shared Twilio trunk for outbound routing. Requires a\n prior `twilio` full-DID import from the same account; without\n it the import returns 400.\n","title":"PhoneNumberProvider"},"PhoneNumberRelayHealth":{"type":"string","enum":["healthy","unhealthy"],"description":"Read-only endpoint health. `unhealthy` after several consecutive\ndelivery failures: inbound calls answer with the goodbye line and\nSMS events are parked instead of delivered. Resets to `healthy`\non the next successful delivery, on rebinding the relay, or on\nrotating the secret.","title":"PhoneNumberRelayHealth"},"PhoneNumberRelay":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The HTTPS endpoint receiving this number's events."},"voice_id":{"type":"string","description":"Voice that speaks the relayed replies. When absent, the platform\ndefault voice is used."},"reply_timeout_ms":{"type":"integer","minimum":1000,"maximum":30000,"description":"Per-turn deadline in milliseconds for the endpoint to answer a\nreply-bearing event. Default 5000."},"fallback_line":{"type":"string","description":"Line spoken to cover a slow or failing endpoint while the\ndelivery is retried (e.g. \"One moment please\")."},"goodbye_line":{"type":"string","description":"Line spoken before hanging up when the endpoint stays\nunreachable after the retry."},"api_version":{"type":"string","description":"Dated version (`YYYY-MM-DD`) the relay's event payloads are\nrendered at. When absent, deliveries follow the workspace's\ncurrent version."},"greeting":{"type":"string","description":"Line spoken when the call is picked up, ahead of the webhook's own\n`call.started` reply (and after any AI-disclosure line). Omitted\nwhen unset."},"transfer_number":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","description":"Default cold-transfer destination in E.164, used when a reply asks\nto transfer without an explicit target. Omitted when unset."},"hangup_after_silence_seconds":{"type":"integer","description":"Auto-hangup silence timeout in seconds. Omitted when unset (no\nsilence hangup)."},"ai_disclosure":{"$ref":"#/components/schemas/AIDisclosure","description":"Opt-in AI-disclosure opening line spoken first on `call.started`,\nbefore the webhook greeting. Omitted when off (the default)."},"signing_secret":{"type":"string","description":"HMAC signing secret (`whsec_...`) for verifying the\n`Speechify-Signature` header on deliveries. Present ONLY in the\nresponse that FIRST bound the relay and in the rotate-secret\nresponse; store it, it cannot be retrieved later. Editing a relay\nsetting on an already-bound number never mints a new one, so the\ncopy you stored keeps verifying - rotate-secret is the only way\nto replace it."},"health":{"$ref":"#/components/schemas/PhoneNumberRelayHealth","description":"Read-only endpoint health. `unhealthy` after several consecutive\ndelivery failures: inbound calls answer with the goodbye line and\nSMS events are parked instead of delivered. Resets to `healthy`\non the next successful delivery, on rebinding the relay, or on\nrotating the secret."}},"required":["url","reply_timeout_ms"],"description":"**Deprecated. Read the number's `brain_id` and fetch the brain at\n`/v1/agents/brains/{brain_id}` instead.**\n\nA read-only projection of the brain a number is bound to, kept for one\ndeprecation cycle. It is returned ONLY by the deprecated\n`POST /v1/agents/phone-numbers/{phone_number_id}/rotate-secret`, which\nis the one place its `signing_secret` can appear; every other\nphone-number response omits it.\n","title":"PhoneNumberRelay","deprecated":true},"PhoneNumberCapability":{"type":"string","enum":["inbound","outbound"],"description":"What the number can do. LiveKit-native numbers are `inbound` only;\nverified caller IDs are `outbound` only; Twilio and BYOC full-DID\nnumbers (and Speechify-purchased numbers) support both directions.\n","title":"PhoneNumberCapability"},"PhoneNumberReputationRisk":{"type":"string","enum":["unknown","low","medium","high"],"description":"Overall spam-risk classification. `unknown` means the number has\nbeen checked but the carrier networks have no score yet (a fresh\nnumber has no calling history); `low` / `medium` / `high` are the\nreported risk levels.","title":"PhoneNumberReputationRisk"},"PhoneNumberReputation":{"type":"object","properties":{"risk":{"$ref":"#/components/schemas/PhoneNumberReputationRisk","description":"Overall spam-risk classification. `unknown` means the number has\nbeen checked but the carrier networks have no score yet (a fresh\nnumber has no calling history); `low` / `medium` / `high` are the\nreported risk levels."},"checked_at":{"type":"string","format":"date-time","description":"When the reputation was last read."}},"required":["risk"],"description":"A number's carrier spam-reputation status, refreshed on a schedule\nfrom the analytics networks carriers use. Read-only; surfaced\nalongside relay health so a number going bad is visible before it\ndrags down deliverability.\n","title":"PhoneNumberReputation"},"PhoneNumber":{"type":"object","properties":{"id":{"type":"string","pattern":"^phone_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`phone_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"e164":{"type":"string","description":"The phone number in E.164 format (e.g. `+12025551234`)."},"type":{"$ref":"#/components/schemas/PhoneNumberProvider"},"label":{"type":"string","description":"Optional human-readable label set by the customer."},"trunk_id":{"type":"string","description":"ID of the SIP trunk backing this number, if applicable."},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"ID of the agent this number's calls run as. Null only while the\nnumber is unbound. Pointing a number at a brain provisions an agent\ntoo - it is what gives an externally-brained call a conversation,\ntranscript and lifecycle - so this is also the `agent_id` to pass to\n`POST /v1/agents/outbound-calls` to dial out from such a number."},"brain_id":{"type":"string","pattern":"^brain_[0-9a-hjkmnp-tv-z]{26}$","description":"The external brain answering this number's traffic. Absent when\nthe number is unbound or answered by a hosted agent. Read the\nendpoint, health and settings at `/v1/agents/brains/{brain_id}`."},"relay":{"$ref":"#/components/schemas/PhoneNumberRelay","description":"**Deprecated. Use `brain_id`.** Returned only by the deprecated\nrotate-secret operation on this resource, to carry the brain's\none-time `signing_secret`; omitted everywhere else.","deprecated":true},"iso_country":{"type":"string","description":"ISO-3166 alpha-2 country of the number. Present on purchased\nnumbers; may be absent on imported numbers."},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumberCapability"},"description":"What this number can do."},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"reputation":{"$ref":"#/components/schemas/PhoneNumberReputation","description":"Carrier spam-reputation status for the number, refreshed on a\nschedule. Present once the number has been checked at least\nonce; omitted otherwise."},"created_at":{"type":"string","format":"date-time","description":"When the number was imported."},"updated_at":{"type":"string","format":"date-time","description":"When the number was last modified."}},"required":["id","e164","type","capabilities","created_at","updated_at"],"description":"A phone number in the workspace inventory. Its brain is a union: a\nhosted agent, or one of your own external brains via `brain_id` - never\nboth at once. Unbound numbers are valid but non-functional until\nassigned. A number bound to an external brain also carries an\n`agent_id` (the bind provisions one, which is what gives the call a\nconversation and a transcript), so `brain_id` is the discriminator.\n","title":"PhoneNumber"},"ListPhoneNumbersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"numbers":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumber"},"description":"Phone numbers in the workspace."}},"required":["next_cursor","has_more","numbers"],"description":"Response for `GET /v1/agents/phone-numbers`.","title":"ListPhoneNumbersResponse"},"TwilioImportSpec":{"type":"object","properties":{"account_sid":{"type":"string","description":"Twilio Account SID (starts with `AC`)."},"auth_token":{"type":"string","description":"Twilio Auth Token. Write-only - never echoed back."}},"required":["account_sid","auth_token"],"description":"Twilio credentials for the one-click import flow. Used only when\n`provider=twilio`. The Account SID and Auth Token are used to\nprovision an Elastic SIP Trunk on the customer's Twilio account\npointing at LiveKit's SIP endpoint, then stored for future trunk\nmanagement operations.\n","title":"TwilioImportSpec"},"TelnyxImportSpec":{"type":"object","properties":{"connection_id":{"type":"string","description":"The Telnyx FQDN connection id (numeric string) the number is\nrouted through. Required.\n"},"sip_username":{"type":"string","description":"Optional credential-auth username for the Telnyx connection.\nWhen both `sip_username` and `sip_password` are provided, the\nimport also provisions outbound calling for the number; omit them\nfor an inbound-only import. Write-only - never echoed back.\n"},"sip_password":{"type":"string","description":"Optional credential-auth password for the Telnyx connection,\npaired with `sip_username`. Write-only - never echoed back.\n"}},"required":["connection_id"],"description":"Telnyx connection details for the bring-your-own-connection import\nflow. Used only when `provider=telnyx`. You provision an FQDN\nconnection on your Telnyx account whose inbound calls forward to\nSpeechify's LiveKit SIP ingress, assign the number to it, then import\nby passing the connection's id. A second number on the same connection\nreuses the one shared trunk + dispatch rule.\n","title":"TelnyxImportSpec"},"ImportPhoneNumberRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"e164":{"type":"string","description":"The phone number in E.164 format. For `provider=livekit` this\nis the number you want LiveKit to purchase. For `provider=twilio`,\n`provider=telnyx`, and `provider=byoc` it is the number you\nalready own.\n"},"provider":{"$ref":"#/components/schemas/PhoneNumberProvider"},"label":{"type":"string","description":"Optional human-readable label."},"trunk_id":{"type":"string","description":"For `provider=byoc`: the SIP trunk to bind this number to.\nPrefixed wire identifier (`trunk_<26 char Crockford base32>`).\nNot required for `provider=livekit`, `provider=twilio`, or\n`provider=telnyx`.\n"},"agent_id":{"type":"string","description":"Optional agent to bind on import. Prefixed wire identifier\n(`agent_<26 char Crockford base32>`).\n"},"twilio":{"$ref":"#/components/schemas/TwilioImportSpec"},"telnyx":{"$ref":"#/components/schemas/TelnyxImportSpec"}},"required":["e164","provider"],"description":"Body for `POST /v1/agents/phone-numbers`. The required fields vary by\n`provider` - see the individual provider descriptions.\n","title":"ImportPhoneNumberRequest"},"PurchasedPhoneNumberProvider":{"type":"string","enum":["twilio_purchased","telnyx_purchased"],"description":"The carrier a Speechify-managed number is bought on, used by\n`POST /v1/agents/phone-numbers/purchase`. The resulting\n`phone_numbers` row carries the matching `PhoneNumberProvider`\nvalue.\n\nOnly `telnyx_purchased` can be bought. `twilio_purchased` is\n**retired for new purchases** and returns `400 validation_failed`;\nit remains in this enum because numbers already bought on it are\nstill listed, released and dialled normally, and because removing a\nrequest enum value would be a breaking change. Omit `provider` and\nthe buy lands on the current carrier automatically - the\nrecommended call for every client.\n","title":"PurchasedPhoneNumberProvider"},"AvailablePhoneNumber":{"type":"object","properties":{"e164":{"type":"string","description":"The phone number in E.164 format."},"friendly_name":{"type":"string","description":"Carrier-formatted display variant, e.g. \"(415) 555-2671\"."},"locality":{"type":"string","description":"City the number is associated with, when known."},"region":{"type":"string","description":"Two-letter state code for US numbers."},"iso_country":{"type":"string","description":"ISO-3166 alpha-2 country code."},"provider":{"$ref":"#/components/schemas/PurchasedPhoneNumberProvider","description":"The purchasable carrier this number comes from. Pass it back\nverbatim as `provider` on the purchase request so the buy is\nplaced on the matching Speechify-managed account.\n"}},"required":["e164","iso_country","provider"],"description":"One hit from `GET /v1/agents/phone-numbers/available`. The number is\nnot held: a concurrent buy by another customer may take it\nbetween this response and a subsequent purchase request.\n","title":"AvailablePhoneNumber"},"SearchAvailablePhoneNumbersResponse":{"type":"object","properties":{"numbers":{"type":"array","items":{"$ref":"#/components/schemas/AvailablePhoneNumber"},"description":"Available numbers (may be empty if no inventory matches)."}},"required":["numbers"],"description":"Response for `GET /v1/agents/phone-numbers/available`.","title":"SearchAvailablePhoneNumbersResponse"},"IntendedUseMarketingOrInformational":{"type":"string","enum":["marketing","informational","mixed"],"description":"The nature of the traffic.","title":"IntendedUseMarketingOrInformational"},"IntendedUse":{"type":"object","properties":{"use_case":{"type":"string","maxLength":500,"description":"What the numbers are used for."},"geographies":{"type":"array","items":{"type":"string"},"description":"Regions or countries the traffic targets."},"expected_monthly_volume":{"type":"integer","minimum":0,"description":"Rough expected calls + messages per month."},"marketing_or_informational":{"$ref":"#/components/schemas/IntendedUseMarketingOrInformational","description":"The nature of the traffic."}},"required":["use_case"],"description":"A workspace's declared intended use for its phone numbers, captured\nat first number purchase (or relay bind): the use case, target\ngeographies, expected monthly volume, and whether the traffic is\nmarketing or informational.\n","title":"IntendedUse"},"PurchasePhoneNumberRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"e164":{"type":"string","description":"The E.164 number to buy. Must currently be in carrier inventory."},"label":{"type":"string","description":"Optional human-readable label."},"provider":{"$ref":"#/components/schemas/PurchasedPhoneNumberProvider","description":"Which carrier's Speechify-managed account to buy on. Optional\nand best omitted: the platform picks the current carrier\n(`telnyx_purchased`). Passing the retired `twilio_purchased`\nreturns `400 validation_failed`.\n"},"agent_id":{"type":"string","description":"Optional agent to bind the number to at purchase time.\nPrefixed wire identifier (`agent_<26 char Crockford base32>`).\n"},"intended_use":{"$ref":"#/components/schemas/IntendedUse","description":"Optional workspace-level intended-use declaration recorded with\nthe purchase (replaces any earlier declaration). May be required\nby the workspace trust policy.\n"},"tos_accepted":{"type":"boolean","description":"Set true to record the ToS/consent attestation for this\nworkspace. Recorded once - a repeat purchase never moves the\noriginal attestation's actor or timestamp.\n"}},"required":["e164"],"description":"Body for `POST /v1/agents/phone-numbers/purchase`. The `e164` must come\nfrom a recent `SearchAvailablePhoneNumbers` response.\n","title":"PurchasePhoneNumberRequest"},"UpdatePhoneNumberRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"Move the resource between workspace projects: a `proj_...` id\nmoves it there, an explicit null moves it back to the implicit\nDefault project, omitted leaves it unchanged.\n"},"label":{"type":"string","description":"New label. Pass an empty string to clear."},"brain_id":{"type":["string","null"],"description":"Point this number at one of your external brains: a `brain_...`\nid binds it, an explicit null takes it off the number, omitted\nleaves the binding unchanged. Only a reference ever rides this body\n- the endpoint and its signing secret belong to the brain and are\nedited at `/v1/agents/brains/{brain_id}`, so rotating the secret\nthere is enough and no number has to be re-saved.\n"},"replace_agent_binding":{"type":"boolean","description":"Acknowledges that this bind takes the number away from a hosted\nagent that is still answering on it. Without it, pointing an\nagent-bound number at an external brain is refused with\n`409 relay_displaces_agent` naming the agent, so the displacement\nis never a surprise. Re-pointing one brain at another does not need\nit.\n"}},"description":"PATCH body for `PATCH /v1/agents/phone-numbers/{phone_number_id}`. Edits\nthe number's own attributes - `label` - and, where the Phone product is\nenabled, points the number at one of your external brains via\n`brain_id`. The\nnumber's provider and `e164` are immutable after import. The agent\nbinding is managed separately as a relationship, via\n`POST`/`DELETE /v1/agents/{agent_id}/phone-numbers/{phone_number_id}`;\na number's brain is a hosted agent or a brain of your own, never both.\n","title":"UpdatePhoneNumberRequest"},"CreateOutboundCallRequest":{"type":"object","properties":{"agent_id":{"type":"string","description":"ID of the agent that handles the answered call."},"to":{"type":"string","description":"Destination phone number in E.164 format (e.g. `+12025559876`)."},"caller_id_number":{"type":"string","description":"The number shown to the callee as caller ID, in E.164 format.\nDefaults to the first outbound-capable number in the workspace.\nUseful for multi-number campaigns where you want to rotate\ncaller IDs.\n"},"language":{"type":"string","description":"Starts the call in one of the agent's configured languages (the\ndefault `language` or an `additional_languages` entry, matched\nby primary subtag) - e.g. a batch campaign dialing a per-row\nlocale. Omit for the agent's default language; an unconfigured\nlanguage is rejected with 400.\n"},"dtmf_prefix":{"type":"string","description":"DTMF digits dialed automatically after the call is answered,\nbefore the agent begins speaking. Use this for IVR navigation\n(e.g. `1ww2` presses 1, waits two seconds, presses 2). `w`\nis a half-second pause; `W` is a one-second pause.\n"},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-call variable overrides merged on top of the agent's stored\ndefaults. Keys must not use the reserved `system__` prefix.\nUseful for injecting per-call context (customer name, order ID)\ninto the agent prompt.\n"},"ringing_timeout_ms":{"type":"integer","description":"How long to wait for the callee to answer before abandoning,\nin milliseconds. Defaults to 30000 (30s). Capped at 80000 (80s).\n"},"amd":{"$ref":"#/components/schemas/AMDConfig","description":"Optional per-call override for the AMD routing config. When\nset, wholesale-replaces the agent's stored AMD shape for\nthis single call (PATCH-replace, not merge). Unlocks the\nbatch-campaign pattern: one agent dialling many recipients\nwith per-row tailored voicemail messages via the existing\ndynamic_variables substitution. Validation rules match\nthe agent-update boundary.\n"}},"required":["agent_id","to"],"description":"Body for `POST /v1/agents/outbound-calls`. Requires a Twilio or BYOC\ntrunk; LiveKit-native numbers do not support outbound today.\n","title":"CreateOutboundCallRequest"},"CreateOutboundCallResponse":{"type":"object","properties":{"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"ID of the conversation created for this call. Use to poll status."}},"required":["conversation_id"],"description":"Returned synchronously when LiveKit accepts the SIP INVITE. Poll\n`GET /v1/agents/conversations/{conversation_id}` for status transitions:\n`pending` (ringing) \u2192 `active` (answered) \u2192 `completed`.\n","title":"CreateOutboundCallResponse"},"BatchCall":{"type":"object","properties":{"id":{"type":"string","pattern":"^batch_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`batch_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent that will run the batch.\n"},"phone_number_id":{"type":["string","null"],"pattern":"^phone_[0-9a-hjkmnp-tv-z]{26}$","description":"Caller-ID override. When set, prefixed wire identifier\n(`phone_<26 char Crockford base32>`) of the phone number to\nuse; falls back to the agent's bound number when null.\n"},"name":{"type":"string","description":"Human-readable batch name."},"status":{"$ref":"#/components/schemas/JobStatus","description":"Lifecycle status. A batch deferred to a future time is\n`pending` with a non-null `scheduled_at`; it moves to `running`\nonce the scheduled time arrives and the dispatcher starts\ndialing.\n"},"total":{"type":"integer","description":"Total number of recipients (the progress denominator)."},"completed":{"type":"integer","description":"Recipients successfully dialed."},"failed":{"type":"integer","description":"Recipients that failed."},"error":{"type":"string","description":"Populated when the batch itself fails."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time","description":"When the dispatcher started dialing."},"ended_at":{"type":["string","null"],"format":"date-time","description":"When the last recipient was resolved."},"scheduled_at":{"type":["string","null"],"format":"date-time","description":"If set, the batch waits until this time before dialing."},"ringing_timeout_ms":{"type":["integer","null"],"description":"Per-call ringing timeout in milliseconds applied to every\nrecipient in the batch. Null when the batch uses the 30s\ndefault.\n"}},"required":["id","agent_id","name","status","total","completed","failed","created_at","updated_at"],"description":"A batch of outbound calls dispatched to a list of recipients.","title":"BatchCall"},"ListBatchCallsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"batches":{"type":"array","items":{"$ref":"#/components/schemas/BatchCall"}}},"required":["next_cursor","has_more","batches"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListBatchCallsResponse"},"BatchRecipientRequest":{"type":"object","properties":{"phone":{"type":"string","description":"Recipient phone number in E.164 format."},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-recipient variable overrides injected into the agent prompt.\nValues may be any JSON type, matching the outbound-call and\nweb-session `dynamic_variables` surfaces. CSV uploads carry\nstring values.\n"},"custom_id":{"type":"string","maxLength":256,"description":"Optional client-supplied correlation key, echoed back on the\nrecipient read shape so results can be reconciled to the caller's\nown identifier (an order id, a CRM lead id). A CSV upload sets it\nfrom a `custom_id` column. Max 256 characters.\n"}},"required":["phone"],"description":"One entry in a batch-call request.","title":"BatchRecipientRequest"},"CreateBatchCallRequest":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable batch name."},"agent_id":{"type":"string","description":"Agent that handles each call."},"phone_number_id":{"type":"string","description":"Caller-ID override. Falls back to the agent's bound number."},"scheduled_at":{"type":"string","format":"date-time","description":"Schedule the batch for a future time (RFC 3339). Omit to start immediately."},"ringing_timeout_ms":{"type":"integer","minimum":1000,"maximum":80000,"description":"Ringing timeout in milliseconds applied to every call in the\nbatch (how long each recipient rings before the dial gives\nup). Range 1000-80000 (1-80s). Omit to use the 30s default.\nExpress this in milliseconds even if a UI collects seconds.\n"},"recipients":{"type":"array","items":{"$ref":"#/components/schemas/BatchRecipientRequest"}}},"required":["name","agent_id","recipients"],"description":"Body for `POST /v1/agents/batch-calls`. Also accepts `multipart/form-data`\nwith a CSV file upload (`csv_file` field) where the `phone` column is\nrequired, an optional `custom_id` column sets the recipient correlation\nkey, and remaining columns become per-recipient `dynamic_variables`.\n","title":"CreateBatchCallRequest"},"BatchRecipientStatus":{"type":"string","enum":["pending","dialing","completed","failed","cancelled","interrupted"],"description":"`cancelled` marks a recipient the batch cancel spared before it\nwas dialed - distinct from `failed` (a dial that was attempted\nand failed). `interrupted` marks a recipient a crashed dispatcher\nleft mid-attempt on resume: the dial was attempted but its outcome\nwas never recorded, so it is not re-dialed - check the linked\nconversation, if any, for the real result.\n","title":"BatchRecipientStatus"},"BatchRecipient":{"type":"object","properties":{"id":{"type":"string","pattern":"^rcpt_[0-9a-hjkmnp-tv-z]{26}$"},"batch_id":{"type":"string","pattern":"^batch_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`batch_<26 char Crockford base32>`)\nof the parent batch.\n"},"phone":{"type":"string","description":"Recipient phone number in E.164 format."},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-recipient variable overrides injected into the agent prompt."},"status":{"$ref":"#/components/schemas/BatchRecipientStatus","description":"`cancelled` marks a recipient the batch cancel spared before it\nwas dialed - distinct from `failed` (a dial that was attempted\nand failed). `interrupted` marks a recipient a crashed dispatcher\nleft mid-attempt on resume: the dial was attempted but its outcome\nwas never recorded, so it is not re-dialed - check the linked\nconversation, if any, for the real result.\n"},"conversation_id":{"type":["string","null"],"pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Set once the call is placed. Prefixed wire identifier\n(`conv_<26 char Crockford base32>`).\n"},"custom_id":{"type":"string","description":"Client-supplied correlation key echoed back from the create\nrequest, so results can be reconciled to the caller's own\nidentifier. Absent when the recipient carried none.\n"},"error":{"type":"string","description":"Populated when this recipient fails."},"attempted_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}},"required":["id","batch_id","phone","status"],"description":"One recipient row in a batch call.","title":"BatchRecipient"},"ListBatchRecipientsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"recipients":{"type":"array","items":{"$ref":"#/components/schemas/BatchRecipient"}}},"required":["next_cursor","has_more","recipients"],"description":"Response for `GET /v1/agents/batch-calls/{batch_call_id}/recipients`.","title":"ListBatchRecipientsResponse"},"IvrMenuListEntryMenuTree":{"type":"object","properties":{},"description":"Validated menu_tree per contracts/agents/ivr_menu.schema.json. Opaque to consumers other than the worker.","title":"IvrMenuListEntryMenuTree"},"IVRMenuListEntry":{"type":"object","properties":{"id":{"type":"string","pattern":"^menu_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`menu_<26 char Crockford base32>`)."},"fingerprint_id":{"type":"string","pattern":"^fp_[0-9a-hjkmnp-tv-z]{26}$"},"transcript_sample":{"type":"string"},"schema_version":{"type":"integer"},"menu_tree":{"$ref":"#/components/schemas/IvrMenuListEntryMenuTree","description":"Validated menu_tree per contracts/agents/ivr_menu.schema.json. Opaque to consumers other than the worker."},"confidence_score":{"type":"string","title":"float"},"succeeded_traversals":{"type":"integer"},"total_traversals":{"type":"integer"},"last_validated_at":{"type":"string","format":"date-time"},"last_observed_at":{"type":"string","format":"date-time"},"occurrence_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}},"required":["id","fingerprint_id","transcript_sample","schema_version","menu_tree","confidence_score","succeeded_traversals","total_traversals","last_validated_at","last_observed_at","occurrence_count","created_at"],"description":"One row in the list-IVR-menus response. Carries the sample\ntranscript so a client can render the IVR identity (keyed on\nthe prefixed `fingerprint_id`) without a second round-trip.\n`last_observed_at` and `occurrence_count` are projected from\n`ivr_fingerprints` for the \"when did we last see this IVR\" signal.\n","title":"IVRMenuListEntry"},"ListIVRMenusResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"menus":{"type":"array","items":{"$ref":"#/components/schemas/IVRMenuListEntry"}}},"required":["next_cursor","has_more","menus"],"description":"Payload for `GET /v1/agents/ivr-menus`.","title":"ListIVRMenusResponse"},"IvrMenuMenuTree":{"type":"object","properties":{},"description":"Validated menu_tree per contracts/agents/ivr_menu.schema.json. Opaque to consumers other than the worker.","title":"IvrMenuMenuTree"},"IVRMenu":{"type":"object","properties":{"id":{"type":"string","pattern":"^menu_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`menu_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"fingerprint_id":{"type":"string","pattern":"^fp_[0-9a-hjkmnp-tv-z]{26}$"},"schema_version":{"type":"integer"},"menu_tree":{"$ref":"#/components/schemas/IvrMenuMenuTree","description":"Validated menu_tree per contracts/agents/ivr_menu.schema.json. Opaque to consumers other than the worker."},"confidence_score":{"type":"string","title":"float"},"succeeded_traversals":{"type":"integer"},"total_traversals":{"type":"integer"},"last_validated_at":{"type":"string","format":"date-time"},"invalidated_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","fingerprint_id","schema_version","menu_tree","confidence_score","succeeded_traversals","total_traversals","last_validated_at","created_at","updated_at"],"description":"One memorized IVR menu level. Identified by the\nSHA-256 fingerprint of the normalized greeting transcript;\nscoped to the caller's workspace (foreign-tenant menus are\nnever returned).\n\n`menu_tree` is the validated JSONB blob the worker consumes:\nprompt text plus the options offered (label + DTMF). Sub-menus\nreached by pressing an option are their own rows, looked up at\ndescent time by a fresh fingerprint - the tree structure is the\nimplicit graph of fingerprint -> fingerprint transitions.\n\n`confidence_score` is `succeeded_traversals / total_traversals`.\nThe worker's plan-then-execute fast path only activates at or\nabove 0.5.\n\n`invalidated_at` is non-null on a soft-deleted row; the API\nfilters these out of list / lookup / get responses so this field\nis informational only.\n","title":"IVRMenu"},"UpdateIVRMenuLabelRequest":{"type":"object","properties":{"dtmf":{"type":"string","description":"DTMF value of the option to relabel (e.g. \"1\", \"*\", \"#\")."},"label":{"type":"string","maxLength":256,"description":"New label. Capped at 256 chars server-side."}},"required":["dtmf","label"],"description":"Re-label one option in the stored menu_tree. The option is\nmatched by its DTMF value; the label is the human-readable text\nshown for review and surfaced to the agent at navigate time.\n","title":"UpdateIVRMenuLabelRequest"},"InvalidateIVRMenuRequest":{"type":"object","properties":{"reason":{"type":"string","maxLength":256,"description":"Operator-debug cause string. Bounded to 256 chars."}},"description":"Optional reason captured in structured logs. The column today\nis the timestamp, not the cause; a future audit table may\npersist the reason if customer demand justifies it.\n","title":"InvalidateIVRMenuRequest"},"SIPTrunkProvider":{"type":"string","enum":["livekit","twilio","telnyx","byoc"],"description":"Which provider backs the trunk. Informs the provisioning path and\nportability story.\n\n- `livekit` - Provisioned by LiveKit's native phone-number API.\n- `twilio` - Backed by a Twilio Elastic SIP Trunk on the customer's account.\n- `telnyx` - Backed by a Telnyx FQDN connection on the customer's account.\n- `byoc` - Any SIP provider with a customer-managed trunk.\n","title":"SIPTrunkProvider"},"SIPTrunkDirection":{"type":"string","enum":["inbound","outbound","both"],"description":"Whether the trunk handles inbound calls, outbound calls, or both.\nA `both` trunk has distinct provider inbound and outbound trunk IDs.\n","title":"SIPTrunkDirection"},"SIPTransport":{"type":"string","enum":["auto","udp","tcp","tls"],"description":"SIP transport protocol. `auto` lets LiveKit negotiate. Use `tls`\nfor production where available - note that TLS is incompatible\nwith SIP REFER (cold transfer). Trunks that need `transfer_to_number`\nshould use `udp` or `tcp`.\n","title":"SIPTransport"},"SIPMediaEncryption":{"type":"string","enum":["disable","allow","require"],"description":"SRTP media encryption policy.\n\n- `disable` - Unencrypted media only.\n- `allow` - Negotiate SRTP; fall back to unencrypted. Recommended default.\n- `require` - Reject calls that do not support SRTP.\n","title":"SIPMediaEncryption"},"SIPTrunk":{"type":"object","properties":{"id":{"type":"string","pattern":"^trunk_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`trunk_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"name":{"type":"string","description":"Human-readable name."},"provider":{"$ref":"#/components/schemas/SIPTrunkProvider"},"direction":{"$ref":"#/components/schemas/SIPTrunkDirection"},"outbound_trunk_set":{"type":"boolean","description":"Whether an outbound trunk is provisioned for this trunk (i.e. it can place outbound calls)."},"sip_address":{"type":"string","description":"SIP endpoint hostname (e.g. `sip.telnyx.com`). Required for `provider=byoc`."},"auth_username":{"type":"string","description":"SIP digest auth username."},"auth_password_set":{"type":"boolean","description":"Whether a SIP digest auth password is configured. The value is never returned."},"allowed_addresses":{"type":"array","items":{"type":"string"},"description":"IP address / CIDR allowlist for inbound SIP connections."},"destination_country":{"type":"string","description":"ISO 3166-1 alpha-2 country code for the outbound dial plan\n(e.g. `US`, `DE`). Required for international outbound on\nsome carriers.\n"},"transport":{"$ref":"#/components/schemas/SIPTransport"},"media_encryption":{"$ref":"#/components/schemas/SIPMediaEncryption"},"headers_to_attributes":{"type":"object","additionalProperties":{"type":"string"},"description":"Inbound SIP headers mapped onto agent variables, as configured\nwhen the trunk was created. Absent when the trunk maps none.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","provider","direction","allowed_addresses","transport","media_encryption","created_at","updated_at"],"description":"A SIP trunk in the workspace. Trunks back one or more phone numbers\nand hold the carrier credentials LiveKit uses to route calls.\n`auth_password` is never echoed - `auth_password_set` indicates\nwhether one is configured.\n","title":"SIPTrunk"},"ListSIPTrunksResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"trunks":{"type":"array","items":{"$ref":"#/components/schemas/SIPTrunk"},"description":"SIP trunks in the workspace (up to 20)."}},"required":["next_cursor","has_more","trunks"],"description":"Response for `GET /v1/agents/sip-trunks`.","title":"ListSIPTrunksResponse"},"CreateSipTrunkRequestCredentials":{"type":"object","properties":{},"description":"Provider-specific credential blob (for future extensibility).","title":"CreateSipTrunkRequestCredentials"},"CreateSIPTrunkRequest":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for the trunk."},"provider":{"$ref":"#/components/schemas/SIPTrunkProvider"},"direction":{"$ref":"#/components/schemas/SIPTrunkDirection"},"sip_address":{"type":"string","description":"SIP endpoint hostname. Required for `provider=byoc`."},"auth_username":{"type":"string","description":"SIP digest auth username."},"auth_password":{"type":"string","description":"SIP digest auth password. Write-only."},"allowed_addresses":{"type":"array","items":{"type":"string"},"description":"IP / CIDR allowlist for inbound connections. Empty means any source is accepted."},"destination_country":{"type":"string","description":"ISO 3166-1 alpha-2 country for the outbound dial plan."},"transport":{"$ref":"#/components/schemas/SIPTransport"},"media_encryption":{"$ref":"#/components/schemas/SIPMediaEncryption"},"credentials":{"$ref":"#/components/schemas/CreateSipTrunkRequestCredentials","description":"Provider-specific credential blob (for future extensibility)."},"headers_to_attributes":{"type":"object","additionalProperties":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,39}$"},"description":"Map inbound SIP headers onto agent variables, so an upstream that\nhas already identified the caller can pass that through instead of\nthe agent re-verifying it in conversation.\n\nEach key is an extension header on the INVITE (`X-\u2026`, a valid SIP\ntoken); each value names the variable its content is exposed under.\nA header mapped to `customer_id` renders in the agent's prompt, and\nin flow tool arguments, as `{{system__sip_customer_id}}`. Two\nheaders may not share a variable name, which is the one rule the\nschema cannot state and the server rejects with\n`validation_failed`.\n\nOnly applies to inbound calls on this trunk. For `provider=byoc`\nthe mapping takes effect when the trunk's first phone number is\nimported, which is when its inbound route is provisioned.\n\n**A mapped header is an identity assertion from your own phone\nsystem, and is only as trustworthy as that system.** PSTN caller ID\nis spoofable and is a hint, not a credential; do not treat either as\nproof of identity for a high-value action. Values are passed to the\nagent as data, never as instructions.\n"}},"required":["name","provider","direction"],"description":"Body for `POST /v1/agents/sip-trunks`.","title":"CreateSIPTrunkRequest"},"Caller":{"type":"object","properties":{"id":{"type":"string","pattern":"^caller_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`caller_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"tenant_id":{"type":"string","pattern":"^ws_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`ws_<26 char Crockford base32>`) of\nthe owning workspace.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent the caller is scoped under.\n"},"contact_id":{"type":"string","pattern":"^contact_[0-9a-hjkmnp-tv-z]{26}$","description":"The workspace person this row is one agent's view of. Absent only\nfor a row minted before contacts existed and not seen since.\n"},"caller_identity":{"type":"string","description":"The raw identifier the caller arrived with (E.164 phone for SIP, LiveKit\nparticipant id for web). Stable for the life of the caller row.\n"},"display_name":{"type":["string","null"],"description":"Operator-editable display name, nullable."},"external_ref":{"type":["string","null"],"description":"Optional handle into the customer's own CRM, nullable."},"metadata":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Customer-supplied JSON metadata blob."},"first_seen_at":{"type":"string","format":"date-time","description":"Timestamp of the earliest observed conversation / memory for this caller."},"last_seen_at":{"type":"string","format":"date-time","description":"Timestamp of the most recent observation. Drives the default list ordering."},"conversation_count":{"type":"integer","description":"Number of conversation rows currently pointing at this caller."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","tenant_id","agent_id","caller_identity","metadata","first_seen_at","last_seen_at","conversation_count","created_at","updated_at"],"description":"First-class Caller entity. Identified by\nthe (tenant, agent, identity) triple. Memories and conversations\nFK at it via `caller_id`.\n","title":"Caller"},"ListCallersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"callers":{"type":"array","items":{"$ref":"#/components/schemas/Caller"}}},"required":["next_cursor","has_more","callers"],"description":"Payload for GET /v1/agents/callers.","title":"ListCallersResponse"},"UpdateCallerRequest":{"type":"object","properties":{"display_name":{"type":["string","null"],"description":"Operator-editable display name. Empty string clears the column."},"external_ref":{"type":["string","null"],"description":"Optional handle into the customer's own CRM. Empty string clears the column."},"metadata":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Replacement metadata JSONB. Must not be `null`."}},"description":"PATCH payload. Omitted fields are unchanged; present fields\noverwrite. Empty string clears nullable text columns; `metadata`\nreplaces the JSONB blob in full when supplied.\n","title":"UpdateCallerRequest"},"DeleteCallerResponse":{"type":"object","properties":{"caller_purged":{"type":"integer","description":"1 on the first delete; 0 on idempotent re-delete."},"memories_purged":{"type":"integer","description":"Number of user_memories rows cascade-soft-deleted under this caller."}},"required":["caller_purged","memories_purged"],"description":"Audit envelope returned by DELETE /v1/agents/callers/{caller_id}. Surfaces\nthe cascade row counts so a privacy operator has direct evidence\nof the purge without re-querying.\n","title":"DeleteCallerResponse"},"ListCallerConversationsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}},"required":["next_cursor","has_more","conversations"],"description":"Payload for GET /v1/agents/callers/{caller_id}/conversations.","title":"ListCallerConversationsResponse"},"ListCallerMemoriesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"memories":{"type":"array","items":{"$ref":"#/components/schemas/Memory"}}},"required":["next_cursor","has_more","memories"],"description":"Payload for GET /v1/agents/callers/{caller_id}/memories.","title":"ListCallerMemoriesResponse"},"AudioAsset":{"type":"object","properties":{"id":{"type":"string","pattern":"^audio_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`audio_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"original_filename":{"type":"string","description":"The filename supplied at upload time, kept for display."},"content_type":{"type":"string","description":"Always `audio/wav`. Pinned server-side after WAV validation\nrather than trusting the upload's multipart Content-Type\nheader.\n"},"size_bytes":{"type":"integer","description":"Stored byte length. Capped at 4 MiB at upload time."},"duration_ms":{"type":"integer","description":"Clip duration in milliseconds. Capped at 30000 (30s) at upload time."},"sample_rate_hz":{"type":"integer","description":"WAV sample rate. Always 48000 (the rate voice agents play back natively)."},"channels":{"type":"integer","description":"Channel count. Always 1 (mono)."},"bit_depth":{"type":"integer","description":"PCM sample bit depth. Always 16."},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"created_at":{"type":"string","format":"date-time"}},"required":["id","original_filename","content_type","size_bytes","duration_ms","sample_rate_hz","channels","bit_depth","created_at"],"description":"Metadata for a pre-recorded WAV clip stored in the workspace's\naudio-asset bucket. Bytes are immutable once uploaded \u2014 to\nreplace a clip, upload a new asset and update any references.\n","title":"AudioAsset"},"ListAudioAssetsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AudioAsset"}}},"required":["next_cursor","has_more","assets"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAudioAssetsResponse"},"TeamMember":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"The member agent (prefixed external id, `agent_...`)."},"role":{"type":"string","description":"The member's role within the team. Defaults to `member`."},"delegation":{"type":"string","description":"Natural-language note for when the manager should hand work to this\nmember (for example \"refunds over $100\"). Empty when unset.\n"},"position":{"type":"integer","description":"The member's ordering position on the composition canvas."}},"required":["agent_id","role","delegation","position"],"description":"One agent the manager delegates to, with an optional natural-language\nhand-off note and a canvas ordering position.\n","title":"TeamMember"},"Team":{"type":"object","properties":{"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project this team belongs to; null when it belongs to none.\nIts agents live in the same project.\n"},"id":{"type":"string","pattern":"^team_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`team_<26 char Crockford base32>`).\n"},"name":{"type":"string","description":"Human-readable label, unique within the workspace."},"description":{"type":"string","description":"Optional longer description. Empty string when unset."},"manager_agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"The orchestrator agent, or `null` while a draft has no manager\npicked yet.\n"},"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","manager_agent_id","members","created_at","updated_at"],"description":"A workspace-owned composition of agents: a `manager_agent_id` that\norchestrates plus the `members` it delegates to. Persists the\ncomposition; the delegation runtime that runs a team is a later\naddition.\n","title":"Team"},"ListTeamsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"teams":{"type":"array","items":{"$ref":"#/components/schemas/Team"}}},"required":["next_cursor","has_more","teams"],"description":"Payload for `GET /v1/agents/teams` (the workspace team catalog).\nCursor-paginated.\n","title":"ListTeamsResponse"},"TeamMemberInput":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"role":{"type":"string"},"delegation":{"type":"string"},"position":{"type":"integer"}},"required":["agent_id"],"description":"A member as written on a create/update body. Only `agent_id` is\nrequired: `role` defaults to `member`, `delegation` is optional, and\n`position` defaults to the member's index in the array.\n","title":"TeamMemberInput"},"CreateTeamRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"The project to create it in. A project-pinned credential creates\ninto its own project regardless, and naming a different one is\nrefused rather than silently redirected. Omit for no project.\n"},"name":{"type":"string"},"description":{"type":"string"},"manager_agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMemberInput"}}},"required":["name"],"description":"Compose a team. The manager and every member must be an agent in the\ncaller's workspace. `description` defaults to an empty string and\n`members` to an empty roster.\n","title":"CreateTeamRequest"},"UpdateTeamRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"manager_agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMemberInput"}}},"description":"All fields optional (merge-patch): an omitted field is left unchanged,\n`manager_agent_id: null` clears the manager, and a `members` array\nreplaces the whole roster.\n","title":"UpdateTeamRequest"},"CreateTeamRunRequest":{"type":"object","properties":{"instruction":{"type":"string","maxLength":8000,"description":"The task or goal to give the team. The manager agent runs its brain against this, delegating sub-goals to the members, and returns its aggregated result. The server's limit is 8000 **bytes**, so a mostly non-ASCII instruction reaches it before 8000 characters."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-run values that seed the manager's flow variables (override its\nstored defaults). As on a single-agent run, the `system__*`\nnamespace and the legacy `memory` alias belong to the platform and\nare rejected with a 400 naming `variables`.\n"},"user_identity":{"type":"string","maxLength":256,"description":"The person this team run acts for, in your own vocabulary - the same\nfield a single-agent run, a conversation and a widget session take,\nso one workspace never has two answers to who a person is. The\nmanager run carries it and every child it delegates inherits it, so\neach member opens knowing what the platform has learned about that\nperson.\n\nOmit it to run the team for nobody in particular. Must not begin\nwith `user_`, `embed_` or `anon_`, which name identities the platform\nderives.\n\nEvery tool called anywhere in the team run - by the manager or by a\ndelegated member - is told this value: a webhook receives it as\n`user_identity` inside the signed body, an MCP server as the\n`Speechify-User-Identity` header, and it renders in a tool's\ntemplated URL or headers as `{{system__caller_id}}`.\n"},"max_turns":{"type":"integer","description":"Upper bound on the manager run's internal turn budget - one turn is\none plan-act-observe cycle. Defaults to 8 when omitted, and bounds\nthe MANAGER only: each delegated child gets its own budget.\n\n**Clamped to the workspace's per-run ceiling** (5 on Free, 10\non Starter, 20 on Pro, 30 on Scale, 50 on Enterprise; per-workspace\noverrides apply): the run's `input.max_turns` echoes the budget it\nactually got, and `GET /v1/workspaces/current/entitlements`\n(`max_run_turns`) reports the ceiling up front, so plan against\nthat rather than the value you sent. An omitted `max_turns` takes\nthe default, clamped to the ceiling.\n"},"output_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional JSON Schema (2020-12) the run's final answer must satisfy.\nWhen set, the agent answers with a JSON object, the platform\nvalidates it, and the conforming object is returned as\n`output.data`.\n\nThe top level must be `type: object` - an array-typed or scalar\nschema is refused at create with `400`. At most 16 KiB.\n\nOn a mismatch the platform re-asks the agent, feeding back up to 8\nof the violations. **At most two repair attempts, and each one\nspends a turn from `max_turns`** - so a schema-constrained run on a\n5-turn ceiling has little room left for tool calls. Each attempt is\njournaled as an `observation` step whose `tool` is the reserved\nname `output_schema`, which a timeline renderer should expect\nalongside real tool names.\n\nA run that never produces a conforming object settles `succeeded`\nwith `incomplete_reason: output_schema_violation` and no\n`output.data` - its prose answer is still on `output.reply`. The\nplatform never returns an object the schema refused.\n"},"metadata":{"type":"object","additionalProperties":{"type":"string"},"description":"Up to 16 arbitrary key/value pairs echoed back on the run. Your own correlation ids belong here - the platform never reads them."}},"required":["instruction"],"description":"Body for `POST /v1/agents/teams/{team_id}/runs`.","title":"CreateTeamRunRequest"},"AgentTriggerType":{"type":"string","enum":["schedule","webhook"],"title":"AgentTriggerType"},"TriggerRunSpec":{"type":"object","properties":{"instruction":{"type":"string","description":"The goal instruction that drives the run (1-8000 characters)."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Variable seed for the run. A webhook fire's JSON body is merged in\nunder the webhook's `payload_variable` key.\n"},"max_turns":{"type":"integer","description":"Optional per-run turn budget; 0 uses the workspace default."},"output_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional JSON Schema (2020-12) the run's final answer must satisfy.\nWhen set, the agent answers with a JSON object, the platform\nvalidates it, and the conforming object is returned as\n`output.data`.\n\nThe top level must be `type: object` - an array-typed or scalar\nschema is refused at create with `400`. At most 16 KiB.\n\nOn a mismatch the platform re-asks the agent, feeding back up to 8\nof the violations. **At most two repair attempts, and each one\nspends a turn from `max_turns`** - so a schema-constrained run on a\n5-turn ceiling has little room left for tool calls. Each attempt is\njournaled as an `observation` step whose `tool` is the reserved\nname `output_schema`, which a timeline renderer should expect\nalongside real tool names.\n\nA run that never produces a conforming object settles `succeeded`\nwith `incomplete_reason: output_schema_violation` and no\n`output.data` - its prose answer is still on `output.reply`. The\nplatform never returns an object the schema refused.\n"},"user_identity":{"type":"string","maxLength":256,"description":"The person every run this trigger fires acts for, in your own\nvocabulary - the same field a request-started run takes. A\nscheduled or webhook-fired run then opens with that person's\nmemory, every tool it calls is told who it is for, and\n`GET /v1/agents/runs?user_identity=` finds it, so you keep no\ntrigger-to-person index of your own. Validated when the trigger is\nsaved, by the run surface's rule: must not begin with `user_`,\n`embed_` or `anon_`.\n"},"metadata":{"type":"object","additionalProperties":{"type":"string"},"description":"Up to 16 key/value pairs merged onto every run this trigger fires,\nbeside the `trigger_id` and `trigger_fire_key` the platform stamps.\nYour own correlation ids belong here. The platform's keys\n(`trigger`, `trigger_id`, `trigger_fire_key`) are refused with a\n400 when the trigger is saved, since the stamp would win.\n"}},"required":["instruction"],"description":"The durable run a trigger starts when it fires.","title":"TriggerRunSpec"},"TriggerScheduleConfig":{"type":"object","properties":{"cron":{"type":"string","description":"A standard 5-field cron expression (minute hour day-of-month month day-of-week)."},"interval_seconds":{"type":"integer","minimum":60,"maximum":7776000,"description":"Fire every N seconds (minimum 60, maximum 7776000 = 90 days)."},"timezone":{"type":"string","description":"IANA time zone name applied to `cron`, such as `America/New_York`,\n`Europe/Berlin` or `Asia/Tokyo`. Any name in the IANA time zone\ndatabase is accepted; an unknown name is refused with a 400 that\nnames `schedule.timezone`. The cron fires at the same local\nwall-clock time on both sides of a daylight-saving change.\nDefaults to UTC.\n"}},"description":"The cadence of a schedule trigger. Set exactly one of `cron` or\n`interval_seconds`.\n","title":"TriggerScheduleConfig"},"TriggerWebhookConfig":{"type":"object","properties":{"payload_variable":{"type":"string","description":"The run-variable key the POST body is bound to. Defaults to `payload`."}},"description":"The inbound-fire config of a webhook trigger.","title":"TriggerWebhookConfig"},"AgentTriggerLastFireStatus":{"type":"string","enum":["pending","dispatched","failed"],"description":"The outcome of the most recent fire: `dispatched` with a run,\n`failed` with the reason in `last_fire_error`, or `pending` while a\ntransient failure waits for its retry. Absent until the trigger has\nfired once. A `failed` trigger stays `failed` until a later fire\nsucceeds; `trigger.fire_failed` is delivered each time.\n","title":"AgentTriggerLastFireStatus"},"AgentTrigger":{"type":"object","properties":{"id":{"type":"string","pattern":"^trigger_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`trigger_<26 char Crockford base32>`)."},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"type":{"$ref":"#/components/schemas/AgentTriggerType"},"name":{"type":"string"},"enabled":{"type":"boolean"},"run":{"$ref":"#/components/schemas/TriggerRunSpec"},"schedule":{"$ref":"#/components/schemas/TriggerScheduleConfig"},"webhook":{"$ref":"#/components/schemas/TriggerWebhookConfig"},"next_fire_at":{"type":"string","format":"date-time","description":"When a schedule trigger next fires, as an absolute UTC instant\ncomputed in the schedule's `timezone`. Absent for a webhook or a\ndisabled schedule.\n"},"last_fired_at":{"type":"string","format":"date-time"},"last_fire_status":{"$ref":"#/components/schemas/AgentTriggerLastFireStatus","description":"The outcome of the most recent fire: `dispatched` with a run,\n`failed` with the reason in `last_fire_error`, or `pending` while a\ntransient failure waits for its retry. Absent until the trigger has\nfired once. A `failed` trigger stays `failed` until a later fire\nsucceeds; `trigger.fire_failed` is delivered each time.\n"},"last_fire_error":{"type":"string","description":"Why the most recent fire did not start a run: the error code a request\nwould have been answered with and what to do about it, for example\n`agent_publish_gate_required: the agent's current configuration has not\npassed the publish gate; publish it and the next fire runs`. Empty when\nthe last fire dispatched.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"fire_path":{"type":"string","description":"The inbound fire URL path for a webhook trigger; prepend the API\nhost. Absent for a schedule trigger.\n"},"secret":{"type":"string","description":"The plaintext fire token for a webhook trigger. Present in the\ncreate response ONLY and never again - store it securely.\n"},"secret_hint":{"type":"string","description":"A masked fragment of the webhook fire token, for display."}},"required":["id","agent_id","type","name","enabled","run","created_at","updated_at"],"description":"An automated start condition bound to a task agent - a `schedule`\n(cron/interval) or a `webhook` (an inbound fire URL) that starts a\ndurable run.\n","title":"AgentTrigger"},"ListAgentTriggersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"triggers":{"type":"array","items":{"$ref":"#/components/schemas/AgentTrigger"}}},"required":["next_cursor","has_more","triggers"],"description":"Payload for `GET /v1/agents/{agent_id}/triggers`. Cursor-paginated.\n","title":"ListAgentTriggersResponse"},"CreateAgentTriggerRequestType":{"type":"string","enum":["schedule","webhook"],"title":"CreateAgentTriggerRequestType"},"CreateAgentTriggerRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/CreateAgentTriggerRequestType"},"name":{"type":"string"},"enabled":{"type":"boolean"},"run":{"$ref":"#/components/schemas/TriggerRunSpec"},"schedule":{"$ref":"#/components/schemas/TriggerScheduleConfig"},"webhook":{"$ref":"#/components/schemas/TriggerWebhookConfig"}},"required":["type","name","run"],"description":"Create a trigger. For a `schedule` trigger set `schedule`; for a\n`webhook` trigger set `webhook` (optional). `enabled` defaults to true.\n","title":"CreateAgentTriggerRequest"},"UpdateAgentTriggerRequest":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"run":{"$ref":"#/components/schemas/TriggerRunSpec"},"schedule":{"$ref":"#/components/schemas/TriggerScheduleConfig"},"webhook":{"$ref":"#/components/schemas/TriggerWebhookConfig"}},"description":"All fields optional (merge-patch): an omitted field is left unchanged.\n`type` is immutable.\n","title":"UpdateAgentTriggerRequest"},"AgentTriggerFireResponseStatus":{"type":"string","enum":["pending","dispatched","failed"],"description":"The fire's dispatch status.","title":"AgentTriggerFireResponseStatus"},"AgentTriggerFireResponse":{"type":"object","properties":{"run_id":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$","description":"The started durable run, present when dispatch succeeded."},"status":{"$ref":"#/components/schemas/AgentTriggerFireResponseStatus","description":"The fire's dispatch status."},"replayed":{"type":"boolean","description":"True when a duplicate delivery replayed the original fire."}},"required":["status"],"description":"Acknowledgement of an inbound webhook fire.","title":"AgentTriggerFireResponse"},"Skill":{"type":"object","properties":{"id":{"type":"string","pattern":"^skill_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"description":{"type":"string"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project this skill belongs to; null when it belongs to none."},"version":{"type":"integer","description":"The version this body came from."},"latest_version":{"type":"integer","description":"The highest version minted. Differs from `version` only when you asked for an older one."},"instructions":{"type":"string","description":"The procedure, rendered into the system prompt of every agent attached at this version."},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Tool definitions this skill contributes to an attached agent's toolbelt."},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Knowledge bases this procedure needs. Unlike tools these are not\ncontributed - the agent must already have them attached, and an\nattach naming one it lacks is refused.\n"},"variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Defaults for the `{{tokens}}` the instructions reference. They only\nfill keys nothing else set - the agent's own values, the flow's and\nthe session's all outrank them.\n"},"attached_agent_count":{"type":"integer","description":"How many agents hold this skill. Zero on list responses."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","project_id","version","latest_version","instructions","tool_ids","knowledge_base_ids","variables","attached_agent_count","created_at","updated_at"],"description":"A versioned procedure: an instruction block plus the tools and\nknowledge it needs, written once and attached to many agents. The body\nreturned is one version's - the latest, or the one `?version=` named.\n","title":"Skill"},"ListSkillsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"skills":{"type":"array","items":{"$ref":"#/components/schemas/Skill"}}},"required":["next_cursor","has_more","skills"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListSkillsResponse"},"CreateSkillRequest":{"type":"object","properties":{"name":{"type":"string","description":"1-128 characters of letters, digits, spaces, or `. _ -`; unique per workspace."},"description":{"type":"string","maxLength":1000},"instructions":{"type":"string","minLength":1,"maxLength":4000,"description":"The procedure. Bounded because it rides on every dispatched turn of\nevery agent that attaches it, so the limit is a per-turn token bill\nrather than a storage bound.\n"},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Default token values. Keys in the reserved `system__` namespace are refused."},"project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project to create the skill in; omit for the caller's default."}},"required":["name","instructions"],"title":"CreateSkillRequest"},"UpdateSkillRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"instructions":{"type":"string","minLength":1,"maxLength":4000},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"variables":{"type":"object","additionalProperties":{"type":"string"}}},"description":"Merge-patch. `name` and `description` edit in place; any of\n`instructions`, `tool_ids`, `knowledge_base_ids` or `variables` mints a\nnew version instead. A present list replaces rather than appends.\n","title":"UpdateSkillRequest"},"SkillVersion":{"type":"object","properties":{"version":{"type":"integer"},"summary":{"type":"string","description":"The first line of the body, bounded. Read the full body with `GET /v1/skills/{skill_id}?version=`."},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"created_at":{"type":"string","format":"date-time"}},"required":["version","summary","tool_ids","knowledge_base_ids","created_at"],"title":"SkillVersion"},"ListSkillVersionsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"versions":{"type":"array","items":{"$ref":"#/components/schemas/SkillVersion"}}},"required":["next_cursor","has_more","versions"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListSkillVersionsResponse"},"AgentSkill":{"type":"object","properties":{"skill_id":{"type":"string","pattern":"^skill_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"description":{"type":"string"},"version":{"type":"integer","description":"The version this agent is pinned to, and the one dispatch renders."},"latest_version":{"type":"integer","description":"The version the catalog holds now. Higher than `version` means an upgrade is available."},"enabled":{"type":"boolean","description":"A disabled skill keeps its pin but is not rendered or dispatched."},"instructions":{"type":"string"},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"variables":{"type":"object","additionalProperties":{"type":"string"}},"attached_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["skill_id","name","description","version","latest_version","enabled","instructions","tool_ids","knowledge_base_ids","variables","attached_at","updated_at"],"description":"One skill as it is attached to one agent, pinned to a version.","title":"AgentSkill"},"ListAgentSkillsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"skills":{"type":"array","items":{"$ref":"#/components/schemas/AgentSkill"}}},"required":["next_cursor","has_more","skills"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentSkillsResponse"},"AttachSkillRequest":{"type":"object","properties":{"version":{"type":"integer","minimum":1,"description":"Version to pin. Omit to pin whatever is latest at this moment."},"enabled":{"type":"boolean","description":"Defaults to true."}},"title":"AttachSkillRequest"},"UpdateAgentSkillRequest":{"type":"object","properties":{"version":{"type":"integer","minimum":1},"enabled":{"type":"boolean"}},"title":"UpdateAgentSkillRequest"},"Store":{"type":"object","properties":{"id":{"type":"string","pattern":"^store_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"description":{"type":"string"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project this store belongs to; null when it belongs to none."},"created_at":{"type":"string","format":"date-time"},"used_bytes":{"type":"integer","format":"int64","description":"Bytes the store holds across its documents and files, kept in step with every write."},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","project_id","created_at","used_bytes","updated_at"],"description":"A durable document + asset store. Runs write to it through\ntheir store tools; hosted-API routes serve it; your own code reads and\nwrites it directly. Document bodies live in object storage, so a\ngrowing dataset costs nothing on the database.\n","title":"Store"},"ListStoresResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"stores":{"type":"array","items":{"$ref":"#/components/schemas/Store"}}},"required":["next_cursor","has_more","stores"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListStoresResponse"},"CreateStoreRequest":{"type":"object","properties":{"name":{"type":"string","description":"1-128 characters of letters, digits, spaces, or `. _ -`; unique among live stores."},"description":{"type":"string","maxLength":1000},"project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project to create the store in; omit for the caller's default."}},"required":["name"],"title":"CreateStoreRequest"},"UpdateStoreRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string","maxLength":1000}},"title":"UpdateStoreRequest"},"StoreCollection":{"type":"object","properties":{"name":{"type":"string"},"document_count":{"type":"integer","minimum":0}},"required":["name","document_count"],"title":"StoreCollection"},"ListStoreCollectionsResponse":{"type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/components/schemas/StoreCollection"}}},"required":["collections"],"title":"ListStoreCollectionsResponse"},"StoreDocumentSource":{"type":"object","properties":{"run_id":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$"},"step":{"type":"integer","minimum":1}},"required":["run_id"],"description":"The durable run (and its journal step) that wrote this version. Absent for a direct API write.","title":"StoreDocumentSource"},"StoreDocument":{"type":"object","properties":{"id":{"type":"string"},"collection":{"type":"string"},"version":{"type":"integer","minimum":1,"description":"Incremented on every write to this id."},"size_bytes":{"type":"integer","minimum":0},"source":{"$ref":"#/components/schemas/StoreDocumentSource"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The document body. Absent when the caller asked for index rows only."}},"required":["id","collection","version","size_bytes","created_at","updated_at"],"description":"One document: its index row plus, when bodies were requested, the JSON object itself.","title":"StoreDocument"},"QueryStoreDocumentsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"documents":{"type":"array","items":{"$ref":"#/components/schemas/StoreDocument"}}},"required":["next_cursor","has_more","documents"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"QueryStoreDocumentsResponse"},"WriteStoreDocumentRequest":{"type":"object","properties":{"id":{"type":"string","description":"On `createDocument`, the id to write at (letters, digits, `_ . - : ~ @ +`, at most 200);\nminted when absent. Ignored on `putDocument` / `updateDocument`, where the URL names it.\n"},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The document body (a JSON object, at most 256 KiB). On `updateDocument`, the fields to merge; a null removes a field."}},"required":["data"],"title":"WriteStoreDocumentRequest"},"StoreWhereClauseOp":{"type":"string","enum":["eq","ne","gt","gte","lt","lte","in"],"title":"StoreWhereClauseOp"},"StoreWhereClause":{"type":"object","properties":{"field":{"type":"string","description":"A top-level field name of the documents."},"op":{"$ref":"#/components/schemas/StoreWhereClauseOp"},"value":{"description":"A string, number, boolean or null; an array of those for `in`."}},"required":["field","op","value"],"title":"StoreWhereClause"},"StoreOrderByDirection":{"type":"string","enum":["asc","desc"],"description":"asc when omitted.","title":"StoreOrderByDirection"},"StoreOrderBy":{"type":"object","properties":{"field":{"type":"string"},"direction":{"$ref":"#/components/schemas/StoreOrderByDirection","description":"asc when omitted."}},"required":["field"],"title":"StoreOrderBy"},"StoreDocumentQuery":{"type":"object","properties":{"where":{"type":"array","items":{"$ref":"#/components/schemas/StoreWhereClause"},"description":"Filters, ANDed. Only top-level string / number / boolean / null fields are queryable."},"order_by":{"$ref":"#/components/schemas/StoreOrderBy"},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Page size, 50 when omitted."},"cursor":{"type":"string","description":"The `next_cursor` of the previous page; must belong to the same ordering."},"include_data":{"type":"boolean","description":"Set false to return index rows only; bodies are included when omitted."}},"title":"StoreDocumentQuery"},"StoreBatchWriteOp":{"type":"string","enum":["set","update","delete"],"title":"StoreBatchWriteOp"},"StoreBatchWrite":{"type":"object","properties":{"op":{"$ref":"#/components/schemas/StoreBatchWriteOp"},"id":{"type":"string","description":"The document id; minted for a `set` without one."},"data":{"type":"object","additionalProperties":{"description":"Any type"}}},"required":["op"],"title":"StoreBatchWrite"},"StoreBatchRequest":{"type":"object","properties":{"writes":{"type":"array","items":{"$ref":"#/components/schemas/StoreBatchWrite"}}},"required":["writes"],"title":"StoreBatchRequest"},"StoreBatchResultOp":{"type":"string","enum":["set","update","delete"],"title":"StoreBatchResultOp"},"StoreBatchResult":{"type":"object","properties":{"op":{"$ref":"#/components/schemas/StoreBatchResultOp"},"id":{"type":"string"},"document":{"$ref":"#/components/schemas/StoreDocument"}},"required":["op","id"],"title":"StoreBatchResult"},"StoreBatchResponse":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/StoreBatchResult"}}},"required":["results"],"title":"StoreBatchResponse"},"StoreAsset":{"type":"object","properties":{"id":{"type":"string","pattern":"^asset_[0-9a-hjkmnp-tv-z]{26}$"},"filename":{"type":"string"},"content_type":{"type":"string"},"size_bytes":{"type":"integer","minimum":0},"source":{"$ref":"#/components/schemas/StoreDocumentSource"},"created_at":{"type":"string","format":"date-time"},"bytes_path":{"type":"string","description":"The API path the bytes stream from (prepend the API host)."}},"required":["id","filename","content_type","size_bytes","created_at","bytes_path"],"title":"StoreAsset"},"ListStoreAssetsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"assets":{"type":"array","items":{"$ref":"#/components/schemas/StoreAsset"}}},"required":["next_cursor","has_more","assets"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListStoreAssetsResponse"},"ListAgentStoresResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"stores":{"type":"array","items":{"$ref":"#/components/schemas/Store"}}},"required":["next_cursor","has_more","stores"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentStoresResponse"},"HostedApiAuthMode":{"type":"string","enum":["consumer_key","public"],"description":"`consumer_key`: every request presents a `ck_` bearer minted for this\nAPI. `public`: no credential; only read resolvers may be served, and\nthe per-IP limiter is the only bound.\n","title":"HostedApiAuthMode"},"HostedAPI":{"type":"object","properties":{"id":{"type":"string","pattern":"^api_[0-9a-hjkmnp-tv-z]{26}$"},"slug":{"type":"string"},"hostname":{"type":"string","description":"The public host, `.`; empty where no domain is configured."},"base_url":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"auth_mode":{"$ref":"#/components/schemas/HostedApiAuthMode","description":"`consumer_key`: every request presents a `ck_` bearer minted for this\nAPI. `public`: no credential; only read resolvers may be served, and\nthe per-IP limiter is the only bound.\n"},"cors_origins":{"type":"array","items":{"type":"string"},"description":"Browser origins allowed to call the API (`*` for any). Empty for server-to-server only."},"enabled":{"type":"boolean"},"daily_run_cap":{"type":"integer","minimum":1,"maximum":100000,"description":"Runs the API's run routes may start per UTC day; the spend ceiling behind a leaked consumer key."},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","slug","hostname","base_url","name","description","auth_mode","cors_origins","enabled","daily_run_cap","project_id","created_at","updated_at"],"description":"A customer-assembled API: a slug on the shared hosted-API\ndomain, routes answered by resolvers, and consumer keys. Build product;\nthe customer assembles it, Speechify hosts it.\n","title":"HostedAPI"},"ListHostedAPIsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"apis":{"type":"array","items":{"$ref":"#/components/schemas/HostedAPI"}}},"required":["next_cursor","has_more","apis"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListHostedAPIsResponse"},"CreateHostedApiRequestAuthMode":{"type":"string","enum":["consumer_key","public"],"description":"consumer_key when omitted.","title":"CreateHostedApiRequestAuthMode"},"CreateHostedAPIRequest":{"type":"object","properties":{"slug":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])?$","description":"3-40 lowercase letters, digits or hyphens; a DNS label, unique on the shared domain; immutable."},"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"auth_mode":{"$ref":"#/components/schemas/CreateHostedApiRequestAuthMode","description":"consumer_key when omitted."},"cors_origins":{"type":"array","items":{"type":"string"}},"daily_run_cap":{"type":"integer","minimum":1,"maximum":100000,"description":"Runs the API may start per UTC day through its run routes; 1000 when omitted."},"project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},"required":["slug","name"],"title":"CreateHostedAPIRequest"},"UpdateHostedApiRequestAuthMode":{"type":"string","enum":["consumer_key","public"],"title":"UpdateHostedApiRequestAuthMode"},"UpdateHostedAPIRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"auth_mode":{"$ref":"#/components/schemas/UpdateHostedApiRequestAuthMode"},"cors_origins":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean","description":"A paused API answers 503 to every consumer request."},"daily_run_cap":{"type":"integer","minimum":1,"maximum":100000}},"title":"UpdateHostedAPIRequest"},"HostedApiRouteMethod":{"type":"string","enum":["GET","POST"],"title":"HostedApiRouteMethod"},"HostedApiResolverType":{"type":"string","enum":["store_query","store_document","run_latest","run"],"title":"HostedApiResolverType"},"HostedApiResolverWhereItemsOp":{"type":"string","enum":["eq","ne","gt","gte","lt","lte","in"],"title":"HostedApiResolverWhereItemsOp"},"HostedApiResolverWhereItems":{"type":"object","properties":{"field":{"type":"string"},"op":{"$ref":"#/components/schemas/HostedApiResolverWhereItemsOp"},"value":{"description":"A literal, or a `{{query.x}}` / `{{path.x}}` / `{{body.x}}` template; a clause whose template is absent from the request is skipped."}},"required":["field","op","value"],"title":"HostedApiResolverWhereItems"},"HostedApiResolverOrderByDirection":{"type":"string","enum":["asc","desc"],"title":"HostedApiResolverOrderByDirection"},"HostedApiResolverOrderBy":{"type":"object","properties":{"field":{"type":"string"},"direction":{"$ref":"#/components/schemas/HostedApiResolverOrderByDirection"}},"required":["field"],"title":"HostedApiResolverOrderBy"},"HostedAPIResolver":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/HostedApiResolverType"},"store_id":{"type":"string","pattern":"^store_[0-9a-hjkmnp-tv-z]{26}$"},"collection":{"type":"string"},"document_id":{"type":"string","description":"A literal id or a `{{path.x}}` / `{{query.x}}` template."},"where":{"type":"array","items":{"$ref":"#/components/schemas/HostedApiResolverWhereItems"}},"order_by":{"$ref":"#/components/schemas/HostedApiResolverOrderBy"},"limit":{"type":"integer","minimum":0,"maximum":100,"description":"Default page size; the consumer's `limit` query parameter overrides it."},"trigger_id":{"type":"string","pattern":"^trigger_[0-9a-hjkmnp-tv-z]{26}$"},"wait_seconds":{"type":"integer","minimum":0,"maximum":30,"description":"How long a `run` route waits for the run before answering 202 (default 20; 0 answers 202 at once)."}},"required":["type"],"description":"What answers a route. `type` selects the fields that apply:\n`store_query` (store_id, collection, where, order_by, limit),\n`store_document` (store_id, collection, document_id),\n`run_latest` (trigger_id of a schedule trigger),\n`run` (trigger_id of a webhook trigger, wait_seconds).\n","title":"HostedAPIResolver"},"HostedAPIRoute":{"type":"object","properties":{"id":{"type":"string","pattern":"^route_[0-9a-hjkmnp-tv-z]{26}$"},"api_id":{"type":"string","pattern":"^api_[0-9a-hjkmnp-tv-z]{26}$"},"method":{"$ref":"#/components/schemas/HostedApiRouteMethod"},"path":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"resolver":{"$ref":"#/components/schemas/HostedAPIResolver"},"response_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional JSON Schema of the response body, rendered into the OpenAPI document."},"cache_ttl_seconds":{"type":"integer","minimum":0,"maximum":3600,"description":"Cache-Control max-age on GET responses; 0 disables caching."},"enabled":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","api_id","method","path","name","description","resolver","cache_ttl_seconds","enabled","created_at","updated_at"],"title":"HostedAPIRoute"},"ListHostedAPIRoutesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"routes":{"type":"array","items":{"$ref":"#/components/schemas/HostedAPIRoute"}}},"required":["next_cursor","has_more","routes"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListHostedAPIRoutesResponse"},"CreateHostedApiRouteRequestMethod":{"type":"string","enum":["GET","POST"],"title":"CreateHostedApiRouteRequestMethod"},"CreateHostedAPIRouteRequest":{"type":"object","properties":{"method":{"$ref":"#/components/schemas/CreateHostedApiRouteRequestMethod"},"path":{"type":"string","description":"Lowercase segments of letters, digits, `. _ -` or a `{param}`; `/openapi.json` and `/_runs` are reserved."},"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"resolver":{"$ref":"#/components/schemas/HostedAPIResolver"},"response_schema":{"type":"object","additionalProperties":{"description":"Any type"}},"cache_ttl_seconds":{"type":"integer","minimum":0,"maximum":3600},"enabled":{"type":"boolean","description":"Enabled when omitted."}},"required":["method","path","resolver"],"title":"CreateHostedAPIRouteRequest"},"UpdateHostedApiRouteRequestMethod":{"type":"string","enum":["GET","POST"],"title":"UpdateHostedApiRouteRequestMethod"},"UpdateHostedAPIRouteRequest":{"type":"object","properties":{"method":{"$ref":"#/components/schemas/UpdateHostedApiRouteRequestMethod"},"path":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"resolver":{"$ref":"#/components/schemas/HostedAPIResolver"},"response_schema":{"type":["object","null"],"additionalProperties":{"description":"Any type"}},"cache_ttl_seconds":{"type":"integer","minimum":0,"maximum":3600},"enabled":{"type":"boolean"}},"title":"UpdateHostedAPIRouteRequest"},"HostedAPIKey":{"type":"object","properties":{"id":{"type":"string","pattern":"^ckey_[0-9a-hjkmnp-tv-z]{26}$"},"api_id":{"type":"string","pattern":"^api_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"key_hint":{"type":"string","description":"The masked secret shown on every read."},"rate_per_minute":{"type":"integer","minimum":0,"description":"Requests per minute this key may make; 0 is unlimited."},"secret":{"type":"string","description":"The plaintext key, present on the create response only."},"revoked_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"required":["id","api_id","name","key_hint","rate_per_minute","created_at"],"title":"HostedAPIKey"},"ListHostedAPIKeysResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"keys":{"type":"array","items":{"$ref":"#/components/schemas/HostedAPIKey"}}},"required":["next_cursor","has_more","keys"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListHostedAPIKeysResponse"},"CreateHostedAPIKeyRequest":{"type":"object","properties":{"name":{"type":"string"},"rate_per_minute":{"type":"integer","minimum":0,"maximum":100000,"description":"Requests per minute; 60 when omitted, 0 for unlimited."}},"required":["name"],"title":"CreateHostedAPIKeyRequest"},"ContactIdentifierKind":{"type":"string","enum":["external_id","phone","email"],"description":"The namespace the value lives in. Values of different kinds never\ndenote the same person by themselves, whatever they look like.\n","title":"ContactIdentifierKind"},"ContactIdentifierAssertedBy":{"type":"string","enum":["api","telephony","unverified"],"description":"Who vouched for this identifier. Server-assigned, never accepted\nfrom a client.\n\n- `api` - your authenticated backend, via this endpoint or a\n server-to-server session. May be linked to an existing contact.\n- `telephony` - carrier ANI on an inbound call. Trustworthy as a\n fact about the call and silent about which of your users it is,\n so it creates a phone-only contact until you say otherwise.\n- `unverified` - a self-asserted claim from a public or embedded\n browser session. Nobody stands behind it, so it never joins a\n verified contact.\n","title":"ContactIdentifierAssertedBy"},"ContactIdentifier":{"type":"object","properties":{"id":{"type":"string","pattern":"^ident_[0-9a-hjkmnp-tv-z]{26}$"},"contact_id":{"type":"string","pattern":"^contact_[0-9a-hjkmnp-tv-z]{26}$"},"kind":{"$ref":"#/components/schemas/ContactIdentifierKind","description":"The namespace the value lives in. Values of different kinds never\ndenote the same person by themselves, whatever they look like.\n"},"value":{"type":"string","description":"The handle itself - an application user id, an E.164 number, an email address."},"asserted_by":{"$ref":"#/components/schemas/ContactIdentifierAssertedBy","description":"Who vouched for this identifier. Server-assigned, never accepted\nfrom a client.\n\n- `api` - your authenticated backend, via this endpoint or a\n server-to-server session. May be linked to an existing contact.\n- `telephony` - carrier ANI on an inbound call. Trustworthy as a\n fact about the call and silent about which of your users it is,\n so it creates a phone-only contact until you say otherwise.\n- `unverified` - a self-asserted claim from a public or embedded\n browser session. Nobody stands behind it, so it never joins a\n verified contact.\n"},"verified_at":{"type":"string","format":"date-time","description":"When an authenticated party asserted this identifier. Absent\nexactly when `asserted_by` is `unverified`, which is what lets you\ntell a vouched-for handle from a self-claim without re-deriving it\nfrom the value's shape.\n"},"created_at":{"type":"string","format":"date-time"}},"required":["id","contact_id","kind","value","asserted_by","created_at"],"description":"One typed handle attached to a contact, with the provenance that\ndecides what may be joined to what. Two identifiers carrying the same\n`value` at different `asserted_by` levels are two different\nidentifiers on two different contacts - collapsing them is exactly the\ncross-caller disclosure the trust model exists to prevent.\n","title":"ContactIdentifier"},"Contact":{"type":"object","properties":{"id":{"type":"string","pattern":"^contact_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`contact_<26 char Crockford base32>`).\nURL paths accept only this prefixed form; bare UUIDs are rejected\nwith 404.\n"},"tenant_id":{"type":"string","pattern":"^ws_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace that owns this contact."},"display_name":{"type":"string","description":"Operator-editable name. Absent when nobody has named this person\nand no resolver supplied one.\n"},"metadata":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Customer-controlled JSON blob."},"identifiers":{"type":"array","items":{"$ref":"#/components/schemas/ContactIdentifier"},"description":"The contact's live identifiers. Always present; empty only for a\ncontact whose every link has been detached.\n"},"agent_ids":{"type":"array","items":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The agents that have reached this person, each holding its own\nview of them under `/v1/agents/callers`. Always present; empty for\na contact nobody has spoken to yet.\n"},"first_seen_at":{"type":"string","format":"date-time","description":"Earliest sighting across every channel and agent. A merge widens\nthis to span both records, because the window belongs to the\nperson rather than to whichever record saw them first.\n"},"last_seen_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","tenant_id","metadata","identifiers","agent_ids","first_seen_at","last_seen_at","created_at","updated_at"],"description":"A workspace-scoped person. Identity lives in the `identifiers` set,\nnot in this row: a contact is whoever those handles denote, and each\nhandle records who vouched for it.\n","title":"Contact"},"ListContactsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}},"required":["next_cursor","has_more","contacts"],"description":"Payload for GET /v1/contacts.","title":"ListContactsResponse"},"UpdateContactRequest":{"type":"object","properties":{"display_name":{"type":"string","description":"Operator-editable display name. Empty string clears the column; an explicit `null` is rejected with a 400."},"metadata":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Replacement metadata JSONB. An explicit `null` is rejected with a 400."}},"description":"PATCH payload. Omitted fields are unchanged; present fields overwrite.\nAn empty `display_name` clears the column; `metadata` replaces the\nstored blob in full when supplied.\n\nNeither field is nullable. Clearing is spelled `\"\"` for\n`display_name` and `{}` for `metadata`; an explicit `null` is\nrejected with a 400 naming the field, never accepted as a no-op.\n","title":"UpdateContactRequest"},"DeleteContactResponse":{"type":"object","properties":{"contact_purged":{"type":"integer","description":"1 when this call tombstoned the contact, 0 when it was already gone."},"identifiers_purged":{"type":"integer","description":"Identifiers tombstoned. They stop resolving immediately, so a\nfuture call or session on that channel mints a fresh contact.\n"},"callers_purged":{"type":"integer","description":"Per-agent caller records tombstoned."},"memories_purged":{"type":"integer","description":"Memory rows erased, across every agent and channel - reached by\ncontact, by caller record, and by the stored caller identity, so a\nrow is not missed because of which key it happened to be written\nunder.\n"},"runs_erased":{"type":"integer","description":"Durable runs whose content was scrubbed: the instruction, the\nvariables, the output and every journalled step, which together\ncarry the person verbatim. The run row itself stays, with its\nstatus, timings and usage - what the workspace was billed for is\nnot the person's to erase.\n"}},"required":["contact_purged","identifiers_purged","callers_purged","memories_purged"],"description":"The audit tally of a contact purge. A privacy operator answering an\nerasure request gets the evidence in the response, without re-querying\na corpus they just asked us to stop returning. All zeros means the\ncontact was already purged.\n","title":"DeleteContactResponse"},"AttachContactIdentifierRequestKind":{"type":"string","enum":["external_id","phone","email"],"description":"The namespace this value lives in.","title":"AttachContactIdentifierRequestKind"},"AttachContactIdentifierRequest":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/AttachContactIdentifierRequestKind","description":"The namespace this value lives in."},"value":{"type":"string","minLength":1,"maxLength":512,"description":"The handle - an application user id, an E.164 number, an email address."}},"required":["kind","value"],"description":"The customer's assertion that this handle denotes this person. It\ncarries the handle and nothing else: `asserted_by` and `verified_at`\nare stamped by the server from the fact that the request\nauthenticated, and are not accepted here.\n","title":"AttachContactIdentifierRequest"},"MergeContactsRequest":{"type":"object","properties":{"from_contact_id":{"type":"string","description":"The contact to fold into the one in the URL. It is tombstoned, and\neverything pointing at it is re-pointed at the survivor. Must\ndiffer from the contact in the URL.\n"}},"required":["from_contact_id"],"description":"Names the contact being merged AWAY; the survivor is the one in the URL.","title":"MergeContactsRequest"},"MergeContactsResponseMerged":{"type":"object","properties":{"identifiers":{"type":"integer","description":"Identifiers re-pointed at the survivor."},"callers":{"type":"integer","description":"Per-agent caller projections re-pointed at the survivor."},"conversations":{"type":"integer","description":"Conversations re-pointed at the survivor."},"memories":{"type":"integer","description":"Memory rows re-pointed at the survivor."}},"required":["identifiers","callers","conversations","memories"],"title":"MergeContactsResponseMerged"},"MergeContactsResponse":{"type":"object","properties":{"contact":{"$ref":"#/components/schemas/Contact"},"merged":{"$ref":"#/components/schemas/MergeContactsResponseMerged"}},"required":["contact","merged"],"description":"The surviving contact plus the audit tally of what moved onto it, so\nan operator reviewing a merge sees the blast radius without\nre-querying. A retry reports zeros - that is how a client tells\n\"already applied\" from \"nothing matched\".\n","title":"MergeContactsResponse"},"BillingEntitlementsMaxLlmModelClass":{"type":"string","enum":["managed","standard","premium"],"description":"Highest LLM model access class an agent on this plan may select.\n`managed`: Speechify-served models, included on every plan.\n`standard`: low-cost third-party models (e.g. GPT-4.1 Mini).\n`premium`: flagship third-party models (e.g. GPT-4.1).\n","title":"BillingEntitlementsMaxLlmModelClass"},"BillingEntitlements":{"type":"object","properties":{"phone_number_quota":{"type":"integer","description":"Speechify-managed (purchased) phone numbers allowed."},"concurrent_call_cap":{"type":"integer","description":"Simultaneously-active voice calls allowed."},"max_members":{"type":"integer","description":"Members allowed in a single workspace (owner + invitees)."},"max_call_duration_seconds":{"type":"integer","description":"Per-call wall-clock ceiling, in seconds."},"max_service_accounts":{"type":"integer","description":"Active service accounts allowed. 0 means service accounts are not\nincluded in the plan (creating one returns 402\nservice_accounts_not_in_plan); existing accounts keep working.\n"},"tts_requests_per_second":{"type":"integer","description":"Sustained requests per second across the audio (TTS) endpoints."},"tts_concurrency":{"type":"integer","description":"Simultaneous in-flight requests across the audio (TTS) endpoints."},"agent_requests_per_second":{"type":"integer","description":"Sustained requests per second across the agents API."},"agent_concurrency":{"type":"integer","description":"Simultaneous in-flight requests across the agents API."},"max_llm_model_class":{"$ref":"#/components/schemas/BillingEntitlementsMaxLlmModelClass","description":"Highest LLM model access class an agent on this plan may select.\n`managed`: Speechify-served models, included on every plan.\n`standard`: low-cost third-party models (e.g. GPT-4.1 Mini).\n`premium`: flagship third-party models (e.g. GPT-4.1).\n"},"can_create_workspaces":{"type":"boolean","description":"Whether the workspace may create ADDITIONAL workspaces."},"topup_allowed":{"type":"boolean","description":"Whether prepaid funds may be added beyond the monthly grant."},"voice_cloning":{"type":"boolean","description":"Whether voice cloning is available."},"batch_calls":{"type":"boolean","description":"Whether batch outbound calls are available."},"custom_llm_endpoint":{"type":"boolean","description":"Whether agents may use a bring-your-own custom LLM endpoint."},"waymark_access":{"type":"boolean","description":"Whether Waymark API access is available."},"text_agent_access":{"type":"boolean","description":"Whether the workspace may create and run agents that serve the text\nchannel. When false, enabling the text channel or opening a text\nconversation returns 402 text_channel_not_in_plan.\n"},"durable_runs_access":{"type":"boolean","description":"Whether the workspace may start and read durable async agent runs,\nincluding the approvals they raise and team runs. When false, those\nendpoints return 402 durable_runs_not_in_plan.\n"},"max_run_turns":{"type":"integer","minimum":1,"maximum":50,"description":"The per-run turn ceiling (one turn is one plan-act-observe cycle).\nA run or a trigger fire asking for more is clamped to it, and the\nrun echoes the budget it got in `input.max_turns`; plan against\nthis value.\n"},"slack_channel_access":{"type":"boolean","description":"Whether the workspace may connect a Slack front door. Off on every\nplan; granted per workspace. A Slack turn runs on the async-turn\nruntime and meters as a text message, so `text_agent_access` is\nrequired alongside it.\n"},"hosted_apis_access":{"type":"boolean","description":"Whether stores and hosted APIs are available. Off on every plan; granted per workspace."},"max_store_documents":{"type":"integer","description":"Documents one store may hold; 0 means uncapped."},"max_store_bytes":{"type":"integer","format":"int64","description":"Bytes one store may hold across its documents and files; 0 means uncapped."},"skills_access":{"type":"boolean","description":"Whether the skill catalog and per-agent attachment surface are available."},"max_skills":{"type":"integer","description":"Skills one workspace may hold; 0 means uncapped."},"max_skills_per_agent":{"type":"integer","description":"Skills one agent may attach; 0 means uncapped. Not only an abuse\nceiling: every enabled skill renders its instructions into the\nagent's system prompt, so this is what bounds prompt growth on a\nlatency-sensitive turn.\n"},"enterprise_sso":{"type":"boolean","description":"Whether enterprise SAML/OIDC SSO is available."},"phone_relay":{"type":"boolean","description":"Whether Agent Phone (a number whose brain is your webhook) is available."},"phone_sms":{"type":"boolean","description":"Whether SMS on Agent Phone numbers is available (also requires 10DLC)."},"zero_data_retention":{"type":"boolean","description":"Whether zero data retention is enabled for this workspace on the\nText to Speech API. Read-only: Speechify sets it against a signed\nagreement, never self-serve, and that agreement defines the scope.\n"},"voice_cloning_consent_exempt":{"type":"boolean","description":"Whether `POST /v1/voices` accepts a clone from this workspace\nwithout a consent challenge and verification recording, because the\nworkspace captures and holds speaker consent under its own\nagreement with Speechify. Read-only: Speechify sets it against that\nagreement, never self-serve. It does not grant cloning - the\n`voice_cloning` entitlement still applies.\n"},"max_manual_top_up_usd":{"type":"integer","description":"Largest single manual prepaid top-up accepted, in whole US dollars.\n0 means adding prepaid credit is not included in the plan. This is\nthe enforced amount, so a top-up of exactly this value succeeds; a\nclient may offer it directly without applying further bounds.\n"},"max_auto_recharge_charge_usd":{"type":"integer","description":"Largest single automatic recharge, in whole US dollars. 0 means\nauto-recharge is not included in the plan.\n"}},"required":["phone_number_quota","concurrent_call_cap","max_members","max_call_duration_seconds","max_service_accounts","tts_requests_per_second","tts_concurrency","agent_requests_per_second","agent_concurrency","max_llm_model_class","can_create_workspaces","topup_allowed","voice_cloning","batch_calls","custom_llm_endpoint","waymark_access","text_agent_access","durable_runs_access","max_run_turns","slack_channel_access","hosted_apis_access","max_store_documents","max_store_bytes","skills_access","max_skills","max_skills_per_agent","enterprise_sso","phone_relay","phone_sms","zero_data_retention","voice_cloning_consent_exempt","max_manual_top_up_usd","max_auto_recharge_charge_usd"],"description":"Customer-facing per-tier limits + feature flags, including the API\nrate limits the plan enforces. Numeric fields are caps; booleans gate\nfeatures.\n","title":"BillingEntitlements"},"EntitlementsResponse":{"type":"object","properties":{"catalog":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/BillingEntitlements"},"description":"Per-tier floor entitlements, keyed by tier string."},"current":{"$ref":"#/components/schemas/BillingEntitlements"}},"required":["catalog","current"],"description":"`catalog` is every sellable tier's FLOOR entitlements, keyed by tier\n(`free`/`starter`/`pro`/`scale`/`enterprise`) for the upgrade cards.\n`current` is the caller's workspace RESOLVED entitlements (tier floor\ncomposed with its per-tenant override), so a custom/Enterprise customer\nsees their actual caps - not just the tier floor.\n","title":"EntitlementsResponse"},"ContactResolver":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The HTTPS endpoint Speechify POSTs the identifier to."},"disabled":{"type":"boolean","description":"When true the endpoint is kept but never called, which is how you\npark an integration without losing its secret.\n"},"secret":{"type":"string","description":"The HMAC signing key, present ONLY in the response that mints it\n(first configuration, or a rotation). Verify\n`Speechify-Signature` with it exactly as you would a webhook.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["url","disabled","created_at","updated_at"],"description":"The workspace's contact-resolver configuration: the endpoint asked to\nidentify an inbound caller at session start.\n","title":"ContactResolver"},"PutContactResolverRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"HTTPS endpoint that answers `{external_id, display_name?,\nvariables?}`. Must resolve to a public address - loopback,\nprivate-range and cloud-metadata hosts are rejected.\n"},"disabled":{"type":"boolean","default":false,"description":"Park the integration without deleting it. Defaults to false."}},"required":["url"],"description":"Sets the workspace's single resolver. Re-sending the same body is a\nno-op; the signing secret is minted on first configuration only.\n","title":"PutContactResolverRequest"},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","pattern":"^whe_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire id (`whe_<26 char Crockford base32>`)."},"url":{"type":"string","format":"uri","description":"HTTPS destination Speechify POSTs signed events to."},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project whose events this endpoint receives (prefixed\nexternal id). Null means workspace-wide - it receives every\nproject's events. Endpoints have no Default project.\n\nAn event is routed by the project frozen on the row that produced\nit: a conversation's, an API key's, a run's (captured when the run\nwas created, so it does not move if the agent later does). The\nworkspace-level events (`batch_call.*`, `workspace.spend_budget.*`,\n`trigger.fire_failed`) belong to no project and reach workspace-wide\nendpoints only, so a scoped endpoint subscribed to those alone is\nrefused with a 400 naming `enabled_events` rather than accepted\nand never delivered to. A scoped endpoint records no delivery for\nanother project's events, so read its deliveries list next to the\nproject's own runs or conversations list.\n"},"enabled_events":{"type":"array","items":{"type":"string"},"description":"The events this endpoint receives: a list of catalog event names\n(see `WebhookEventType`) or `[\"*\"]` for every event, current and\nfuture.\n"},"include":{"type":"array","items":{"type":"string"},"description":"Per-event payload shaping. Deliveries are lean by default:\n`data.object` carries only the resource GET snapshot. List heavy\ncollections here to have them appended under the event's `data`\nalongside `object`, so receivers behind hard request-size caps stay\nlean unless they opt in. Recognised keys (conversation events only):\n`messages` (the full transcript) and `evaluations`. Empty = lean.\n"},"api_version":{"type":"string","format":"date","description":"The dated payload shape this endpoint receives (`YYYY-MM-DD`), the\nsame versioning vocabulary the REST API uses. Every delivery is\nrendered back to this version and carries it in the\n`Speechify-Version` header and the payload's top-level `version`\nfield. Defaults to your workspace's current version at creation;\nchange it to opt into a newer shape.\n"},"description":{"type":["string","null"],"description":"Optional human-readable label for the endpoint."},"disabled":{"type":"boolean","description":"When true, Speechify stops delivering to this endpoint."},"secret":{"type":"string","description":"The HMAC-SHA256 signing secret (`whsec_\u2026`) used to verify the\n`Speechify-Signature` header. Returned ONLY when the endpoint is\ncreated or its secret is rotated \u2014 it is never shown again.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","url","enabled_events","include","api_version","disabled","created_at","updated_at"],"description":"A workspace webhook endpoint: a destination URL, the events it\nsubscribes to, and a server-minted HMAC signing secret. `secret` is\nreturned ONLY in the create and rotate-secret responses \u2014 store it\nthen; every other read omits it.\n\nAn endpoint is either scoped to one project (`project_id`) or\nworkspace-wide, and a workspace-wide endpoint receives events from\nevery project.\n","title":"WebhookEndpoint"},"ListWebhookEndpointsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"endpoints":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}},"required":["next_cursor","has_more","endpoints"],"description":"Payload for GET /v1/webhooks/endpoints.","title":"ListWebhookEndpointsResponse"},"CreateWebhookEndpointRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"HTTPS destination for event deliveries. Must be a publicly\nreachable host: loopback, private, link-local, and cloud-metadata\naddresses (and reserved hostnames like `localhost`) are rejected.\n"},"project_id":{"type":["string","null"],"description":"Optionally scope the endpoint to one project (prefixed\n`proj_...` id): a scoped endpoint receives only that project's\nevents. Omit (or null) for workspace-wide - it receives every\nproject's events. An unknown id returns 404 project_not_found.\nA project-pinned API key creates into its own project and\ncannot name the workspace-wide tier.\n"},"enabled_events":{"type":"array","items":{"type":"string"},"description":"Catalog event names to subscribe to, or `[\"*\"]` for all events."},"include":{"type":"array","items":{"type":"string"},"description":"Optional payload-shaping keys (see `WebhookEndpoint.include`):\n`messages`, `evaluations`. Omit for the lean default.\n"},"api_version":{"type":"string","format":"date","description":"Optionally pin the endpoint's payload shape to a dated version\n(`YYYY-MM-DD`, see `WebhookEndpoint.api_version`). Omit to use the\nworkspace's current version. An unknown version is rejected.\n"},"description":{"type":["string","null"]}},"required":["url","enabled_events"],"title":"CreateWebhookEndpointRequest"},"UpdateWebhookEndpointRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"HTTPS destination for event deliveries. Must be a publicly\nreachable host: loopback, private, link-local, and cloud-metadata\naddresses (and reserved hostnames like `localhost`) are rejected.\n"},"project_id":{"type":["string","null"],"description":"Re-scope the endpoint: a `proj_...` id narrows it to that\nproject's events, an explicit null makes it workspace-wide\n(every project's events), omitted leaves it unchanged. The\nsigning secret and delivery history are untouched, so\nre-scoping never requires redeploying your receiver. An\nunknown id returns 404 project_not_found. A project-pinned API\nkey may only scope an endpoint to its own project.\n"},"enabled_events":{"type":"array","items":{"type":"string"}},"include":{"type":"array","items":{"type":"string"},"description":"Payload-shaping keys (see `WebhookEndpoint.include`). Send `[]` to\nclear back to the lean default.\n"},"api_version":{"type":"string","format":"date","description":"Opt the endpoint into a different (typically newer) payload shape\n(`YYYY-MM-DD`, see `WebhookEndpoint.api_version`). Omit to leave it\nunchanged. An unknown version is rejected.\n"},"description":{"type":["string","null"]},"disabled":{"type":"boolean"}},"description":"Partial update; omitted fields are left unchanged.","title":"UpdateWebhookEndpointRequest"},"WebhookEndpointDelivery":{"type":"object","properties":{"id":{"type":"string","description":"Prefixed delivery id (`whd_<26 char Crockford base32>`)."},"webhook_endpoint_id":{"type":"string","description":"The endpoint this delivery targeted (`whe_\u2026`)."},"event":{"type":"string","description":"The event type delivered (see `WebhookEventType`)."},"url":{"type":"string","description":"The destination URL at delivery time."},"status":{"$ref":"#/components/schemas/WebhookDeliveryStatus"},"attempt_count":{"type":"integer"},"last_attempt_at":{"type":"string","format":"date-time"},"last_status_code":{"type":"integer"},"last_error":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"request_body":{"type":"string","description":"The verbatim payload the `Speechify-Signature` HMAC was computed over\n(as `.`).\n"},"request_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Headers sent, including `Speechify-Signature` / `Speechify-Event` / `Speechify-Delivery-Id`."},"last_response_body":{"type":"string","description":"Your server's response body on the most recent attempt (truncated to 8 KiB)."},"last_response_headers":{"type":"object","additionalProperties":{"type":"string"}}},"required":["id","webhook_endpoint_id","event","url","status","attempt_count","created_at"],"description":"One row of an endpoint's delivery log. One row per (endpoint, event,\nresource); updated in place across retry attempts. Each row records the\nexact request payload + signed headers Speechify sent and the response\nyour server returned, so you can verify the signature and debug failures.\n","title":"WebhookEndpointDelivery"},"ListWebhookEndpointDeliveriesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpointDelivery"}}},"required":["next_cursor","has_more","deliveries"],"description":"Payload for GET /v1/webhooks/endpoints/{webhook_endpoint_id}/deliveries.","title":"ListWebhookEndpointDeliveriesResponse"},"Project":{"type":"object","properties":{"archived_at":{"type":["string","null"],"format":"date-time","description":"When the project was archived; `null` while it is live. While\nset, nothing new starts or bills inside the project and every\nsuch attempt answers `409 project_archived`.\n\nAbsent rather than null while a console runs ahead of an API\nthat predates archiving, which is why it is not required:\nread absent and null alike.\n"},"purged_at":{"type":"string","format":"date-time","description":"When this project was purged, present only on the rows returned\nby `include_purged=true`. A purged project is kept for 30 days\nfrom this stamp and permanently deleted after that, so the\nrestore deadline is this value plus the window; it is absent on\nevery project that still exists. Restore it with\n`POST /v1/projects/{project_id}/restore`.\n"},"max_concurrent_calls":{"type":"integer","minimum":1,"description":"The most voice-agent calls this project may have active at\nonce, present only when set. Checked after the workspace's own\nactive-call cap on every call start (web session, outbound\ncall, batch dial, inbound SIP), keyed on the project the\ncall's agent lives in: a call over the ceiling is refused with\nthe same `429 concurrency_limit_reached` the workspace cap\nanswers (an inbound caller hears the busy message), while\nsibling projects keep their headroom. Never higher than the\nworkspace's cap: a project can narrow the workspace's\ncapacity, not raise it.\n"},"max_concurrent_runs":{"type":"integer","minimum":1,"description":"The most durable agent runs this project may have queued or\nrunning at once, present only when set. Checked after the\nworkspace's own run ceiling on every run start (a request, a\nteam run, or an unattended trigger fire), keyed on the project\nthe run is ATTRIBUTED to - its agent's project, captured at\ncreation - so a workspace-wide key starting a run in this\nproject counts against it. A run over the ceiling is refused\nwith the same `429 concurrency_limit_reached` the workspace\nceiling answers, carrying `Retry-After`, while sibling projects\nkeep their headroom. A run parked on a human approval or on its\nchildren holds no slot and does not count. Never higher than the\nworkspace's own run ceiling: a project can narrow the\nworkspace's capacity, not raise it.\n"},"max_requests_per_minute":{"type":"integer","minimum":1,"description":"The most API requests per minute credentials pinned to this\nproject may make across every surface, present only when set.\nChecked after the workspace's own request-rate limit, in one\nbucket per project: a request over the ceiling is refused with\nthe same `429 rate_limited` the workspace limit answers, while\nother projects and unpinned credentials are untouched. Never\nhigher than the workspace's widest per-surface rate over a\nminute: a project can narrow the workspace's capacity, not\nraise it. Console sessions and unpinned keys carry no project\nand are never subject to it.\n"},"monthly_budget":{"type":"number","format":"double","maximum":1000000000,"exclusiveMinimum":0,"description":"The project's monthly spend limit in US dollars, present only\nwhen one is set. New billable work attributed to this project is\nrefused with the coded `402 project_spend_limit_exceeded` once\n`monthly_spend` reaches it; the limit resets at the calendar-month\nboundary (UTC).\n\nSpend is attributed the same way it is billed: work from a\nproject-pinned API key counts against that key's project, and a\nvoice-agent conversation counts against its agent's project. The\nimplicit Default project cannot carry a limit \u2014 it has no project\nrecord \u2014 so spend there is bounded by the workspace's\n`monthly_budget` instead.\n"},"monthly_spend":{"type":"number","format":"double","minimum":0,"description":"The project's billed month-to-date spend in US dollars, present\nwhenever the billing plane answered - regardless of whether a\nspend limit is set.\n"},"id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace-scoped project identifier (prefixed external id)."},"name":{"type":"string","description":"Human-readable label, unique per workspace (case-insensitive).\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","created_at","updated_at"],"description":"A workspace project: resource grouping that a member grant or a\ncredential pin can also scope. Resources\n(agents, knowledge bases, phone numbers, tools, audio) reference a\nproject by id; a resource with no `project_id` lives in the\nimplicit Default project. A project can carry member grants, which\nscope a granted member to the projects they hold, and the invoice\nstays workspace-level with no billing split. A project may\ncarry a monthly spend LIMIT, which bounds work without splitting\nthe bill, capacity CEILINGS (`max_concurrent_calls`,\n`max_requests_per_minute`, `max_concurrent_runs`) that sit below\nthe workspace's own plan limits so one project cannot consume the\nwhole allowance, and it may be ARCHIVED, which suspends all work\nand spend inside it while everything stays readable.\n\nAn application building on this platform models one of its OWN\nbusiness customers as a project: it is the level that both\nattributes cost (`monthly_spend`, grouped per project on the usage\nbreakdown) and ENFORCES it (`monthly_budget` and the three capacity\nceilings). The person an agent is dealing with is NOT a project -\nthat is `user_identity` on a conversation or a run. How many\nprojects a workspace may hold is the workspace's own ceiling; a\ncreate past it answers `409 project_limit_reached`, and deleting an\nunused project frees a slot.\n","title":"Project"},"ListProjectsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}},"required":["next_cursor","has_more","projects"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListProjectsResponse"},"CreateProjectRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":120,"description":"Project name; unique per workspace (case-insensitive),\nsurrounding whitespace is trimmed.\n"}},"required":["name"],"description":"Create a project in the caller's workspace.","title":"CreateProjectRequest"},"UpdateProjectRequest":{"type":"object","properties":{"max_concurrent_runs":{"type":["integer","null"],"minimum":1,"description":"Sets the project's concurrent-run ceiling; `null` removes it.\nMust be a positive integer at or below the workspace's own run\nceiling, otherwise the request is refused with\n`400 validation_failed` naming the field and the ceiling.\nRequires the `billing.manage` permission. Takes effect on the\nnext run start attributed to the project.\n"},"max_concurrent_calls":{"type":["integer","null"],"minimum":1,"description":"Sets the project's active-call ceiling; `null` removes it.\nMust be a positive integer at or below the workspace's own\nactive-call cap, otherwise the request is refused with\n`400 validation_failed` naming the field and the ceiling: a\nproject can only narrow the workspace's capacity, never raise\nit. Requires the `billing.manage` permission, like the spend\nlimit beside it. Takes effect on the next call start.\n"},"max_requests_per_minute":{"type":["integer","null"],"minimum":1,"description":"Sets the project's request-rate ceiling in requests per\nminute; `null` removes it. Must be a positive integer at or\nbelow the workspace's widest per-surface request rate over a\nminute, otherwise the request is refused with\n`400 validation_failed` naming the field and the ceiling.\nRequires the `billing.manage` permission. Takes effect on the\nnext request from a credential pinned to the project.\n"},"name":{"type":"string","maxLength":120,"description":"New project name; unique per workspace (case-insensitive),\nsurrounding whitespace is trimmed.\n"},"monthly_budget":{"type":"number","format":"double","minimum":0,"maximum":1000000000,"multipleOf":0.01,"description":"Edits the project's MONTHLY spend limit in US dollars: omit to\nleave it unchanged, send a positive value to set or change it, or\nan explicit `0` to remove it. Amounts are whole cents written as a\nplain decimal; a finer value, or exponent notation, is refused\nrather than rounded. Requires the\n`billing.manage`\npermission (owners/admins), like the workspace budget \u2014 a\nspend ceiling is a billing control, not a grouping edit. Once the\nproject's billed spend within the current calendar month (UTC)\nreaches the limit, new billable work attributed to that project is\nrefused with the coded `402 project_spend_limit_exceeded` until\nthe month resets or the limit is raised.\n"}},"description":"Edits a project's display name, its monthly spend limit, and/or its\ncapacity ceilings; at least one field must be present. The project\nid is immutable, so a rename never re-points the resources grouped\nunder it. The limit fields are merge-patched: omit one to leave it\nunchanged; the capacity ceilings clear on an explicit `null`, the\nspend limit on `0`.\n","title":"UpdateProjectRequest"},"DeleteProjectRequestMode":{"type":"string","enum":["detach","purge"],"default":"detach","description":"`detach` removes the grouping row only; `purge` removes the\nproject with its contents.\n","title":"DeleteProjectRequestMode"},"DeleteProjectRequest":{"type":"object","properties":{"mode":{"$ref":"#/components/schemas/DeleteProjectRequestMode","default":"detach","description":"`detach` removes the grouping row only; `purge` removes the\nproject with its contents.\n"},"confirm":{"type":"string","description":"Required for `purge`: the project's name, exactly as returned by\nGET. A mismatch answers 400 `validation_failed` naming this field.\n"}},"description":"Optional body for DELETE /v1/projects/{project_id}. Omit it, or send\n`mode: detach`, for the default delete that keeps every resource and\nmoves it to the Default project. Send `mode: purge` with `confirm`\nequal to the project's exact name to remove the project with its\ncontents.\n","title":"DeleteProjectRequest"},"ProjectRestoredCounts":{"type":"object","properties":{"agents":{"type":"integer"},"agent_tests":{"type":"integer"},"knowledge_bases":{"type":"integer"},"tools":{"type":"integer"},"audio_assets":{"type":"integer"}},"description":"What a restore brought back, by kind. Every field is a real count,\nincluding zero.\n","title":"ProjectRestoredCounts"},"ProjectStillRevokedCounts":{"type":"object","properties":{"api_keys":{"type":"integer"},"service_accounts":{"type":"integer"},"credentials":{"type":"integer"},"webhook_endpoints":{"type":"integer"}},"description":"What the restore deliberately left revoked: the credentials the purge\nkilled. Every field is a real count, including zero.\n","title":"ProjectStillRevokedCounts"},"ProjectRestore":{"type":"object","properties":{"id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"purged_at":{"type":"string","format":"date-time","description":"When the purge this call walked back happened."},"archived_at":{"type":["string","null"],"format":"date-time","description":"When the project was archived. Always set on a restore: a project\ncomes back suspended, never live.\n"},"restored":{"$ref":"#/components/schemas/ProjectRestoredCounts"},"still_revoked":{"$ref":"#/components/schemas/ProjectStillRevokedCounts"},"not_restored_note":{"type":"string","description":"A plain-language statement of what a restore does not bring back.\nConstant text, meant to be shown to a person; branch on\n`still_revoked` rather than parsing it.\n"}},"description":"The outcome of restoring a purged project: what came back, and what\ndeliberately did not.\n\n`restored` counts the resources the purge removed and this call\nbrought back. `still_revoked` counts the credentials the purge revoked\nand this call left revoked, because re-issuing a credential someone\nkilled would hand back access rather than restore a project. Member\ngrants and pending-invite scopes are in that second group too, but\ntheir rows were removed by the purge, so they cannot be counted and\nappear only in `not_restored_note`.\n\nThe project comes back ARCHIVED, the state it was purged from, so\n`archived_at` is always set: nothing dispatches or bills inside it\nuntil you unarchive it on purpose.\n","title":"ProjectRestore"},"ProjectTeardownBlockerKind":{"type":"string","enum":["api_key","service_account","credential","webhook_endpoint","project_member","invite","phone_number"],"title":"ProjectTeardownBlockerKind"},"ProjectTeardownBlockerBlocksItems":{"type":"string","enum":["detach","purge"],"title":"ProjectTeardownBlockerBlocksItems"},"ProjectTeardownBlocker":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/ProjectTeardownBlockerKind"},"id":{"type":"string","pattern":"^(?:key|svc|cred|whe|invite|phone)_[0-9a-hjkmnp-tv-z]{26}$|^user_[a-z2-7]+$","description":"The row's prefixed external id. The kind decides which prefix:\n`key_` (API key), `svc_` (service account), `cred_` (vault\ncredential), `whe_` (webhook endpoint), `invite_` (pending\ninvite) and `phone_` (phone number) are Crockford base32 uuidv7\nids, while `user_` (member grant) wraps the workspace user's\nopaque identifier, exactly as the project members surface returns\nit. Read `kind` to know which shape to expect.\n"},"name":{"type":"string"},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTeardownBlockerBlocksItems"},"description":"The delete modes this row refuses."}},"required":["kind","id","name","blocks"],"description":"One row whose scope holds the project open, and the delete modes it\nrefuses. A pinned API key, a service account, a scoped vault\ncredential, a scoped webhook endpoint, a member grant and a pending\ninvite carrying the project refuse a detach (detaching would widen\nthem); a phone number refuses a purge (its release is an external\nside effect); a member grant that is the member's only one, and a\nlive invite whose only project is this one, refuse both (clearing\neither would widen that person to the whole workspace, the invite one\nacceptance earlier).\n","title":"ProjectTeardownBlocker"},"ProjectTeardownGroup":{"type":"object","properties":{"kind":{"type":"string"},"count":{"type":"integer"},"sample":{"type":"array","items":{"type":"string"}}},"required":["kind","count","sample"],"description":"One resource kind a purge would remove or keep: a live count and, for\nremoved kinds, a bounded sample of names. Retained kinds carry no\nsample, because a caller identity or a memory is personal data.\n","title":"ProjectTeardownGroup"},"ProjectTeardown":{"type":"object","properties":{"id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"archived":{"type":"boolean","description":"Whether the project is archived. A purge is only available from\nthe archived state, so this is the precondition to check before\noffering a teardown.\n"},"purge_available":{"type":"boolean","description":"Whether `mode: purge` would be accepted right now: the project is\narchived and nothing blocks a purge. A courtesy read for the UI,\nnever the authority - the delete re-decides both halves under the\nproject's row lock, so a stale `true` still answers 409.\n"},"blocker_count":{"type":"integer","description":"How many rows hold this project open in total. `blockers` lists at\nmost 50 of them, so a project with more says so here; a delete is\nrefused on this number, never on the length of the list.\n"},"blockers":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTeardownBlocker"}},"removes":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTeardownGroup"}},"retains":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTeardownGroup"}}},"required":["id","name","archived","purge_available","blocker_count","blockers","removes","retains"],"description":"What deleting a project would do, in either mode, with nothing\nmutated. `blockers` is a bounded sample (at most 50) of the rows\nholding the project open, with the modes each refuses, and\n`blocker_count` is how many there are in total. `removes` is what a purge deletes or revokes, one\nentry per kind in a fixed order (zero counts included). `retains` is\nthe operational records a purge keeps (a conversation with its frozen\nattribution, the rest moved to the Default project).\n","title":"ProjectTeardown"},"ProjectResourceCounts":{"type":"object","properties":{"agents":{"type":"integer","minimum":0,"description":"Number of agents in the project."},"knowledge_bases":{"type":"integer","minimum":0,"description":"Number of knowledge bases in the project."},"tools":{"type":"integer","minimum":0,"description":"Number of tools in the project."},"phone_numbers":{"type":"integer","minimum":0,"description":"Number of phone numbers in the project."},"audio_assets":{"type":"integer","minimum":0,"description":"Number of audio assets in the project."},"conversations":{"type":"integer","minimum":0,"description":"Number of conversations attributed to the project. Attribution is\nfrozen at the conversation's creation time and does not follow\nits agent to a later project.\n"},"callers":{"type":"integer","minimum":0,"description":"Number of callers attributed to the project, frozen the same way\nas conversations.\n"},"agent_tests":{"type":"integer","minimum":0,"description":"Number of agent tests attributed to the project, frozen the same\nway as conversations.\n"},"batch_calls":{"type":"integer","minimum":0,"description":"Number of batch calls attributed to the project, frozen the same\nway as conversations.\n"},"agent_test_suite_runs":{"type":"integer","minimum":0,"description":"Number of agent test suite runs attributed to the project, frozen\nthe same way as conversations.\n"},"memories":{"type":"integer","minimum":0,"description":"Number of live caller memories attributed to the project, frozen\nthe same way as conversations. A count only; the extracted facts\nthemselves are never returned here.\n"}},"required":["agents","knowledge_bases","tools","phone_numbers","audio_assets","conversations","callers","agent_tests","batch_calls","agent_test_suite_runs","memories"],"description":"Live counts of every resource kind grouped under one project. Each\ncount is zero, never absent, when the project holds none of that\nkind.\n","title":"ProjectResourceCounts"},"ProjectStats":{"type":"object","properties":{"resource_counts":{"$ref":"#/components/schemas/ProjectResourceCounts"}},"required":["resource_counts"],"description":"What a project contains, as live resource counts.\n\nCredentials pinned to the project are deliberately not included.\nListing API keys and service accounts is an account-level operation\nwith its own authentication, so use those endpoints directly rather\nthan reading credential names through a project read.\n","title":"ProjectStats"},"PromoteProjectRequest":{"type":"object","properties":{"target_project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project to copy into. Must differ from the source and belong to the same workspace."}},"required":["target_project_id"],"title":"PromoteProjectRequest"},"PromoteCreated":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"}},"tools":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_bases":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"audio_assets":{"type":"array","items":{"type":"string","pattern":"^audio_[0-9a-hjkmnp-tv-z]{26}$"}},"webhook_endpoints":{"type":"array","items":{"type":"string","pattern":"^whe_[0-9a-hjkmnp-tv-z]{26}$"}},"agent_tests":{"type":"array","items":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$"}},"import_jobs":{"type":"array","items":{"type":"string","pattern":"^import_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The URL re-imports queued in the target's knowledge bases; poll each for completion."}},"required":["agents","tools","knowledge_bases","audio_assets","webhook_endpoints","agent_tests","import_jobs"],"description":"Ids minted in the target, by kind. Each list is empty, never absent, when nothing of that kind was copied.","title":"PromoteCreated"},"PromoteReused":{"type":"object","properties":{"tools":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}}},"required":["tools"],"description":"Target resources bound instead of copied because an identical definition already existed there.","title":"PromoteReused"},"PromoteAttentionKind":{"type":"string","enum":["agent","tool","knowledge_base","audio_asset","webhook_endpoint"],"description":"The kind of resource the entry names.","title":"PromoteAttentionKind"},"PromoteAttentionReason":{"type":"string","enum":["credential_not_attached","webhook_secret_reminted","webhook_secret_not_copied","renamed","phone_number_not_copied","documents_not_copied","import_not_requeued","audio_asset_not_copied"],"description":"Stable code a client can branch on. `credential_not_attached`: the\nsource referenced a vault credential, which never travels.\n`webhook_secret_reminted`: a fresh signing secret was minted; rotate\nto read it. `webhook_secret_not_copied`: the agent's customer-set\npost-call secret was cleared. `renamed`: the copy took a suffixed\nname. `phone_number_not_copied`: the source agent had numbers bound.\n`documents_not_copied`: the knowledge base held uploaded or pasted\ndocuments. `import_not_requeued`: URL sources could not be queued.\n`audio_asset_not_copied`: no asset storage is configured here.\n","title":"PromoteAttentionReason"},"PromoteAttention":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/PromoteAttentionKind","description":"The kind of resource the entry names."},"id":{"type":"string","pattern":"^[a-z][a-z_]*_[0-9a-hjkmnp-tv-z]{26}$","description":"The resource's prefixed external id; a target id except for `audio_asset_not_copied`, which names the source asset."},"reason":{"$ref":"#/components/schemas/PromoteAttentionReason","description":"Stable code a client can branch on. `credential_not_attached`: the\nsource referenced a vault credential, which never travels.\n`webhook_secret_reminted`: a fresh signing secret was minted; rotate\nto read it. `webhook_secret_not_copied`: the agent's customer-set\npost-call secret was cleared. `renamed`: the copy took a suffixed\nname. `phone_number_not_copied`: the source agent had numbers bound.\n`documents_not_copied`: the knowledge base held uploaded or pasted\ndocuments. `import_not_requeued`: URL sources could not be queued.\n`audio_asset_not_copied`: no asset storage is configured here.\n"},"message":{"type":"string","description":"Human-readable detail for the entry."}},"required":["kind","id","reason","message"],"description":"One thing the promote could not finish on its own.","title":"PromoteAttention"},"PromoteProjectResponse":{"type":"object","properties":{"created":{"$ref":"#/components/schemas/PromoteCreated"},"reused":{"$ref":"#/components/schemas/PromoteReused"},"needs_attention":{"type":"array","items":{"$ref":"#/components/schemas/PromoteAttention"}}},"required":["created","reused","needs_attention"],"description":"The manifest of one promote. Every id is a resource created in the\ntarget, except `reused`, which names target resources bound instead of\ncopied. `needs_attention` is what a person must finish by hand.\n","title":"PromoteProjectResponse"},"ProjectAuditEventType":{"type":"string","enum":["access_granted","access_revoked","access_widened_to_workspace","archived","unarchived"],"description":"What changed. `access_widened_to_workspace` is the revoke that\nremoved the subject's LAST grant anywhere: a member with no grants\nreaches the whole workspace, so that revoke widened them rather\nthan narrowing them.\n","title":"ProjectAuditEventType"},"ProjectAuditEvent":{"type":"object","properties":{"time":{"type":"string","format":"date-time","description":"When the change was recorded."},"type":{"$ref":"#/components/schemas/ProjectAuditEventType","description":"What changed. `access_widened_to_workspace` is the revoke that\nremoved the subject's LAST grant anywhere: a member with no grants\nreaches the whole workspace, so that revoke widened them rather\nthan narrowing them.\n"},"subject_user_id":{"type":["string","null"],"pattern":"^user_[a-z2-7]+$","description":"The member whose access changed. Null on a lifecycle change, which is done to the project rather than to a person."},"actor_user_id":{"type":["string","null"],"pattern":"^user_[a-z2-7]+$","description":"The member who made the change. Null when the change was system-initiated."},"actor_email":{"type":["string","null"],"description":"The actor's email at the time of the change. Null when the change carried no email."},"actor_role":{"type":["string","null"],"description":"The workspace role the actor held at the time. A role read back today cannot answer what they held when they acted. Null when the change carried no role."},"impersonator_email":{"type":["string","null"],"description":"The Speechify support admin acting on the workspace's behalf. Null unless the change came through support."},"remaining_grants":{"type":["integer","null"],"description":"How many projects the subject held after the change. Null on a lifecycle change; zero is what makes a revoke a widening to the whole workspace."}},"required":["time","type"],"description":"One change to this project's access or lifecycle state. The subject is\nwhose access changed; the actor is who changed it.\n\nEvery field below is always present. A field the event did not carry is\n`null`, which is a different fact from the key being absent altogether:\n`null` means this event had no such party, an absent key would mean the\nentry predates the platform recording that party at all. Audit entries\nare never backfilled, so the two are kept distinct.\n","title":"ProjectAuditEvent"},"ProjectAuditResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"events":{"type":"array","items":{"$ref":"#/components/schemas/ProjectAuditEvent"}}},"required":["next_cursor","has_more","events"],"description":"One page of a project's audit trail, newest change first.","title":"ProjectAuditResponse"},"ProjectMember":{"type":"object","properties":{"user_id":{"type":"string","pattern":"^user_[a-z2-7]+$","description":"The member's prefixed user id, joinable to the workspace members list."},"created_at":{"type":"string","format":"date-time"},"granted_by":{"type":"string","pattern":"^user_[a-z2-7]+$","description":"The prefixed user id of whoever made the grant. Attribution only."}},"required":["user_id","created_at","granted_by"],"description":"One project grant - a workspace member who may reach this project.","title":"ProjectMember"},"ProjectMembersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"members":{"type":"array","items":{"$ref":"#/components/schemas/ProjectMember"}}},"required":["next_cursor","has_more","members"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ProjectMembersResponse"},"GrantProjectMemberRequest":{"type":"object","properties":{"user_id":{"type":"string","pattern":"^user_[a-z2-7]+$","description":"The prefixed user id of the workspace member to grant."}},"required":["user_id"],"title":"GrantProjectMemberRequest"}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'."}}}}
+{"openapi":"3.1.0","info":{"title":"API Reference","version":"1.0.0"},"paths":{"/v1/audio/speech":{"post":{"operationId":"speech","summary":"Create Speech","description":"Synthesize speech audio from text or SSML. Returns the complete audio\nfile plus billing and speech-mark metadata in a single JSON response.\nFor low-latency playback or long-form text, use POST /v1/audio/stream.\nSet `output_format` for explicit sample-rate/bitrate control (e.g.\n`pcm_16000` or `ulaw_8000` for telephony).","tags":["audio"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Synthesized speech audio for the requested input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSpeechResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSpeechRequest"}}}}}},"/v1/audio/stream":{"post":{"operationId":"stream","summary":"Stream Speech","description":"Synthesize speech and stream the audio back as it is generated, for\nlow-latency playback. Set `output_format` in the body for explicit\ncodec/sample-rate/bitrate control (e.g. `pcm_16000` or `ulaw_8000` for\ntelephony), or fall back to the Accept header for the container; the\nresponse is raw audio bytes (HTTP chunked). For Base64-encoded audio\nwith speech-mark metadata in a single JSON response, use\nPOST /v1/audio/speech.","tags":["audio"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"Selects the audio container/codec for the streamed response when\n`output_format` is not set in the request body. The response\nContent-Type echoes this value, except `audio/pcm` returns\n`audio/L16` with rate and channels parameters (raw 16-bit linear\nPCM, 24 kHz mono, little-endian). For explicit sample-rate/bitrate\ncontrol (e.g. `pcm_16000`, `ulaw_8000`), set `output_format` in the\nbody instead; it takes precedence over this header.","required":false,"schema":{"$ref":"#/components/schemas/V1AudioStreamPostParametersAccept"}}],"responses":{"200":{"description":"Streamed audio. When `output_format` is set it selects the\ncodec/sample rate; otherwise the Accept header does. The\nContent-Type reflects the selected format: it matches the Accept\nheader, except raw PCM returns `audio/L16` (with rate and channels\nparameters) and u-law returns `audio/basic`.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetStreamRequest"}}}}}},"/v1/audio/stream/with-timestamps":{"post":{"operationId":"streamWithTimestamps","summary":"Stream Speech With Timestamps","description":"Synthesize speech and stream it back together with word-level speech\nmarks, for text highlighting, captions and audio-text synchronization\nwhile the audio is still arriving.\n\nThe response is a Server-Sent Events stream. Each `speech.chunk` event\ncarries a Base64-encoded run of audio, the speech marks that became\nfinal with it, or both - a chunk may carry only one of the two, and the\nlast chunk of a stream is often marks-only. A terminal `speech.done`\nevent ends the stream; there is no `[DONE]` sentinel. Ignore any event\ntype you do not recognize, so that new event types do not break your\nintegration.\n\nSpeech-mark times are absolute milliseconds from the start of the\nsynthesis, so concatenate the audio chunks into one stream and apply the\nmarks against that single timeline. Which chunk a mark arrives on is a\ndelivery detail and carries no meaning. Times stay correct for every\n`output_format`: changing the codec or sample rate does not change the\nduration.\n\nSpeech marks are produced by the streaming-native models. The default\n`simba-3.0` and `simba-3.2` both serve this route. The legacy\n`simba-english` and `simba-multilingual` models never could: on a\nworkspace pinned before API version `2026-09-21` they return 400\n`speech_marks_unsupported` here, and from that version on they return\n400 `model_retired` on every synthesis route. Both are switched off\nentirely on 2026-11-21.\nFor Base64-encoded audio and speech marks in one non-streamed JSON\nresponse, on any model, use POST /v1/audio/speech.","tags":["audio"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Accept","in":"header","description":"Selects the audio container/codec carried inside the events when\n`output_format` is not set in the request body. The selected media\ntype is echoed on the `Speechify-Audio-Content-Type` response\nheader, since the response's own Content-Type is `text/event-stream`.","required":false,"schema":{"$ref":"#/components/schemas/V1AudioStreamWithTimestampsPostParametersAccept"}}],"responses":{"200":{"description":"A Server-Sent Events stream of `speech.chunk` events followed by one\nterminal `speech.done` event. A failure after the stream has started\nis delivered as a `speech.error` event carrying the standard error\nenvelope, because the status code is already committed.\n\nThe transport is `text/event-stream`: each event is an\n`event:`/`data:` pair whose `data` is one JSON payload matching the\nschema below. The payload's `type` field mirrors the `event:` name,\nso the stream is also parseable from `data:` lines alone. Ignore\nevent types you do not recognize.","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/SpeechStreamEvent"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetStreamRequest"}}}}}},"/v1/voices":{"get":{"operationId":"list","summary":"List Voices","description":"Lists the voices available to the caller - the shared voice\ncatalog plus the cloned voices they can reach, whichever member or\nservice-account key created them. A clone filed under a project is\nlisted only for a caller who can reach that project; a clone no\nproject filed is shared with the whole workspace and is listed for\neveryone in it. By default\nthe full catalogue is returned in one response. Pagination is\nopt-in: pass `limit` (and then `cursor` from the previous\nresponse) to page through the list while `has_more` is true. Max\npage size is 200. Narrow the list with the `type` and `locale`\nfilters.\n\nA page can come back with fewer than `limit` voices, and a short\npage - an empty one included - is not the end of the list. Keep\nfollowing `next_cursor` while `has_more` is true.","tags":["voices"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"type","in":"query","description":"Filter by voice type: `personal` (the workspace's cloned voices)\nor `shared` (the public catalogue). Omit to return both.","required":false,"schema":{"$ref":"#/components/schemas/V1VoicesGetParametersType"}},{"name":"locale","in":"query","description":"Filter to voices whose locale matches this BCP-47 language range,\nprefix-matched: `en` matches `en-US` and `en-GB`; `en-US` matches\nonly `en-US`. Case-insensitive. Omit to return all locales.","required":false,"schema":{"type":"string"}},{"name":"gender","in":"query","description":"Filter by voice gender. Omit to return all genders.","required":false,"schema":{"$ref":"#/components/schemas/V1VoicesGetParametersGender"}},{"name":"model","in":"query","description":"Filter to voices that support this model (as listed in each voice's\n`models[]`), e.g. `simba-3.2`. Omit to return voices for all models.","required":false,"schema":{"type":"string"}},{"name":"project_id","in":"query","description":"Filter cloned voices by workspace project: omit for every voice you\ncan reach, pass the literal `shared` for the clones no project\nfiled, or a `proj_...` id for the clones filed under that project.\nThe shared catalog carries no project and is returned either way.\n\nA clone is filed under a project when a project-pinned key created\nit. A clone with no project is shared with the whole workspace\nrather than sitting in a Default project, so the literal here is\n`shared`, never `default` - passing `default` is a 400. Returns 404\nproject_not_found for a malformed id and for any project outside\nyour reach: a project-pinned key reaches only its pinned project,\nand a member holding project grants reaches only the granted ones.\nThat 404 is the same in every case and does not reveal whether such\na project exists. `shared` is always inside your reach.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The voice catalogue (or a page of it when `limit` is set).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListVoicesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Voice","description":"Create a cloned voice for the workspace from a 10-30 second audio sample, with verified consent from the speaker.\n\nCloning requires proof that the speaker agreed to it. Create a consent challenge with `POST /v1/voices/consent-challenges`, show the returned `phrase` to the speaker, record them reading it aloud, and send that recording here as `consent_recording` together with the challenge's `consent_challenge_id`. Speechify transcribes the recording, checks it against the phrase it issued, checks that its speaker is the speaker in your `sample`, and keeps it as the consent record for the voice. The person consenting therefore has to be the person being cloned. A challenge is single use and short-lived, so record and submit in one sitting.\n\nThe clone belongs to the workspace rather than the member who created it, and access follows the caller's workspace role and API-key scopes exactly as for any other voice: voices scopes to list it, audio scopes to synthesize with it, and the content-management permission plus a write scope on the key to delete it. Cloned voices are usable self-serve on `simba-3.0` (and, on a workspace pinned before API version `2026-09-21`, on the retired `simba-english` and `simba-multilingual` until they are switched off on 2026-11-21). `simba-3.2` also serves cloned voices.\n\nCallers pinned before `Speechify-Version: 2026-09-13` use the previous flow instead: no challenge, and a `consent` form field carrying the speaker's name and email as a JSON string. That flow is deprecated and will be removed after a sunset window announced in the changelog.","tags":["voices"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"A created voice","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetVoice"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Name of the personal voice"},"locale":{"type":"string","default":"en-US","description":"Native language (locale) of the personal voice (e.g. en-US, es-ES, etc.)"},"gender":{"$ref":"#/components/schemas/V1VoicesPostRequestBodyContentMultipartFormDataSchemaGender","description":"Gender marker for the personal voice\nmale GenderMale\nfemale GenderFemale\nnot_specified GenderNotSpecified"},"sample":{"type":"string","format":"binary","description":"Audio sample of the voice to clone, 10-30 seconds of clean speech."},"avatar":{"type":"string","format":"binary","description":"Avatar image file"},"consent_challenge_id":{"type":"string","description":"The `id` of the consent challenge this create consumes, from\n`POST /v1/voices/consent-challenges`. Single use: once a\ncreate has consumed it, whether or not that create\nsucceeded, it cannot be used again."},"consent_recording":{"type":"string","format":"binary","description":"Recording of the speaker reading the challenge's `phrase`\naloud. This is the consent record for the voice, not a\nsecond voice sample: it must be the same person as in\n`sample`, and it is retained as evidence. 5-30 seconds, at\nmost 25 MB, in any common audio container."}},"required":["name","gender","sample","consent_challenge_id","consent_recording"]}}}}}},"/v1/voices/consent-challenges":{"post":{"operationId":"create","summary":"Create Consent Challenge","description":"Start the consent check for a voice clone.\n\nReturns a `phrase` for the speaker to read aloud and an `id` that identifies this challenge. Show the phrase to the speaker exactly as returned, record them reading it, then send the recording and the `id` to `POST /v1/voices`, which verifies the recording against the phrase and against the voice sample being cloned, then keeps it as the consent record.\n\nA challenge is single use, is bound to the workspace that created it, and expires at `expires_at` - it is proof that a speaker was in front of a microphone just now, so create it when you are ready to record, not at the start of your flow. If it expires, create another one and record again.\n\nChallenge creation is rate limited per workspace at a few dozen per hour, far more tightly than the rest of the voice surface, because each one precedes a person recording themselves - mint it when your speaker is ready, not speculatively. Read the live ceiling off `RateLimit-*` rather than hard-coding it. **On a `429`, always honour `Retry-After` rather than a fixed backoff of your own**: the wait is measured in minutes and can run to most of an hour. `RateLimit-*` are omitted rather than reporting a bucket that is not the one refusing.","tags":["consentChallenges"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created consent challenge.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentChallenge"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConsentChallengeRequest"}}}}}},"/v1/voices/{voice_id}":{"get":{"operationId":"get","summary":"Get Voice","description":"Fetch a single voice by id - a shared catalogue voice or one of\nthe workspace's cloned voices. A cloned voice that belongs to\nanother workspace returns 404, identical to an unknown id, so\nvoice inventory is never enumerable across tenants.","tags":["voices"],"parameters":[{"name":"voice_id","in":"path","description":"The ID of the voice to fetch","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The voice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetVoice"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete","summary":"Delete Voice","description":"Delete one of the workspace's cloned voices. Requires the\n`content.manage` permission (owner, admin, or member); a\nservice-account key is authorized by its scopes instead.","tags":["voices"],"parameters":[{"name":"voice_id","in":"path","description":"The ID of the voice to delete","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Voice deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/voices/{voice_id}/sample":{"get":{"operationId":"downloadSample","summary":"Download Voice Sample","description":"Download a personal (cloned) voice sample","tags":["voices"],"parameters":[{"name":"voice_id","in":"path","description":"The ID of the voice to download sample for","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Voice sample audio file","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audio/models":{"get":{"operationId":"list","summary":"List Models","description":"List the text-to-speech models available for synthesis. Drive a model\npicker from this response, then pass a model `id` as the `model`\nparameter to POST /v1/audio/speech or /v1/audio/stream. The response\nmarks the default model (used when a request omits `model`), the\nroutes each model may be passed to, and which voices it accepts.\nMulti-speaker models arrive in a separate `dialogue_models` array\nbecause they are valid only on POST /v1/audio/dialogue. Returns\nthe full set in a single response: the model catalog is static\nplatform reference data, so it is intentionally not paginated.","tags":["models"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The available text-to-speech models.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audio/watermark/detect":{"post":{"operationId":"detect","summary":"Detect Watermark","description":"Check whether a clip carries the watermark Speechify seals into audio it\ngenerates. Upload the audio as `audio`; nothing about it is stored, and\nno voice is read or written.\n\nRead the answer carefully in one direction. A `watermarked: true` is\npositive evidence that the audio came from Speechify synthesis. A\n`watermarked: false` is NOT proof that it did not: only models\nredeployed since the watermark shipped mark their output, the detector\nneeds at least three seconds of clear speech to judge, and re-encoding\nor changing the speed of a clip degrades the mark. Treat a negative as\nthe absence of evidence rather than as evidence of absence.\n\nChecks are rate-limited well below the synthesis budget: this is a\nforensic question, not a data-plane call.","tags":["watermark"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The clip was checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatermarkDetectionResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"audio":{"type":"string","format":"binary","description":"The clip to check, at most 25MB. Give the detector at least\nthree seconds of clear speech; below that its confidence is\nnot worth acting on, and below half a second it always\nreports zero."}},"required":["audio"]}}}}}},"/v1/audio/watermark/verify":{"post":{"operationId":"verify","summary":"Verify Watermark","description":"The public AI detection tool. Ask whether a clip carries the watermark\nSpeechify seals into audio it generates, with no account, no API key and\nno credential of any kind.\n\n`verify` answers; `detect` measures. This route returns a bare yes or no,\nthe way verifying a signature does. Its sibling\n`POST /v1/audio/watermark/detect` takes an API key and returns the\ndetector's confidence alongside the verdict.\n\nThis is the programmatic half of the tool published at\n, and it exists so the tool can be invoked\nwithout visiting our website, as California's AI Transparency Act\n(BPC 22757.2) requires. Nothing about the clip is stored, and nothing\nidentifying about you is collected or retained.\n\nThe answer is a bare verdict. `watermarked: true` is positive evidence\nthat the audio came from Speechify synthesis. `watermarked: false` is\nNOT proof that it did not: only models redeployed since the watermark\nshipped mark their output, the detector needs at least three seconds of\nclear speech to judge, and re-encoding or changing the speed of a clip\ndegrades the mark. Treat a negative as the absence of evidence rather\nthan as evidence of absence.\n\nBecause the tool takes no credential, it is rate-limited per client\naddress and shares a platform-wide budget: expect a 429 under sustained\nautomated use, and retry after the interval the response advertises.\nUse `POST /v1/audio/watermark/detect` with an API key for the detector's\nconfidence score and a per-workspace allowance of its own.","tags":["watermark"],"parameters":[{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The clip was checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatermarkVerificationResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"audio":{"type":"string","format":"binary","description":"The clip to check, at most 25MB. Give the detector at least\nthree seconds of clear speech; below that its answer is not\nworth acting on."}},"required":["audio"]}}}}}},"/v1/agents":{"get":{"operationId":"list","summary":"List Agents","description":"List voice agents owned by the caller. Cursor-paginated: omit\n`cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200).","tags":["agent"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of voice agents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Agent","description":"Create a voice agent.\n\nThe agent lands in the project named by `project_id`, or in the\nimplicit Default project when none is given. Each project holds at\nmost a fixed number of live agents; a create past that answers\n`409 agent_limit_reached` naming the ceiling, and deleting an agent\nin that project - or creating this one in another - frees a slot.\n","tags":["agent"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentRequest"}}}}}},"/v1/agents/voices":{"get":{"operationId":"listAgentVoices","summary":"List Agent Voices","description":"List the voice catalogue available for voice agents: the curated\nshared catalogue plus your workspace's own cloned voices, each\nmarked by `type` (`shared` or `personal`). The same slug set is\naccepted by POST/PATCH /v1/agents, so any voice listed here can be\nassigned to an agent. Cloned voices are workspace-owned only - a\npersonal voice scoped to an individual is not selectable on an\nagent, which is a workspace-shared resource. The JSON layout\nintentionally mirrors the TTS `/v1/voices` shape so a single voice\npicker can consume both endpoints.\n\nCursor-paginated: pass `cursor` + `limit` and walk pages while\n`has_more` is true. From API version `2026-08-07` an omitted `limit`\nreturns the first page (default 50, max 200); a caller pinned before\nthat date keeps the historical whole-catalogue response until it opts\nin. The shared catalogue is served first, then your cloned voices.\n","tags":["agent"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The curated agent voice catalogue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentVoicesResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tts-models":{"get":{"operationId":"listTTSModels","summary":"List Agent TTS Models","description":"List the Simba text-to-speech models a voice agent can be pinned to\nwith `tts.model`.\n\nThe response is the whole catalog and is the same for every\nworkspace - which model serves an agent is a routing decision, not\nan entitlement, so nothing here is plan-gated. Whether a *particular*\nagent may select an entry depends on two facts this response\npublishes rather than resolving for you, because both change without\nthis list changing:\n\n- `english_only` - the model has no multilingual deployment, so an\n agent that declares `additional_languages` (or whose `language` is\n not English) cannot select it.\n- `requires_voice_curation` - deprecated and always false. No model\n asks a voice for a registration: every training conditions on the\n voice's own prompt audio, so every model is selectable on every\n voice we speak ourselves, cloned voices included.\n\nDrive a model picker from this and the agent's languages and it can\nonly offer combinations POST/PATCH /v1/agents will accept.\nReturns the full set in one response: bounded by the platform's\ncurated model catalog, so it is intentionally not paginated.\n","tags":["agent"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The TTS models selectable for voice agents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTTSModelsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}":{"delete":{"operationId":"delete","summary":"Delete Agent","description":"Delete an agent, and with it everything that belonged only to that\nagent.\n\n**Goes with the agent**, immediately unreadable through every\nendpoint: its runs and their step journals and approvals, its\nconversations and the transcripts, messages and recordings inside\nthem, what it learned about the people it spoke to, its triggers,\nshare links, channel bindings, team memberships and the batch\ncampaigns it dialled. A run that has not settled is asked to stop,\nincluding one parked waiting on a person or on a delegated child. These are removed for good 30 days\nlater; until then the delete can still be walked back by support.\n\n**Survives the agent**, because it belongs to the workspace rather\nthan to this agent: attached tools, knowledge bases, stores and\nskills (only the attachment is dropped), the agent's tests, phone\nnumbers, and the teams it belonged to.\n\n**Kept regardless**, because it is accounting rather than content:\nthe usage the workspace was billed for.\n\nRefused with `409 agent_in_use` while a live phone number still\nroutes to the agent, including the agent a webhook binding\nprovisioned for its number; `used_by` names the numbers. Unbind them\n(`DELETE /v1/agents/{agent_id}/phone-numbers/{phone_number_id}`,\nwhich also clears a webhook binding) and the delete proceeds.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Agent deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A live phone number still routes to the agent. `used_by` names\nthem so the caller can unbind first.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentInUseError"}}}}}},"get":{"operationId":"get","summary":"Get Agent","description":"Retrieve a voice agent by ID.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Agent","description":"Update a voice agent. Only fields present on the request body are changed.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentRequest"}}}}}},"/v1/agents/{agent_id}/widget-config":{"get":{"operationId":"getWidgetConfig","summary":"Get Agent Widget Config","description":"Return the embed-widget appearance config for an agent. Works\nunauthenticated for public agents; the body is cosmetic only.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's widget configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WidgetConfig"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/flow/schema":{"get":{"operationId":"getSchema","summary":"Get Flow Graph Schema","description":"Return the JSON Schema describing the flow graph node taxonomy.\nUnauthenticated; flow editors fetch it to validate graphs client-side.\n","tags":["flow"],"parameters":[{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A JSON Schema document for the flow graph.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/agent_flow_getSchema_Response_200"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/flow":{"get":{"operationId":"getFlow","summary":"Get Agent Flow","description":"Return the agent's flow graph: the current draft (if any), the\nactive published graph (if any), and the version history.\n","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The draft graph, active graph, and version history.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetFlowResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateFlow","summary":"Update Agent Flow Draft","description":"Replace the agent's draft flow graph. The graph is validated\nbefore it is stored; publish it separately to make it active.\n","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The stored draft graph.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowGraph"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutFlowRequest"}}}}}},"/v1/agents/{agent_id}/flow/publish":{"post":{"operationId":"publish","summary":"Publish Agent Flow","description":"Publish the agent's draft graph as a new active flow version.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The newly published flow version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowVersion"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The behavioral-eval publish gate refused the publish\n(`agent_publish_gate_failed`): the agent's configured suite did not\npass. `error.details` carries the verdict (per-case, per-criterion).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The publish checks could not run (`agent_publish_gate_unavailable`):\nthe eval worker, the judge or the probe corpus errored, so nothing\nabout the agent was judged. Not a verdict on the configuration; retry\nafter `Retry-After`. `error.details` lists the cases that could not\nrun.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishFlowRequest"}}}}}},"/v1/agents/{agent_id}/flow/rollback":{"post":{"operationId":"rollback","summary":"Roll Back Agent Flow","description":"Publish a prior flow version as the active graph.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The flow version that is now active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowVersion"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The behavioral-eval publish gate refused the rollback\n(`agent_publish_gate_failed`): the target version did not pass the\nagent's configured suite. `error.details` carries the verdict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The gate's checks could not run on the rollback target\n(`agent_publish_gate_unavailable`): the eval worker, the judge or\nthe probe corpus errored, so nothing about the version was judged.\nNot a verdict on it; retry after `Retry-After`. `error.details`\nlists the cases that could not run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RollbackFlowRequest"}}}}}},"/v1/agents/{agent_id}/flow/deactivate":{"post":{"operationId":"deactivate","summary":"Deactivate Agent Flow","description":"Deactivate the agent's published flow so the agent runs the synthesized default flow.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Flow deactivated.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/flow/draft":{"delete":{"operationId":"discardDraft","summary":"Discard Agent Flow Draft","description":"Discard the agent's unpublished draft flow graph.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Draft discarded.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/flow/versions":{"get":{"operationId":"listVersions","summary":"List Agent Flow Versions","description":"List every published flow version for the agent, newest first.\nCursor-paginated: omit `cursor` for the first page; walk pages\nwhile `has_more` is true (default page size 50, max 200).","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's flow version history.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListFlowVersionsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/flow/versions/{version_id}":{"get":{"operationId":"getVersion","summary":"Get Agent Flow Version","description":"Return the full flow graph for a specific published version.","tags":["flow"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"version_id","in":"path","description":"Prefixed flow version id (`fver_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested flow version's graph.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowGraph"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/flow/templates":{"get":{"operationId":"listTemplates","summary":"List Flow Templates","description":"List the reusable flow templates available to the workspace.\nCursor-paginated: omit `cursor` for the first page; walk pages\nwhile `has_more` is true (default page size 50, max 200).","tags":["flow"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The available flow templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListFlowTemplatesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createTemplate","summary":"Create Flow Template","description":"Create a reusable flow template from a graph.","tags":["flow"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created flow template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowTemplate"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFlowTemplateRequest"}}}}}},"/v1/agents/flow/templates/{flow_template_id}":{"get":{"operationId":"getTemplate","summary":"Get Flow Template","description":"Retrieve a flow template by id.","tags":["flow"],"parameters":[{"name":"flow_template_id","in":"path","description":"Flow template id (prefixed external id, `tmpl_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested flow template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowTemplate"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateTemplate","summary":"Update Flow Template","description":"Replace a flow template. The whole template is replaced, not patched field-by-field.","tags":["flow"],"parameters":[{"name":"flow_template_id","in":"path","description":"Flow template id (prefixed external id, `tmpl_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated flow template.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowTemplate"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFlowTemplateRequest"}}}}},"delete":{"operationId":"deleteTemplate","summary":"Delete Flow Template","description":"Delete a flow template.","tags":["flow"],"parameters":[{"name":"flow_template_id","in":"path","description":"Flow template id (prefixed external id, `tmpl_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Template deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/flow/templates/{flow_template_id}/clone":{"post":{"operationId":"cloneTemplate","summary":"Clone Flow Template","description":"Clone a flow template onto an agent as a new draft graph.","tags":["flow"],"parameters":[{"name":"flow_template_id","in":"path","description":"Flow template id (prefixed external id, `tmpl_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's new draft graph.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowGraph"}}}},"400":{"description":"Flow validation failed (or the request body was undecodable). The\nstandard `Error` envelope plus an `issues` array locating each problem\nfor the editor. See the `FlowValidationError` schema.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowValidationError"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloneFlowTemplateRequest"}}}}}},"/v1/agents/{agent_id}/evaluation-config":{"get":{"operationId":"getEvaluationConfig","summary":"Get Evaluation Config","description":"Retrieve the agent's post-call evaluation criteria + data-collection config.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluation config for the agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationConfig"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateEvaluationConfig","summary":"Update Evaluation Config","description":"Replace the agent's evaluation criteria + data-collection fields.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated evaluation config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationConfig"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEvaluationConfigRequest"}}}}}},"/v1/agents/{agent_id}/variables":{"get":{"operationId":"getDynamicVariables","summary":"Get Dynamic Variables","description":"Retrieve the agent's customer-scope dynamic variables and the read-only\ncatalogue of reserved `system__*` keys. The system variables list is\nprovided so editor UIs can render the reference list without maintaining\na client-side copy of the catalogue.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's variable catalogue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDynamicVariablesResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"updateDynamicVariables","summary":"Update Dynamic Variables","description":"Replace the agent's customer-scope dynamic variable definitions.\nThe supplied list overwrites the stored list wholesale (same\nsemantics as `updateEvaluationConfig`). Pass an empty array to\nclear all variables. Up to 20 variables per agent. Keys must\nmatch `[a-zA-Z0-9_]+` and must not start with the reserved\n`system__` prefix.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated variable catalogue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDynamicVariablesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDynamicVariablesRequest"}}}}}},"/v1/agents/{agent_id}/conversations":{"post":{"operationId":"createConversation","summary":"Create Conversation","description":"Start a new voice conversation with the agent. Returns a realtime\nvoice session + short-lived client token so the caller can\nconnect the audio pipeline directly. The agent is dispatched\nserver-side; no additional client action required.\n\nPass `dynamic_variables` to supply per-session values that override\nthe agent's stored variable defaults for this one conversation.\nKeys in the `system__` namespace are rejected at this boundary.\n\nPass `user_identity` as the stable caller key your application uses\nfor memory and caller correlation. It is kept separate from the\nopaque, per-session realtime participant identity.\n\n`overrides.voice_id` replaces the configured voice for the entire\nconversation, including every configured language the voice can\nserve. The request is rejected when the voice is unavailable or\ncannot cover the agent's configured language set; the rejection\nnames `overrides.voice_id` in the error envelope's `fields` map.\nThe override belongs to this conversation only: if the call is\ntransferred to another agent mid-conversation, that agent speaks\nwith its own configured voice.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created conversation with its realtime session token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationRequest"}}}}}},"/v1/agents/{agent_id}/sessions":{"post":{"operationId":"createSession","summary":"Create Session","description":"Mint a realtime voice session for the given agent. Widget-friendly\ncounterpart to `createConversation` \u2014 same response shape, dual\nauthentication:\n\n* **Authenticated (Bearer)**: works for any agent the caller\n owns. Typical server-to-server flow where the embedding\n site's backend mints a token and hands it to the browser so\n the API key never reaches the client.\n* **Unauthenticated**: works only when `agent.is_public = true`\n AND the request's `Origin` header matches `agent.allowed_origins`\n (or that list is empty). When `agent.hostname_allowlist` is\n non-empty, the `Origin` hostname must additionally be a\n member of that list. Used directly by the\n `` web component.\n\nResponds with the same `CreateConversationResponse` as\n`createConversation`.\n\n`Idempotency-Key` replay is scoped to the authenticated caller, so it\napplies to Bearer requests only: an unauthenticated mint has no scope\nto replay under, and retrying one mints a fresh session.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created session with its realtime token + URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSessionRequest"}}}}}},"/v1/agents/conversations":{"get":{"operationId":"list","summary":"List Conversations","description":"List conversations owned by the caller, ordered by most recent.\nCursor-paginated: omit `cursor` to fetch the first page; pass the\nprevious response's `next_cursor` back to fetch the next page.\nWalk pages while `has_more` is true.\n\n`project_id` matches the project the call was DISPATCHED under, which\nis frozen when the conversation starts. Moving an agent to another\nproject therefore leaves its past calls attributed where they ran, the\nsame way per-project usage reports them.","tags":["conversations"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"agent_id","in":"query","description":"Filter to conversations for this agent.","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter by conversation status.","required":false,"schema":{"$ref":"#/components/schemas/ConversationStatus"}},{"name":"transport","in":"query","description":"Filter by transport.","required":false,"schema":{"$ref":"#/components/schemas/ConversationTransport"}},{"name":"caller_identity","in":"query","description":"Filter by caller identity.","required":false,"schema":{"type":"string"}},{"name":"contact_id","in":"query","description":"Narrow to one person's conversations, whichever agent or channel\nreached them. Prefixed `contact_...` id.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Free-text search across conversation content.","required":false,"schema":{"type":"string"}},{"name":"started_after","in":"query","description":"Only conversations started at or after this RFC 3339 timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"started_before","in":"query","description":"Only conversations started at or before this RFC 3339 timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"duration_min_ms","in":"query","description":"Minimum conversation duration in milliseconds.","required":false,"schema":{"type":"integer"}},{"name":"duration_max_ms","in":"query","description":"Maximum conversation duration in milliseconds.","required":false,"schema":{"type":"integer"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of conversations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConversationsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}":{"get":{"operationId":"get","summary":"Get Conversation","description":"Retrieve a conversation by ID.","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/recent-callees":{"get":{"operationId":"recent_callees","summary":"List recent callees","description":"Distinct phone numbers the caller's workspace has dialled on\noutbound calls, ordered by most recent. Feeds the batch-calls\ncomposer's \"Suggested from history\" surface. Cursor-paginated:\nomit `cursor` to fetch the first page. Default page size is 50\nand max is 200. Walk pages while `has_more` is true.\n","tags":["conversations"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recent callees for the caller's workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListRecentCalleesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/stats":{"get":{"operationId":"stats","summary":"Conversation stats","description":"Aggregated counts and averages over the caller's conversations, scoped\nby the same filters as the list endpoint, `project_id` included.","tags":["conversations"],"parameters":[{"name":"agent_id","in":"query","description":"Filter to conversations for this agent.","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter by conversation status.","required":false,"schema":{"$ref":"#/components/schemas/ConversationStatus"}},{"name":"transport","in":"query","description":"Filter by transport.","required":false,"schema":{"$ref":"#/components/schemas/ConversationTransport"}},{"name":"caller_identity","in":"query","description":"Filter by caller identity.","required":false,"schema":{"type":"string"}},{"name":"contact_id","in":"query","description":"Narrow to one person's conversations, whichever agent or channel\nreached them. Prefixed `contact_...` id.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Free-text search across conversation content.","required":false,"schema":{"type":"string"}},{"name":"started_after","in":"query","description":"Only conversations started at or after this RFC 3339 timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"started_before","in":"query","description":"Only conversations started at or before this RFC 3339 timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"duration_min_ms","in":"query","description":"Minimum conversation duration in milliseconds.","required":false,"schema":{"type":"integer"}},{"name":"duration_max_ms","in":"query","description":"Maximum conversation duration in milliseconds.","required":false,"schema":{"type":"integer"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Stats for the matched conversations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationStats"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/messages":{"get":{"operationId":"listMessages","summary":"List Messages","description":"Retrieve the transcript for a conversation in started_at order\n(oldest first). Cursor-paginated: omit `cursor` to fetch the\nfirst page. Default page size is 50 and max is 200. Walk pages\nwhile `has_more` is true.","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The messages for the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMessagesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/recording":{"get":{"operationId":"streamRecording","summary":"Stream Recording","description":"Proxy the GCS-stored audio recording for a conversation through\nthe Cloud Run service identity. Returns OGG/Opus bytes (LiveKit\nroom-composite egress default). The response is streamed so a\nlong recording does not buffer in memory; `` consumers\ncan seek directly. Only present when the agent had\n`save_audio_recording` enabled at session start.\n","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The recorded audio.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/evaluations":{"get":{"operationId":"listEvaluations","summary":"List Evaluations","description":"Retrieve post-call evaluation results for a conversation.\nCursor-paginated: omit `cursor` for the first page; walk pages while\n`has_more` is true (default page size 50, max 200).","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluations for the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEvaluationsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/memories":{"get":{"operationId":"listMemories","summary":"List Conversation Memories","description":"List memories extracted from a specific conversation.","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Memories written during this conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMemoriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/retrieval-log":{"get":{"operationId":"listRetrievalLog","summary":"List Retrieval Log","description":"Per-conversation retrieval log, newest first \u2014 one row per\n`search_knowledge` invocation made during the call. Each entry\nrecords the query, ranked chunks (denormalised so deletions\ndon't render history unreadable), `limit`, and hit count.\nPowers the Retrieval panel on the conversation detail view.\nCursor-paginated: omit `cursor` to fetch the first page.\nDefault page size is 50 and max is 200. Walk pages while\n`has_more` is true.\n","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The retrieval log entries for the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListRetrievalLogsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/webhook-deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"List Webhook Deliveries","description":"List post-call webhook delivery attempts for a conversation,\nnewest first. Rows appear once the call ends and the post-call\nwebhook has been dispatched to the agent's configured URL. One\nrow per\n`(conversation, webhook-url)`, updated in place across retries.\nEach row includes the exact request payload and signed headers\nSpeechify sent (`request_body`, `request_headers`) and the\nresponse your server returned (`last_status_code`,\n`last_response_body`, `last_response_headers`), so you can verify\nwhat was delivered and debug a failing endpoint.\nCursor-paginated: omit `cursor` to fetch the first page.\nDefault page size is 50 and max is 200. Walk pages while\n`has_more` is true.\n","tags":["conversations"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The post-call webhook deliveries for the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhookDeliveriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/end":{"post":{"operationId":"forceEnd","summary":"Force-end conversation","description":"Force-terminate an ongoing conversation. Idempotent: a\nconversation that has already ended returns 204 the same as a\nsuccessful first-time termination. Requires an `owner` or `admin`\nof the workspace \u2014 the same gate as take-over.\n","tags":["admin"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Conversation ended.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/conversations/{conversation_id}/shadow-token":{"post":{"operationId":"shadowToken","summary":"Mint shadow-call token","description":"Mint a listen-only realtime access token so an authorized observer\ncan join an ongoing voice-agent conversation as a hidden\nparticipant. Any `member`, `admin`, or `owner` of the workspace\nthe conversation belongs to may listen in \u2014 it is read-only\nobservation. The token cannot publish audio or data; the observer\nis invisible to the caller and the agent. Speechify support\nengineers reach this endpoint the same way as any other observer\n\u2014 by being granted a role on the customer's workspace (typically\nunder an NDA-backed support arrangement).\n","tags":["admin"],"parameters":[{"name":"conversation_id","in":"path","description":"Conversation id (prefixed external id, `conv_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Shadow-call connection details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShadowConversationResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/publish":{"post":{"operationId":"publish","summary":"Publish Agent For Runs","description":"Publish an agent's current configuration for durable runs.\n\nThe configuration a run executes is live as soon as it is written, so\nthis promotes nothing. What a publish does is submit the configuration\nto the workspace's publish gates and record the decision against a\nfingerprint of that configuration. On a gated workspace a run will only\nstart against a configuration that carries a passing (or overridden)\ndecision, so this is the step that makes an agent runnable after it is\ncreated or edited.\n\nAny agent publishes here. The conversations it answers are versioned\nseparately, by promoting a flow version via\n`/v1/agents/{agent_id}/flow/publish`.\n\nOn an ungated workspace this succeeds and returns no `gate` object.\n\nThis endpoint is in beta: the gate runs on workspaces granted\n`publish_eval_gate` **or** `red_team_publish_gate`, and the second is\nenabled automatically for every workspace granted `durable_runs_access`\n- so if you can start durable runs, you are gated and this call is a\nprerequisite, not an option. The runs a publish authorises need\n`durable_runs_access` too (the run endpoints answer\n`402 durable_runs_not_in_plan` without it).","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"The published configuration, with the gate outcome when gated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishAgentResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The publish gate refused the publish (`agent_publish_gate_failed`):\nthe configuration did not pass its behavioral and/or adversarial\nred-team checks. `error.details` carries the verdict (per-case,\nper-criterion). Correct the agent and publish again, or publish with a\nrecorded `gate_override_reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The publish checks could not run (`agent_publish_gate_unavailable`):\nthe eval worker, the judge or the probe corpus errored, so nothing\nabout the agent was judged. Not a verdict on the configuration; retry\nafter `Retry-After`. `error.details` lists the probes that could not\nrun.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishAgentRequest"}}}}}},"/v1/agents/{agent_id}/publish-gate/latest":{"get":{"operationId":"latest","summary":"Get Latest Publish Gate Run","description":"Return the agent's most recent publish-gate decision, or `null` when it has\nnever been gated. This is the durable answer to \"are this agent's checks\npassing, and when were they last run\", without republishing it.\n\nThis endpoint is in beta: a decision exists on workspaces granted\n`publish_eval_gate` or `red_team_publish_gate` - the second is enabled\nautomatically alongside `durable_runs_access`, so any workspace that can\nstart durable runs records decisions here. Every other workspace reads\n`null`.","tags":["publishGate"],"parameters":[{"name":"agent_id","in":"path","description":"Prefixed agent id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The latest gate decision, or null if the agent was never gated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LatestPublishGateRunResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected server-side error occurred. Safe to retry with\nexponential backoff for idempotent requests.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs":{"post":{"operationId":"createRun","summary":"Create Agent Run","description":"Enqueue a **durable async run**: hand the agent an instruction and get a\nrun handle back immediately (`202`, status `queued`). The run executes on\nits own - poll it with `getRun` or receive a completion webhook - and it\nsurvives a deploy (it is backed by a durable job, not a request socket or\na short-lived store). This is the managed-agent-platform primitive: start\nwork, walk away, come back for the result.\n\nIdempotent via `Idempotency-Key`: a retry replays the first run instead of\nstarting a duplicate (the `run_id` is the idempotency handle).\n\n## Publish the agent first\n\n**Every workspace granted `durable_runs_access` is publish-gated**, so\nthis is the first thing a new integration hits. Call\n`POST /v1/agents/{agent_id}/publish` before the first run, and again\nafter any change to the agent's configuration: the gate is keyed to a\nfingerprint of that configuration, so an edited agent stops running\nuntil it is republished. Until it passes, this endpoint returns\n`422 agent_publish_gate_required`.\n\nRefused with `422 tool_transport_unsupported` when one of the agent's\nattached MCP tools uses the legacy `sse` transport, which durable runs\ncannot execute; the message names the tool. Switch it to\n`http_streamable` or detach it, then retry.\n\nReturns `429 concurrency_limit_reached` when the workspace already has\n200 runs queued or running, and the same code when the run's PROJECT is\nat its own `max_concurrent_runs` ceiling - only the message says which\nbit, so one retry path handles both. Runs execute on a shared queue, so\nthe workspace ceiling is what keeps one workspace's backlog from\ndelaying everyone else's next run; it is not a plan limit. The project\nceiling narrows it further, which is how an application keeps one of its\ncustomers from occupying every slot the workspace has. `Retry-After` is\na hint at the scale runs take, not a promise - what actually frees a\nslot is one of your own runs ending, so follow the ones you have with\nthe event stream and start the next when one does.\n\n## The project a run bills to\n\nA run is attributed to its AGENT's project, captured at creation and\nfrozen there, so moving the agent later never moves a finished run's\ncost. That project's money gates apply to the run exactly as they apply\nto a call in it: `402 project_spend_limit_exceeded` once its\n`monthly_budget` is reached, and `409 project_archived` while it is\narchived. Both can fire for a workspace-wide key, because the project\ncharged is the agent's rather than the key's pin.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nKeys inside `variables` and `metadata`, and the contents of\n`output_schema`, are your own data rather than field names, and are\nnever refused.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The run was accepted and queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentRunRequest"}}}}},"get":{"operationId":"listRuns","summary":"List Agent Runs","description":"List an agent's runs, newest first.\n\nThe filters are exactly the ones listed here. A query parameter this\nlist does not accept is a 400 naming it, never silently ignored.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Narrow the list to one run status. Omit for every status. A value\noutside the enum is rejected with a 400 naming `status`, rather than\nquietly widening to everything - a filter that ignores a typo is how a\nsurface reports \"nothing waiting\" while a run waits.\n","required":false,"schema":{"$ref":"#/components/schemas/V1AgentsAgentIdRunsGetParametersStatus"}},{"name":"user_identity","in":"query","description":"Narrow the list to runs acting for one person - the `user_identity`\nthey were started with, matched exactly. Omit for every run. A\nscheduled run started by a trigger carrying `user_identity` is found\nhere too.\n","required":false,"schema":{"type":"string","maxLength":256}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's runs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/runs":{"get":{"operationId":"list","summary":"List Runs","description":"List runs across the whole workspace, newest first, optionally narrowed\nto one `status`.\n\nUse this rather than listing per agent when you want to know what is\nhappening overall, and above all what is waiting on a person: pass\n`?status=requires_action`. Listing per agent cannot answer that\nquestion completely, because a run delegated to a team member executes\nunder that member's agent - so an approval that blocks the team's work\nappears under an agent you would not think to open.\n\nRespects `?project_id` like every other workspace-wide list, and\n`?agent_id` narrows it to one agent's runs.\n\nThe filters are exactly the ones listed here. A query parameter this\nlist does not accept is a 400 naming it, never silently ignored: a\nfilter that is accepted and dropped returns the wrong runs with a 200.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"status","in":"query","description":"Narrow the list to one run status. Omit for every status. A value\noutside the enum is rejected with a 400 naming `status`, rather than\nquietly widening to everything - a filter that ignores a typo is how a\nsurface reports \"nothing waiting\" while a run waits.\n","required":false,"schema":{"$ref":"#/components/schemas/V1AgentsRunsGetParametersStatus"}},{"name":"user_identity","in":"query","description":"Narrow the list to runs acting for one person - the `user_identity`\nthey were started with, matched exactly. Omit for every run. A\nscheduled run started by a trigger carrying `user_identity` is found\nhere too.\n","required":false,"schema":{"type":"string","maxLength":256}},{"name":"agent_id","in":"query","description":"Narrow the list to one agent's runs (prefixed external id,\n`agent_...`), delegated children of that agent included. Omit for\nevery agent. A value that is not an agent id is rejected with a 400\nnaming `agent_id`; an agent id your workspace does not own matches\nnothing.\n","required":false,"schema":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The workspace's runs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/runs/{run_id}":{"get":{"operationId":"get","summary":"Get Run","description":"Get a run by its id alone, when that is all you hold: the run id\narrives on its own in the `Speechify-Idempotency-Key` an MCP\nconnector receives (`:`), in the `Idempotency-Key` a\nwebhook tool receives, and on every `run.*` webhook event. The\nresponse is the same run object `getRun` returns, `agent_id`\nincluded, so one call resolves the agent and the run together.\n\nScoped to your workspace and to the projects your key can reach; a\nrun outside either is a 404.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}":{"get":{"operationId":"getRun","summary":"Get Agent Run","description":"Poll a run by its handle. Branch on `status`.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/cancel":{"post":{"operationId":"cancelRun","summary":"Cancel Agent Run","description":"Request cancellation. Idempotent: a queued run cancels immediately; a\nrunning run flips to `canceling` and settles at its next checkpoint; a\nterminal run is unchanged.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"The run after the cancel request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/submit":{"post":{"operationId":"submitRun","summary":"Submit Agent Run Approval","description":"Resolve a run that is durably waiting for human approval (status\n`requires_action`). Read the run's `pending_action` first - it describes,\nverbatim, the exact action awaiting approval and the effect of each\ndisposition. `approve` resumes the run to execute the action; `deny`\nresumes it down its could-not-complete path. Because the wait is durable,\na run resumes even across a deploy.\n\nIdempotent via `Idempotency-Key`: a retry replays the first decision\nrather than re-deciding. If nobody responds before the pending action's\n`expires_at`, the run applies its `default_decision` (deny, for a\nconsequential action).\n\nReturns `409 agent_run_not_pending` if the run is not awaiting approval,\nand `409 agent_run_action_stale` if `action_key` no longer matches the\nrun's current pending action.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"The run after the approval decision (resumed to `running`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitAgentRunRequest"}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/steps":{"get":{"operationId":"listRunSteps","summary":"List Agent Run Steps","description":"List a run's **step journal**: the append-only plan-act-observe trail the\ndurable runtime leaves behind, in order (`seq` ascending). Each step is one\niteration of the loop - a `plan` (the agent's rationale), a `tool_call` (the\ntool and its arguments), or an `observation` (the tool's result). The run's\nfinal answer is not a step; it lives on the run object (`getRun`). Use this\nto render or audit exactly what a run did between its instruction and its\nresult.\n\nCredential-shaped values in a step's arguments and content are redacted.\n\n**The journal is kept for 90 days**, after which an older run returns an\nempty list here. The run itself is not deleted with it: its instruction,\nanswer and usage stay on the run object indefinitely. Export the trail\nif you need it beyond that window.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run's steps, oldest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunStepsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/events":{"get":{"operationId":"streamEvents","summary":"Stream Run Events","description":"Server-Sent Events stream of a run's progress, so a client can follow\na long-running agent instead of polling it.\n\nHolds the connection open and tails the run's journal: a\n`run.step.added` event per step (its `data` is a RunStep object, the\nsame shape List Run Steps returns), a `run.status.changed` event when\nthe run's status moves, and a terminal `run.ended` event carrying the\nfinal status and, when the run stopped short, its\n`incomplete_reason`. Consumers must ignore unknown event types.\n\nA run waiting on a human approval is not terminal: the stream reports\n`requires_action` and keeps tailing, so the client learns it has\nsomething to decide.\n\n## The stream is expected to reconnect\n\n**The server closes the connection after 4 minutes whether or not the\nrun has settled**, so a long run spans several connections. Only\n`run.ended` means the run is over - a closed socket does not. The\nresponse opens with `retry: 2000`, so a browser `EventSource`\nreconnects on its own; a hand-rolled client must do the same.\n\nResume with the standard `Last-Event-ID` header. Step events carry\ntheir `seq` as the event id, so a reconnect continues exactly where it\nleft off; status events carry no id, and an absent or unparseable\n`Last-Event-ID` replays the journal from the beginning rather than\nskipping it. On every connection, including a resume, the server emits\none `run.status.changed` carrying the run's current status before it\nstarts tailing. A `: keepalive` comment arrives every 15 seconds so an\nintermediary does not time the connection out while the agent is\nthinking.\n\nSame read access as List Run Steps, and the same\n`402 durable_runs_not_in_plan` on a workspace without the grant.","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"An event stream (text/event-stream) of run steps and status changes.","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/AgentRunStreamEvent"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/runs/{run_id}/children":{"get":{"operationId":"listRunChildren","summary":"List Agent Run Children","description":"List the runs this run **delegated to**, newest first. When a run hands a\nsub-goal to another agent (a team member), the sub-goal executes as its own\nbounded child run with its own status, output, and `usage` - so a run's true\ncost is its own usage plus its children's.\n\nEach child runs under the MEMBER's agent, not this one, so children never\nappear in this agent's `listRuns`. This is the downward half of lineage; the\nupward half is `parent_run_id` on the run object. A run that delegated\nnothing returns an empty list.\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n","tags":["runs"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"run_id","in":"path","description":"Run id (prefixed external id, `arun_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The run's delegated child runs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases":{"get":{"operationId":"list","summary":"List Knowledge Bases","description":"List knowledge bases owned by the caller. Cursor-paginated:\nomit `cursor` to fetch the first page. The default page size is\n50 and the max is 200; values outside that range are clamped.\nWalk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The knowledge bases for the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBasesResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Knowledge Base","description":"Create a new knowledge base.","tags":["knowledgeBases"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBase"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKnowledgeBaseRequest"}}}}}},"/v1/agents/knowledge-bases/search":{"post":{"operationId":"search","summary":"Search Knowledge Bases","description":"Semantic search across a caller-owned list of knowledge bases.\nReturns ranked chunks with source filename and a cosine-similarity\nscore. Limited to 50 results per request.","tags":["knowledgeBases"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Ranked search hits across the selected knowledge bases.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchKnowledgeBasesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchKnowledgeBasesRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}":{"get":{"operationId":"get","summary":"Get Knowledge Base","description":"Retrieve a knowledge base by ID.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBase"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Knowledge Base","description":"Update a knowledge base.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBase"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateKnowledgeBaseRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Knowledge Base","description":"Soft-delete a knowledge base. Documents and chunks are cascaded.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Knowledge base deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/coverage":{"get":{"operationId":"getCoverage","summary":"Get Knowledge Base Coverage","description":"Report what callers asked that this knowledge base did not cover\nover a trailing window, and which of its documents no search\nsurfaced. Built from the searches agents ran against it: a health\nstrip (searches by verdict, conversations, median latency, documents\nused), the partial and insufficient questions clustered by the\ncontent words they share, and the ready documents no search in the\nwindow retrieved a passage from. Documents added in the last 24\nhours are not listed as unused yet.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"days","in":"query","description":"Trailing window in days (default 30, max 90).","required":false,"schema":{"type":"integer","minimum":1,"maximum":90,"default":30}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The coverage report for the window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseCoverage"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/eval-cases":{"get":{"operationId":"listEvalCases","summary":"List Evaluation Cases","description":"List the labelled questions of a knowledge base, newest first.\nCursor-paginated: omit `cursor` to fetch the first page. Default\npage size is 50 and max is 200. Walk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The cases.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseEvalCasesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createEvalCase","summary":"Create Evaluation Case","description":"Add a labelled question: the documents that answer it, or\n`unanswerable` when nothing in the knowledge base should. Every\nexpected document must belong to this knowledge base. A knowledge\nbase holds at most 500 cases.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvalCase"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKnowledgeBaseEvalCaseRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/eval-cases/{case_id}":{"patch":{"operationId":"updateEvalCase","summary":"Update Evaluation Case","description":"Change a case. Omitted fields keep their value. Past evaluations\nkeep the question and expectation they ran against.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"case_id","in":"path","description":"Evaluation case id (prefixed external id, `kbcase_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvalCase"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateKnowledgeBaseEvalCaseRequest"}}}}},"delete":{"operationId":"deleteEvalCase","summary":"Delete Evaluation Case","description":"Remove a case. Past evaluations keep the outcome they recorded for\nit.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"case_id","in":"path","description":"Evaluation case id (prefixed external id, `kbcase_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Case deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/evaluations":{"get":{"operationId":"listEvaluations","summary":"List Knowledge Base Evaluations","description":"List evaluations of a knowledge base, newest first. Cursor-paginated:\nomit `cursor` to fetch the first page. Default page size is 50 and\nmax is 200. Walk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseEvaluationsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"startEvaluation","summary":"Start Knowledge Base Evaluation","description":"Run every case against the live retrieval pipeline. Returns 202 with\nthe evaluation; poll `GET /{kb_id}/evaluations/{evaluation_id}` until\nits `status` is terminal, then read the metrics on it and the per-case\noutcomes from `/results`. Each case is one search the workspace pays\nfor, so the same spend limits as a search apply.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Evaluation queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvaluation"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartKnowledgeBaseEvaluationRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/evaluations/{evaluation_id}":{"get":{"operationId":"getEvaluation","summary":"Get Knowledge Base Evaluation","description":"Retrieve one evaluation: its lifecycle status, progress counts, the\nverdict breakdown and, once completed, its metrics.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"evaluation_id","in":"path","description":"Knowledge-base evaluation id (prefixed external id, `kbeval_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvaluation"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/evaluations/{evaluation_id}/cancel":{"post":{"operationId":"cancelEvaluation","summary":"Cancel Knowledge Base Evaluation","description":"Stop a pending or running evaluation. Idempotent: a finished\nevaluation comes back unchanged.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"evaluation_id","in":"path","description":"Knowledge-base evaluation id (prefixed external id, `kbeval_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The evaluation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseEvaluation"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/evaluations/{evaluation_id}/results":{"get":{"operationId":"listEvaluationResults","summary":"List Knowledge Base Evaluation Results","description":"The per-case outcomes of an evaluation, in case order.\nCursor-paginated: omit `cursor` to fetch the first page. Default\npage size is 50 and max is 200. Walk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"evaluation_id","in":"path","description":"Knowledge-base evaluation id (prefixed external id, `kbeval_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The results.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseEvaluationResultsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents":{"get":{"operationId":"listDocuments","summary":"List Knowledge Base Documents","description":"List documents ingested into a knowledge base. Cursor-paginated:\nomit `cursor` to fetch the first page. Default page size is 50\nand max is 200. Walk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"folder_id","in":"query","description":"Folder filter: omit for root-level documents, pass `all` for\nevery document in the KB, or a folder id to scope to that\nfolder.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Substring match on filename and source_url.","required":false,"schema":{"type":"string"}},{"name":"source_kind","in":"query","description":"Comma-separated source kinds (file|url|text).","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The documents in the knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseDocumentsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"uploadDocument","summary":"Upload Knowledge Base Document","description":"Upload a document (PDF, plain text, markdown, or HTML) to a\nknowledge base. The document is extracted, chunked, embedded, and\nindexed synchronously; expect a few seconds per MB of input.\nMaximum 10 MB per upload.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The ingested document record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/{document_id}":{"get":{"operationId":"getDocument","summary":"Get Knowledge Base Document","description":"Retrieve a document by ID.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The document record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocumentDetail"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateDocument","summary":"Update Knowledge Base Document","description":"Update a document: move it between folders via `folder_id`, or pin\nit into every prompt via `injection_mode`. The two are independent;\na body naming only one leaves the other unchanged. A pin is refused\nwith 422 when the knowledge base's pinned documents would exceed\nthe prompt budget, naming the token counts.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocumentDetail"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"folder_id":{"type":["string","null"],"description":"Destination folder. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null moves the\ndocument to the knowledge base root.\n"},"injection_mode":{"$ref":"#/components/schemas/V1AgentsKnowledgeBasesKbIdDocumentsDocumentIdPatchRequestBodyContentApplicationJsonSchemaInjectionMode","description":"`always` keeps the document in every prompt; `on_demand`\nreturns it to retrieval.\n"}}}}}}},"delete":{"operationId":"deleteDocument","summary":"Delete Knowledge Base Document","description":"Delete a document and all its chunks.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Document deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/{document_id}/chunks":{"get":{"operationId":"listChunks","summary":"List Knowledge Base Chunks","description":"List the chunks for a document. Cursor-paginated: omit `cursor`\nto fetch the first page. Default page size is 50 and max is 200.\nWalk pages while `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The chunks for the document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseChunksResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/crawl":{"post":{"operationId":"createCrawlImport","summary":"Create Crawl Import","description":"Kick off an async website crawl. Returns 202 with the import\njob row; client polls `GET /{kb_id}/imports` for progress.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Import job queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCrawlImportRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/sitemap":{"post":{"operationId":"createSitemapImport","summary":"Create Sitemap Import","description":"Kick off an async sitemap import. Returns 202 with the import\njob row; client polls `GET /{kb_id}/imports` for progress.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Import job queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSitemapImportRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/text":{"post":{"operationId":"createTextDocument","summary":"Create Text Document","description":"Create a document from inline pasted text. Content is chunked,\nembedded, and indexed synchronously.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTextDocumentRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/url":{"post":{"operationId":"createURLDocument","summary":"Create URL Document","description":"Fetch a URL via Firecrawl and ingest the rendered content as a\ndocument. The fetch happens synchronously; expect a few\nseconds per page. Use the sitemap / crawl endpoints for\nmulti-page imports.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The document was accepted and is being fetched + embedded\nasynchronously. The returned row is a placeholder with\n`status: fetching`; poll the document until it reaches\n`ready` or `failed`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateURLDocumentRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/urls":{"post":{"operationId":"createURLBatchImport","summary":"Create Multi-URL Import","description":"Kick off an async multi-URL import. Accepts 1..N URLs in a\nsingle job (capped per-deployment, default 50) and runs the\nsame per-URL pipeline as the sitemap importer. Returns 202 with\nthe import job row; client polls `GET /{kb_id}/imports` for\nprogress.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Import job queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateURLBatchImportRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/batch":{"delete":{"operationId":"batchDeleteDocuments","summary":"Batch Delete Documents","description":"Delete multiple documents in a single transaction. All ids\nmust belong to the supplied knowledge base; mismatches fail\nthe request with 400 before any rows are touched. Capped at\n200 ids per call.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Documents deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchDeleteDocumentsRequest"}}}}},"patch":{"operationId":"batchMoveDocuments","summary":"Batch Move Documents","description":"Move multiple documents into a folder in a single transaction.\nPass `folder_id: null` to move every doc to root. Capped at\n200 ids per call. Returns the moved documents.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The moved documents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchMoveDocumentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchMoveDocumentsRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/{document_id}/refresh-config":{"patch":{"operationId":"updateRefreshConfig","summary":"Update Refresh Config","description":"Update the per-document auto-refresh state. Only meaningful\nfor url-sourced documents; file and text rows reject the\nrequest.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated refresh config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshConfig"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRefreshConfigRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/documents/{document_id}/refresh-history":{"get":{"operationId":"listRefreshHistory","summary":"List Refresh History","description":"List auto-refresh attempts for a document, newest first.\nCursor-paginated: omit `cursor` to fetch the first page. Default\npage size is 50 and max is 200. Walk pages while `has_more` is\ntrue.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Knowledge-base document id (prefixed external id, `doc_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recent refresh attempts for the document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListRefreshHistoryResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/folders":{"get":{"operationId":"listFolders","summary":"List Folders","description":"List folders inside a knowledge base. Root-level folders have\n`parent_folder_id: null`. Cursor-paginated: omit `cursor` to\nfetch the first page. Default page size is 50 and max is 200.\nBuild the folder tree from `parent_folder_id`, so consumers\nshould walk every page until `has_more` is `false` before\nrendering the tree.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Folders in the knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListKnowledgeBaseFoldersResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createFolder","summary":"Create Folder","description":"Create a folder inside a knowledge base.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseFolder"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFolderRequest"}}}}}},"/v1/agents/knowledge-bases/{kb_id}/folders/{folder_id}":{"patch":{"operationId":"updateFolder","summary":"Update Folder","description":"Update a folder. Pass `parent_folder_id: null` to move to\nroot; omit the field to leave it unchanged.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"folder_id","in":"path","description":"Knowledge-base folder id (prefixed external id, `kfolder_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KnowledgeBaseFolder"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFolderRequest"}}}}},"delete":{"operationId":"deleteFolder","summary":"Delete Folder","description":"Delete a folder. By default a folder that still contains\ndocuments or sub-folders is refused with `409` - pass\n`?force=true` to delete it anyway, which recursively moves its\ndocuments to root and removes its sub-folders.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"folder_id","in":"path","description":"Knowledge-base folder id (prefixed external id, `kfolder_...`).","required":true,"schema":{"type":"string"}},{"name":"force","in":"query","description":"When true, delete the folder even if it still contains documents or sub-folders; documents are moved to root and sub-folders are removed.","required":false,"schema":{"type":"boolean"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Folder deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/imports":{"get":{"operationId":"listImportJobs","summary":"List Import Jobs","description":"List import jobs (sitemap / crawl / refresh) for a knowledge\nbase, newest first. Cursor-paginated: omit `cursor` to fetch the\nfirst page. Default page size is 50 and max is 200. Walk pages\nwhile `has_more` is true.","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The import jobs for the knowledge base.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListImportJobsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/imports/{import_id}":{"get":{"operationId":"getImportJob","summary":"Get Import Job","description":"Retrieve a single import job by ID. Poll this endpoint until\n`status` reaches a terminal state (`completed` / `failed` /\n`cancelled`).\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"import_id","in":"path","description":"Knowledge-base import job id (prefixed external id, `import_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The import job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/knowledge-bases/{kb_id}/imports/{import_id}/cancel":{"post":{"operationId":"cancelImportJob","summary":"Cancel Import Job","description":"Cancel an import job. Moves a non-terminal job to `cancelled` and\nreturns `200` with the job so callers see its new `status` without a\nfollow-up `GET`. Idempotent (\u00a716), matching the other job-cancel verbs\n(`cancel` on batch calls, test runs, and suite runs): cancelling an\nalready-terminal job (`completed` / `failed` / `cancelled`) returns it\nunchanged. 404 only when the job id is unknown or its kb_id does not\nmatch the `{kb_id}` path segment.\n","tags":["knowledgeBases"],"parameters":[{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"import_id","in":"path","description":"Knowledge-base import job id (prefixed external id, `import_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The cancelled import job.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportJob"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/knowledge-bases":{"get":{"operationId":"listAgentKnowledgeBases","summary":"List Agent Knowledge Bases","description":"List knowledge bases attached to an agent. Cursor-paginated: omit\n`cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200).\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The knowledge bases attached to the agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachedKnowledgeBasesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/knowledge-bases/{kb_id}":{"post":{"operationId":"attachKnowledgeBase","summary":"Attach Agent Knowledge Base","description":"Attach a knowledge base to an agent. The `search_knowledge` tool\nis auto-registered on the next conversation and can only query the\nattached knowledge bases.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Knowledge base attached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"detachKnowledgeBase","summary":"Detach Agent Knowledge Base","description":"Detach a knowledge base from an agent.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"kb_id","in":"path","description":"Knowledge base id (prefixed external id, `kb_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Knowledge base detached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/tools":{"get":{"operationId":"listTools","summary":"List Agent Tools","description":"List everything this agent can do: built-in capabilities and\nattached external tool definitions (webhook / client / MCP) in\none mixed-kind list. Each item carries its resolved config inline.\nCursor-paginated: omit `cursor` for the first page; walk pages while\n`has_more` is true (default page size 50, max 200).\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The agent's tools.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentToolsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createTool","summary":"Create Agent Tool","description":"Add a tool to the agent. With `kind=builtin` this creates a\nper-agent built-in instance (e.g. `end_call`, `play_audio`); the\n`builtin` capability name comes from `config.builtin`. With\n`kind=webhook`, `client`, or `mcp` this atomically creates a\nworkspace tool definition AND attaches it to the agent in one\ncall. To attach a definition that already exists, use\n`PUT /v1/agents/{agent_id}/tools/{tool_id}` instead.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nThe contents of `config` are your own data at this level; they are\nvalidated against the tool kind's own schema, which has always\nrefused an unknown key there.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentToolRequest"}}}}}},"/v1/agents/{agent_id}/tools/{tool_id}":{"get":{"operationId":"getTool","summary":"Get Agent Tool","description":"Fetch one of the agent's tools by its id (built-in or attached definition).","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"tool_id","in":"path","description":"Tool id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTool"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"attachTool","summary":"Attach Tool","description":"Attach an existing workspace tool definition (webhook / client /\nMCP) to the agent by its definition id. Idempotent. To create and\nattach in one step, POST to `/v1/agents/{agent_id}/tools` instead.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"tool_id","in":"path","description":"Tool id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Tool attached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateTool","summary":"Update Agent Tool","description":"Update one of the agent's tools. For a built-in, every field\napplies. For an attached external tool, only `enabled` is\neditable here \u2014 its shared config/name is edited on the\ndefinition (`PATCH /v1/agents/tool-definitions/{tool_definition_id}`), since the\ndefinition is referenced live by every agent it is attached to;\nattempting a shared-config edit here returns `tool_config_shared`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nThe contents of `config` are your own data at this level; they are\nvalidated against the tool kind's own schema, which has always\nrefused an unknown key there.\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"tool_id","in":"path","description":"Tool id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentToolRequest"}}}}},"delete":{"operationId":"deleteTool","summary":"Delete Agent Tool","description":"Remove a tool from the agent. A built-in instance is deleted; an\nattached external tool is detached (the workspace definition\nsurvives and stays attachable elsewhere).\n","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"tool_id","in":"path","description":"Tool id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Tool removed.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tool-definitions":{"get":{"operationId":"list","summary":"List Tools","description":"List tools in the caller's workspace, most recently updated\nfirst. Cursor-paginated: omit `cursor` to fetch the first page.\nDefault page size is 50 and max is 200. Walk pages while\n`has_more` is true.","tags":["toolDefinitions"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of tools.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListToolsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Tool","description":"Create a tool. For webhook tools, the response includes the HMAC\n`webhook_secret` exactly once \u2014 store it immediately; subsequent\nreads return a masked placeholder.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nThe contents of `config` are your own data at this level; they are\nvalidated against the tool kind's own schema, which has always\nrefused an unknown key there.\n","tags":["toolDefinitions"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateToolRequest"}}}}}},"/v1/agents/tool-definitions/{tool_definition_id}":{"get":{"operationId":"get","summary":"Get Tool","description":"Retrieve a tool by ID. Webhook secrets are always masked here.","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Tool","description":"Update a tool. Tool kind is immutable \u2014 create a new tool to change\nit.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nThe contents of `config` are your own data at this level; they are\nvalidated against the tool kind's own schema, which has always\nrefused an unknown key there.\n","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateToolRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Tool","description":"Delete a tool. Agents that had it attached get a soft-detach.","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Tool deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tool-definitions/{tool_definition_id}/attached-agents":{"get":{"operationId":"listAttachedAgents","summary":"List Tool Attached Agents","description":"List the agents in the caller's workspace that currently have\nthis tool attached. Useful before deleting a tool, to surface\nwhich agents will lose access. Soft-deleted agents are filtered\nout. Cursor-paginated: omit `cursor` for the first page; walk\npages while `has_more` is true (default page size 50, max 200).","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Agents in the caller's workspace attached to the tool.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListToolAttachedAgentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tool-definitions/{tool_definition_id}/rotate-secret":{"post":{"operationId":"rotateSecret","summary":"Rotate Tool Webhook Secret","description":"Rotate the HMAC signing secret on a webhook tool. The tool id\nis preserved so attached agents keep working; only the secret\nrolls. The new plaintext is returned on `webhook_secret`\nexactly once \u2014 store it immediately, subsequent reads always\nreturn the masked placeholder. The previous secret is\ninvalidated immediately on success.\n","tags":["toolDefinitions"],"parameters":[{"name":"tool_definition_id","in":"path","description":"Tool definition id (prefixed external id, `tool_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The tool with its newly-rotated webhook_secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tool"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tool-definitions/test-mcp-connection":{"post":{"operationId":"testMCPConnection","summary":"Test MCP Connection","description":"Probe a customer-supplied MCP server config without persisting\nanything. The server opens the configured transport, runs the\n`initialize` + `list_tools` handshake, and returns either the\ndiscovered tool catalogue or a structured error string. Pass\n`tool_id` from the edit-form flow when the auth payload carries\n`_set` markers but no plaintext, so the server can hydrate the\nstored secret from the encrypted column before probing.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n","tags":["toolDefinitions"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Probe result. The 200 envelope is used for both success and\nstructured failure \u2014 inspect `error` to disambiguate. Network\nand validation failures never bubble up as non-2xx so a\nclient can render them inline next to the form.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCPProbeResult"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestMCPConnectionRequest"}}}}}},"/v1/agents/tool-definitions/test-webhook-connection":{"post":{"operationId":"testWebhookConnection","summary":"Test Webhook Connection","description":"Probe a customer-supplied webhook tool config without persisting\nanything. The server fires the exact request shape used on a real\ninvocation \u2014 same JSON body, same HMAC-SHA256\nsignature \u2014 with an empty argument set, and reports the\nendpoint's status code, latency, and a truncated response body,\nor a transport-level failure reason. The probe carries a\n`Speechify-Webhook-Test: true` header so a careful endpoint\ncan recognise the test and skip its real side effect. Pass\n`tool_id` from the edit-form flow so the server signs the probe\nwith the tool's stored HMAC secret.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n","tags":["toolDefinitions"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Probe result. The 200 envelope is used for both success and\nstructured failure \u2014 inspect `error` to disambiguate. A\nnon-2xx response from the endpoint is NOT an `error`: it\npopulates `status_code` / `response_body` with `ok=false`.\nTransport and validation failures never bubble up as non-2xx\nso a client can render them inline next to the form.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookProbeResult"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestWebhookConnectionRequest"}}}}}},"/v1/agents/tool-capabilities":{"get":{"operationId":"listToolCapabilities","summary":"List Tool Capabilities","description":"Read-only catalogue of every built-in tool capability the agent\nruntime supports (the names usable as `config.builtin` when adding a\n`kind=builtin` tool to an agent). Fetch this at runtime rather\nthan maintaining a parallel client-side list; the server is the\nsingle source of truth for the label and description text a\ncustomer sees in the capability picker. Returns\nthe full set in a single response: bounded by the platform's\nbuilt-in tool-capability registry, so this list is intentionally\nnot paginated.\n","tags":["agent"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The catalogue of registered built-in capabilities.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSystemBuiltinsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests":{"post":{"operationId":"createTest","summary":"Create Test","description":"Create a workspace-level test. The optional `agent_id` is the\nauthoring agent: when supplied it seeds the test's tool schemas and\nvariables and is recorded as authored-from provenance. Omit it to\ncreate a bare workspace test with no authoring agent (`agent_id`\ncomes back `null`). `agent_id` is never a scope - every read, list,\nand run is scoped by workspace, and the run target is bound at run\ntime (`POST /v1/agents/tests/runs`) independently of it.","tags":["tests"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTest"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentTestRequest"}}}}},"get":{"operationId":"listAllTests","summary":"List Tests","description":"Workspace-wide list of tests across every agent the caller owns.\nSupports filters (agent, type, last-run status, folder), full-text\nsearch on name/description, and cursor pagination. Each row carries\nits newest run and attached agent IDs so the list renders without\nN+1 round-trips. Walk pages while `has_more` is true.\n\n`project_id` matches the project the test was AUTHORED under, frozen\nat create. Folders are deliberately not project-scoped, so a folder\ncan hold tests this filter splits across projects.","tags":["tests"],"parameters":[{"name":"agent_id","in":"query","description":"Comma-separated agent IDs to filter on.","required":false,"schema":{"type":"string"}},{"name":"type","in":"query","description":"Comma-separated test types (reply|tool|simulation).","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","description":"Comma-separated last-run lifecycle statuses to filter on\n(pending, running, completed, failed, cancelled).\n","required":false,"schema":{"type":"string"}},{"name":"verdict","in":"query","description":"Comma-separated last-run verdicts to filter on (passed, failed).\nANDed with the `status` filter when both are present.\n","required":false,"schema":{"type":"string"}},{"name":"folder_id","in":"query","description":"Folder ID to filter on, or \"root\" for unfiled tests.","required":false,"schema":{"type":"string"}},{"name":"updated_after","in":"query","description":"Only return tests updated after this RFC3339 timestamp.","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Substring match on name or description.","required":false,"schema":{"type":"string"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTestsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/{test_id}":{"get":{"operationId":"getTest","summary":"Get Agent Test","description":"Retrieve a test by ID.","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTest"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateTest","summary":"Update Agent Test","description":"Update a test. Only fields present on the request body are changed.","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTest"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentTestRequest"}}}}},"delete":{"operationId":"deleteTest","summary":"Delete Agent Test","description":"Delete a test and all its run history.","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Test deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/folders":{"get":{"operationId":"listTestFolders","summary":"List Test Folders","description":"List every test folder the caller owns. Flat list; build the tree\nclient-side. Cursor-paginated: omit `cursor` for the first page;\nwalk pages while `has_more` is true (default page size 50, max 200).\n\nFolders are workspace-wide, so `project_id` never hides a folder:\nit narrows each folder's `test_count` to the tests that project\nscope admits, the same rows `GET /v1/agents/tests?folder_id=`\nlists under that scope, while `total_test_count` counts what the\ncaller could see with no filter (pin and grants still apply).","tags":["tests"],"parameters":[{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Folder list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentTestFoldersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createTestFolder","summary":"Create Test Folder","description":"Create a test folder. Max depth is 3.","tags":["tests"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"Created folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestFolder"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentTestFolderRequest"}}}}}},"/v1/agents/tests/folders/{test_folder_id}":{"patch":{"operationId":"updateTestFolder","summary":"Update Test Folder","description":"Rename or reparent a test folder. Cycles are rejected.","tags":["tests"],"parameters":[{"name":"test_folder_id","in":"path","description":"Agent test folder id (prefixed external id, `folder_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated folder.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestFolder"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentTestFolderRequest"}}}}},"delete":{"operationId":"deleteTestFolder","summary":"Delete Test Folder","description":"Soft-delete a folder. Child tests drop back to root.","tags":["tests"],"parameters":[{"name":"test_folder_id","in":"path","description":"Agent test folder id (prefixed external id, `folder_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/{test_id}/runs":{"get":{"operationId":"listTestRuns","summary":"List Agent Test Runs","description":"List one page of run history for a test, newest first.\nPaginate by passing `cursor` from the previous response.\n","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Run history for the test.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentTestRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"runTest","summary":"Run Agent Test","description":"Enqueue a single run of the test. The run binds its target agent at\nrun time: pass `agent_id` to run against any agent in the workspace,\nor omit it to run against the agent the test was authored against. A\ntest with no authoring agent requires an explicit `agent_id`. The\nreturned run starts in `pending` status. Poll\n`GET /v1/agents/tests/runs/{test_run_id}` until the status reaches a\nterminal state (`completed`, `failed`, or `cancelled`). A run that\nreached `completed` produced a judgment - read the separate\n`verdict` field (`passed` / `failed`) for the pass/fail result.\n\nA run is admitted against the workspace's remaining credit and its\nspending limits, and executes on the models the workspace's plan\nincludes. A depleted balance or an exhausted spend limit returns\n`402`.","tags":["tests"],"parameters":[{"name":"test_id","in":"path","description":"Test ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The queued run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestRun"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"agent_id":{"type":"string","description":"Bind the run to this agent (any agent in the workspace).\nOmit to run against the agent the test was authored\nagainst; required when the test has no authoring agent.\n"}}}}}}}},"/v1/agents/tests/runs":{"post":{"operationId":"runTargetedTests","summary":"Run Tests (Targeted)","description":"Run a chosen set of tests against a target agent, bound at run\ntime. The tests are not attached to the agent, so the same set can\nrun against another agent variant in a second call (A/B) and,\nonce agent versioning ships, against a pinned version\n(version-pinned regression). Every child run records the resolved\ntarget on the returned suite run. Total runs are capped at 100 per\ncall; poll `GET /v1/agents/tests/runs/{test_run_id}` for each.","tags":["tests"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Runs queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAgentTestsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunTargetedTestsRequest"}}}}}},"/v1/agents/tests/runs/batch":{"post":{"operationId":"runTestsBatch","summary":"Run Tests (Batch)","description":"Queue runs for every (test, agent) pair in the body. Entries\nwithout an `agent_id` fan out to every agent the test is\nattached to. Total expanded runs are capped at 100 per call.\nEach entry in the response is a queued run; poll\n`GET /v1/agents/tests/runs/{test_run_id}` for each.","tags":["tests"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Runs queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAgentTestsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunBatchRequest"}}}}}},"/v1/agents/tests/runs/{test_run_id}":{"get":{"operationId":"getTestRun","summary":"Get Agent Test Run","description":"Retrieve a single test run by ID. Poll this endpoint until\n`status` reaches a terminal state (`completed`, `failed`, or\n`cancelled`). On `completed`, read `verdict` for the pass/fail\njudgment and `result` for the detail.","tags":["tests"],"parameters":[{"name":"test_run_id","in":"path","description":"Test run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The test run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/runs/{test_run_id}/cancel":{"post":{"operationId":"cancelTestRun","summary":"Cancel Agent Test Run","description":"Cancel a non-terminal test run. Idempotent on a terminal run\n(`completed` / `failed` / `cancelled`) - the call returns the\nunchanged run.","tags":["tests"],"parameters":[{"name":"test_run_id","in":"path","description":"Test run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The (possibly already-terminal) test run.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestRun"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/suite-runs":{"get":{"operationId":"listSuiteRuns","summary":"List Suite Runs","description":"List one page of suite runs (test invocations), newest first.\nA suite run groups every test run dispatched by one Run All,\nbatch, or resubmit call. Paginate by passing `cursor` from the\nprevious response.\n","tags":["tests"],"parameters":[{"name":"agent_id","in":"query","description":"Narrow the list to the suite runs of one agent.","required":false,"schema":{"type":"string"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"One page of suite runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSuiteRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/suite-runs/{suite_run_id}":{"get":{"operationId":"getSuiteRun","summary":"Get Suite Run","description":"Retrieve a suite run by ID with its child runs and the derived\naggregate `status`, `verdict`, and progress counts.","tags":["tests"],"parameters":[{"name":"suite_run_id","in":"path","description":"Suite run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The suite run with its child runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestSuiteRunWithRuns"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/suite-runs/{suite_run_id}/resubmit":{"post":{"operationId":"resubmitSuiteRun","summary":"Resubmit Suite Run","description":"Re-run the failed and errored tests of a suite run as a fresh\nsuite run, linked back to the original via\n`parent_suite_run_id`. Returns 400 when the suite run has no\nfailed or errored tests to re-run.\n\nThe parent's `config_override` is re-applied, so it is re-checked\nagainst the workspace's current plan: a model the workspace no\nlonger has returns `402` rather than replaying it.","tags":["tests"],"parameters":[{"name":"suite_run_id","in":"path","description":"Suite run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The new suite run and its queued child runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestSuiteRunWithRuns"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/tests/suite-runs/{suite_run_id}/cancel":{"post":{"operationId":"cancelSuiteRun","summary":"Cancel Suite Run","description":"Cancel a suite run: every child run still pending or running is\ncancelled, which makes the suite's derived `status` `cancelled`.\nChild runs that already produced a verdict or errored are left\nuntouched. Idempotent on a terminal suite - returns it unchanged.","tags":["tests"],"parameters":[{"name":"suite_run_id","in":"path","description":"Suite run ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The suite run with its child runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTestSuiteRunWithRuns"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/tests/runs":{"post":{"operationId":"runAllTests","summary":"Run All Agent Tests","description":"Enqueue runs for every test on the agent concurrently. Up to 50\ntests are dispatched in one call. Each returned run starts in\n`pending` status; poll `GET /v1/agents/tests/runs/{test_run_id}` for the terminal\nresult (`completed` / `failed` / `cancelled`, with the pass/fail\njudgment in the separate `verdict` field).\n\nAn optional request body runs the whole suite against\na proposed config: a `config_override` (prompt / model / tools)\napplied to every test without editing the tests, and/or a\n`flow_version_id` to target a specific flow version instead of\nthe agent's active flow. Omit the body to run against the\nagent's live config and active flow.\n\nRuns are admitted against the workspace's remaining credit and its\nspending limits. `config_override.model` must be a model the\nworkspace's plan includes; an over-tier model returns `402` before\nany run is queued.","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Queued runs for all tests on the agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAgentTestsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunAllTestsRequest"}}}}}},"/v1/agents/tests/stats":{"get":{"operationId":"getTestStats","summary":"Get Test Stats","description":"Aggregate pass-rate metrics over the last N days. Returns dense\ndaily buckets (one entry per day, zero-filled) plus totals and a\nper-type breakdown. Powers the header chart on the global tests\npage. Default window is 30 days, max 90.\n\nScoped by the same `project_id` filter as the list endpoint, so the\nchart above a narrowed table always counts the same tests.","tags":["tests"],"parameters":[{"name":"window_days","in":"query","description":"Trailing window in days (default 30, max 90).","required":false,"schema":{"type":"integer"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Stats payload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestStats"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/share-links":{"get":{"operationId":"list","summary":"List Share Links","description":"List the agent's share links, most recently created first.\nCursor-paginated: omit `cursor` to fetch the first page.\n\nRevoked and expired links are included \u2014 an owner needs to see what\nthey have handed out, not only what is currently live. The bearer\ntoken is never returned; only a short masked prefix so two links can\nbe told apart.","tags":["shareLinks"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of share links.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListShareLinksResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Share Link","description":"Mint a share link for the agent.\n\nThe response carries the link's bearer `token`. It is also stored, so\na caller who loses it reads it back with `revealShareLinkToken`\ninstead of revoking and re-creating \u2014 which would kill the copy the\nrecipient already has.\n\nEvery cap is optional and falls back to a bounded default. There is\nno way to express \"unlimited\": an anonymous link that spends the\nworkspace's balance should not be creatable by omitting a field.\n\nAn agent may hold at most 10 live links at once; an eleventh is\nrefused with `409 share_link_limit_reached`. Revoking a link, or\nletting one expire, frees its slot immediately.\n","tags":["shareLinks"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created link, including its one-time token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateShareLinkResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateShareLinkRequest"}}}}}},"/v1/agents/{agent_id}/share-links/{share_link_id}/reveal":{"post":{"operationId":"reveal","summary":"Reveal Share Link Token","description":"Return a share link's bearer token, so the URL can be copied again.\n\nA `POST` rather than a `GET`, and permission-gated like a mutation\neven though it changes nothing: the response is a live bearer\ncredential, so it must never be cacheable or replayable from a\nbrowser's history, and whoever may hand a link out is whoever may\nhand it out again. It is separately rate-limited.\n\nRevoked and expired links still reveal \u2014 the owner can already see\nthat state and may need to tell a recipient which link they hold.\n","tags":["shareLinks"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"share_link_id","in":"path","description":"Share link id (prefixed external id, `share_...`).","required":true,"schema":{"type":"string","pattern":"^share_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The link's bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevealShareLinkTokenResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/share-links/{share_link_id}":{"delete":{"operationId":"revoke","summary":"Revoke Share Link","description":"Revoke a share link. Idempotent \u2014 revoking an already-revoked link\nsucceeds and leaves the original timestamp intact.\n\nRevocation stops new calls immediately. It does not hang up a call\nalready in progress: that call is already bounded by its own\nduration limit and has already drawn its time from the link's\nbudget, so ending it mid-sentence would surprise the person talking\nwithout saving the owner anything.\n","tags":["shareLinks"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"share_link_id","in":"path","description":"Share link id (prefixed external id, `share_...`).","required":true,"schema":{"type":"string","pattern":"^share_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The link was revoked.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/share-links/preview":{"get":{"operationId":"preview","summary":"Preview Share Link","description":"Resolve a share link for display, before any call is started.\n\nUnauthenticated: the link token is the credential. The response is\ndeliberately cosmetic-only \u2014 the agent's display name, the embed's\nvisual configuration, and the link's remaining budget. It never\nreturns the agent id, the workspace id, the prompt, the model, or\nany tool or knowledge-base name.\n\nThis endpoint performs no write and consumes no budget. Link\nunfurlers (chat clients, mail providers, security scanners) will\nfetch a pasted URL, so a preview must never be able to spend a\nlink's remaining call time.\n\nUnknown, revoked and expired links all answer `404\nshare_link_not_found`. The states are indistinguishable on purpose:\nthe token is a bearer credential that spends money, so telling a\ncaller which of their guesses named a real link would hand them an\nenumeration oracle.\n","tags":["shareLinks"],"parameters":[{"name":"token","in":"query","description":"The share link's opaque bearer token.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The link's public, cosmetic projection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLinkPreview"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/share-links/sessions":{"post":{"operationId":"createSession","summary":"Create Share Link Session","description":"Start a realtime voice session against a share link.\n\nUnauthenticated: the link token is the credential, and it is sent in\nthe request body rather than the URL so it never reaches request\nlogs. The agent's owner is billed for the call.\n\nThe response carries only the realtime handles \u2014 `token` and `url` \u2014\nwhich is exactly the shape the `` element's\n`sessionProvider` expects. It deliberately omits the conversation id\nand the agent snapshot that `createSession` returns, because those\nare owner-side facts an anonymous visitor should not hold.\n\nCall time is claimed from the link's budget before anything is\ndispatched. A link with too little budget left for another call\nanswers `409 share_link_exhausted`; one already running its maximum\nnumber of simultaneous calls answers `429`.\n","tags":["shareLinks"],"parameters":[{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The realtime handles for the new session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLinkSession"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateShareLinkSessionRequest"}}}}}},"/v1/agents/phone-numbers":{"get":{"operationId":"list","summary":"List Phone Numbers","description":"List all phone numbers in the caller's workspace. Cursor-paginated:\nomit `cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200).","tags":["phoneNumbers"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The phone numbers for the workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListPhoneNumbersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"import","summary":"Import Phone Number","description":"Import a phone number into the workspace. The `provider` field\ndetermines the provisioning path:\n\n- `livekit` - LiveKit purchases the number on your behalf. US\n inbound only. Quickest path for local testing.\n- `twilio` - Provide your Twilio Account SID, Auth Token, and\n the E.164 number you already own. We provision an Elastic SIP\n Trunk on your Twilio account automatically.\n- `byoc` - Provide an existing SIP trunk ID. The number is\n registered against that trunk.\n\nReturns 402 when the workspace has reached the 100-number cap.\n","tags":["phoneNumbers"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The imported phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportPhoneNumberRequest"}}}}}},"/v1/agents/phone-numbers/available":{"get":{"operationId":"searchAvailable","summary":"Search Available Phone Numbers","description":"Search carrier inventory for phone numbers available to purchase.\nCurrently restricted to the US (`country=US`); pass `area_code`\nto narrow to a specific NPA. The returned numbers are not held;\na subsequent `POST /v1/agents/phone-numbers/purchase` against the same\nE.164 may fail with 4xx if the number has been taken in the\nmeantime.\n","tags":["phoneNumbers"],"parameters":[{"name":"country","in":"query","description":"ISO-3166 alpha-2 country code. Defaults to \"US\"; only \"US\" is supported in v1.","required":false,"schema":{"type":"string","default":"US"}},{"name":"area_code","in":"query","description":"Three-digit NPA to filter inventory to a region.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max results to return. Capped at 50.","required":false,"schema":{"type":"integer","default":20}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Available numbers (may be empty if no inventory matches).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAvailablePhoneNumbersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/phone-numbers/purchase":{"post":{"operationId":"purchase","summary":"Purchase Phone Number","description":"Purchase a phone number on Speechify's master Twilio account.\nThe number is billed to Speechify until released. A plan that\nincludes no purchased numbers (e.g. Free) returns\n`402 purchased_numbers_not_included`; a plan that has used its full\nincluded quota returns 422. This is independent of the overall\n100-number cap. Because the number is a recurring Speechify-borne\ncarrier cost, the workspace must also have a funded balance (a paid\nplan or a payment method on file, not just trial / granted credits),\nelse the buy is refused with `402 funded_balance_required`.\n`e164` must come from a recent `SearchAvailablePhoneNumbers`\nresponse \u2014 carriers reject buys against numbers that are no\nlonger in inventory. The returned phone number is wired for\nboth inbound (when `agent_id` is set, or after binding the\nnumber to an agent via\n`POST /v1/agents/{agent_id}/phone-numbers/{phone_number_id}`) and\noutbound calls (via the workspace's shared outbound trunk).\n","tags":["phoneNumbers"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The purchased phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchasePhoneNumberRequest"}}}}}},"/v1/agents/phone-numbers/{phone_number_id}":{"get":{"operationId":"get","summary":"Get Phone Number","description":"Retrieve a phone number by ID.","tags":["phoneNumbers"],"parameters":[{"name":"phone_number_id","in":"path","description":"Phone number id (prefixed external id, `phone_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Phone Number","description":"Update a phone number's own attributes (today: `label`), or point it\nat one of your external brains with `brain_id`. `source` and `e164` are\nimmutable after import. To bind or unbind an agent, use the\nrelationship endpoints\n`POST`/`DELETE /v1/agents/{agent_id}/phone-numbers/{phone_number_id}`;\na number's brain is a hosted agent or a brain of your own, never both,\nand setting one clears the other. `brain_id: null` takes the brain off\nthe number and leaves the brain itself untouched, so releasing a number\nnever destroys the service definition behind it. Pointing a number at a\nbrain requires the Phone product to be enabled for the workspace.\n","tags":["phoneNumbers"],"parameters":[{"name":"phone_number_id","in":"path","description":"Phone number id (prefixed external id, `phone_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumber"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePhoneNumberRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Phone Number","description":"Delete a phone number from the workspace. For Twilio and LiveKit\nnumbers this also deprovisions the backing SIP trunk and dispatch\nrule on LiveKit Cloud.\n","tags":["phoneNumbers"],"parameters":[{"name":"phone_number_id","in":"path","description":"Phone number id (prefixed external id, `phone_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Phone number deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/outbound-calls":{"post":{"operationId":"create","summary":"Create Outbound Call","description":"Place an outbound call from an agent to a phone number. LiveKit\noriginates the SIP INVITE through the outbound trunk bound to the\nagent's workspace; the agent is dispatched into the call\nautomatically.\n\nThe response is returned as soon as LiveKit accepts the INVITE.\nPoll `GET /v1/agents/conversations/{conversation_id}` for status\ntransitions: `pending` \u2192 `active` (answered) \u2192 `completed`.\n\nThis is also how a Phone number whose brain is your own webhook\ndials out: when the caller ID resolves to a number carrying a\nrelay binding, every turn on the call is answered by your endpoint\ninstead of an LLM, and `call.started`'s reply is the opening line\nspoken to the callee. Everything else is unchanged - same gates,\nsame conversation, same transcript, same recording. Pass the agent\nthe webhook bind provisioned (it is returned on the number) and,\nfor a workspace with several numbers, `caller_id_number`.\n\nRequires a Twilio or BYOC trunk. LiveKit-native numbers are\ninbound-only.\n\nInternational (non-NANP) destinations require a funded balance - a\npaid plan or a payment method on file, not just trial / granted\ncredits - and are otherwise refused with\n`402 funded_balance_required`. Domestic (`+1`) calls are unaffected.\n","tags":["outboundCalls"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The outbound call was accepted by LiveKit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOutboundCallResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"An upstream dependency (the TTS composer or voice-metadata\nservice) returned a 5xx. The raw upstream detail is not\nforwarded - the cause is in the server log; the response is a\nfixed `upstream_failure` envelope. Safe to retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOutboundCallRequest"}}}}}},"/v1/agents/batch-calls":{"get":{"operationId":"list","summary":"List Batch Calls","description":"Returns one page of batch calls for the workspace, newest first.\nPaginate by passing `cursor` from the previous response.\n\n`project_id` matches the project the batch was STARTED under, frozen\nat create. Moving its agent to another project therefore leaves past\nbatches attributed where they ran.\n","tags":["batchCalls"],"parameters":[{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBatchCallsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Batch Call","description":"Dial a list of phone numbers through one of your voice agents in a\nsingle request. Each recipient can receive personalised dynamic\nvariables that your agent prompt references via `{{key}}` placeholders.\nBatches can run immediately or be scheduled up to 30 days in advance.\n\nAccepts `application/json` or `multipart/form-data` (with a CSV file).\nMax 1000 recipients per batch.\n","tags":["batchCalls"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"Batch accepted for processing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCall"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBatchCallRequest"}}}}}},"/v1/agents/batch-calls/{batch_call_id}":{"get":{"operationId":"get","summary":"Get Batch Call","description":"Returns the batch call. List its recipients with\n`GET /v1/agents/batch-calls/{batch_call_id}/recipients`.\n","tags":["batchCalls"],"parameters":[{"name":"batch_call_id","in":"path","description":"Batch call ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCall"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/batch-calls/{batch_call_id}/recipients":{"get":{"operationId":"listRecipients","summary":"List Batch Call Recipients","description":"Returns one page of recipients for a batch call, ordered by id.\nPaginate by passing `cursor` from the previous response.\n","tags":["batchCalls"],"parameters":[{"name":"batch_call_id","in":"path","description":"Batch call ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBatchRecipientsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/batch-calls/{batch_call_id}/cancel":{"post":{"operationId":"cancel","summary":"Cancel Batch Call","description":"Cancel a batch call. Moves any non-terminal batch - `pending`\n(including one deferred to a future `scheduled_at`) or `running` - to\n`cancelled` and stops the dispatcher from dialing the not-yet-dialed\nrecipients (they become `cancelled`; calls already placed keep their\noutcome). Idempotent (\u00a716): cancelling an already-terminal batch\n(`completed` / `failed` / `cancelled`) returns it unchanged. 404 only\nwhen the batch id is unknown.\n","tags":["batchCalls"],"parameters":[{"name":"batch_call_id","in":"path","description":"Batch call ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The (possibly already-terminal) batch call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCall"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/ivr-menus":{"get":{"operationId":"list","summary":"List IVR Menus","description":"List the active IVR menus the caller's workspace has learned.\nOne row per (fingerprint, tenant).\nInvalidated rows and the cross-tenant shared slot are excluded.\nSorted by `last_observed_at` DESC so the freshest IVRs land at\nthe top. Cursor-paginated: omit `cursor` to fetch the first\npage. Default page size is 50 and max is 200. Walk pages while\n`has_more` is true.\n","tags":["ivrMemory"],"parameters":[{"name":"fingerprint","in":"query","description":"Optional SHA-256 fingerprint hash to narrow the list to one menu.","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of cached IVR menus.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListIVRMenusResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/ivr-menus/{ivr_menu_id}":{"get":{"operationId":"get","summary":"Get IVR Menu","description":"Fetch one menu's full shape. Returns 404 for missing,\nsoft-deleted, or foreign-tenant menus \u2014 existence information\nis never leaked across tenants.\n","tags":["ivrMemory"],"parameters":[{"name":"ivr_menu_id","in":"path","description":"IVR menu ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The IVR menu detail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IVRMenu"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateLabel","summary":"Update IVR Menu Option Label","description":"Re-label one option in the stored menu_tree, matched on the\nsupplied DTMF value. The label is shown when reviewing the menu\nand is read back to the agent at navigate time to surface the\noption semantically. Unknown DTMF values\nare a no-op (the response echoes the unchanged menu).\n","tags":["ivrMemory"],"parameters":[{"name":"ivr_menu_id","in":"path","description":"IVR menu ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The refreshed menu shape.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IVRMenu"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateIVRMenuLabelRequest"}}}}}},"/v1/agents/ivr-menus/{ivr_menu_id}/invalidate":{"post":{"operationId":"invalidate","summary":"Invalidate IVR Menu","description":"Soft-invalidate the named menu. Future lookups skip it; the\nnext discovery for the same fingerprint replaces it (clearing\nthe invalidation). Idempotent: re-invalidating\nan already-invalidated row returns 404.\n\nReason is optional and is captured in structured logs for\noperator triage. A future audit table may persist it.\n","tags":["ivrMemory"],"parameters":[{"name":"ivr_menu_id","in":"path","description":"IVR menu ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Menu invalidated.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvalidateIVRMenuRequest"}}}}}},"/v1/agents/sip-trunks":{"get":{"operationId":"list","summary":"List SIP Trunks","description":"List all SIP trunks in the caller's workspace. Cursor-paginated\nfor uniformity with the other list endpoints: omit `cursor` for\nthe first page; walk pages while `has_more` is true (default page\nsize 50, max 200). In practice the workspace is capped at 20\ntrunks, so every response fits in a single page.","tags":["sipTrunks"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The SIP trunks for the workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSIPTrunksResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create SIP Trunk","description":"Create a SIP trunk. For `provider=byoc` supply `sip_address` plus\noptional digest credentials and IP allowlist. For `provider=twilio`\nuse `ImportPhoneNumber` with a `twilio` spec instead - trunk\ncreation is handled automatically. Returns 422 when the workspace\nhas reached the 20-trunk cap.\n","tags":["sipTrunks"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created SIP trunk.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SIPTrunk"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSIPTrunkRequest"}}}}}},"/v1/agents/sip-trunks/{sip_trunk_id}":{"get":{"operationId":"get","summary":"Get SIP Trunk","description":"Retrieve a SIP trunk by ID.","tags":["sipTrunks"],"parameters":[{"name":"sip_trunk_id","in":"path","description":"SIP trunk id (prefixed external id, `trunk_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested SIP trunk.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SIPTrunk"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete","summary":"Delete SIP Trunk","description":"Delete a SIP trunk. This also removes the backing LiveKit inbound\ntrunk, outbound trunk, and dispatch rule if they were provisioned\nby us. Phone numbers attached to this trunk are left in place but\nbecome non-functional until rebound to a new trunk.\n","tags":["sipTrunks"],"parameters":[{"name":"sip_trunk_id","in":"path","description":"SIP trunk id (prefixed external id, `trunk_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"SIP trunk deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/callers":{"get":{"operationId":"list","summary":"List Callers","description":"List the workspace's callers, ordered by most-recently-seen first.\nA caller is the per-(tenant, agent, identity) entity that owns\nlong-term memories and conversation history.\n\n`project_id` matches the project the caller was FIRST SEEN under,\nfrozen at that moment. Moving an agent to another project therefore\nleaves the callers it already knows attributed where they were met.\n","tags":["callers"],"parameters":[{"name":"agent_id","in":"query","description":"Narrow the list to callers attached to one agent.","required":false,"schema":{"type":"string"}},{"name":"contact_id","in":"query","description":"Narrow the list to one person's rows: the contact's memory, agent by\nagent. Prefixed `contact_...` id.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Identity-prefix search. Filters to rows where `identity LIKE q + '%'`\n(`%`/`_` characters in the input are escaped as literals).\n","required":false,"schema":{"type":"string"}},{"name":"last_seen_after","in":"query","description":"RFC 3339 timestamp. Narrows to callers active strictly AFTER the\nsupplied moment. Useful for \"active this week / month\" filters.\n","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCallersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/callers/{caller_id}":{"get":{"operationId":"get","summary":"Get Caller","description":"Fetch a single caller by id. Returns 404 for soft-deleted or\nforeign-tenant rows \u2014 GDPR-purged callers appear as \"not found\"\nto the API.\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Caller"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Caller","description":"Update the customer-editable fields on a caller. PATCH semantics:\nomitted fields are unchanged, present fields overwrite. To clear\na nullable field (`display_name`, `external_ref`) pass an empty\nstring. `metadata` REPLACES the existing JSONB blob when supplied.\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The refreshed caller row.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Caller"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCallerRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Caller (GDPR purge)","description":"Soft-delete the caller AND cascade soft-delete every memory row\npointing at it. Conversations survive (forensic / billing records)\nbut their caller pointer surfaces as \"deleted\" through the API.\n\nIdempotent \u2014 re-deleting an already-purged caller returns\n`{caller_purged: 0, memories_purged: 0}`. Audit row counts\naccompany every response so a privacy operator has direct\nevidence of the purge without re-querying.\n\nReturns `200` with the row-count body rather than a bodiless\n`204`: the counts are GDPR-purge audit evidence. This is a\ndeliberate exception to the delete-returns-204 convention,\nshared with the bulk memory purge\n(`DELETE /v1/agents/{agent_id}/memories`).\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Soft-delete completed; row counts in the body (GDPR audit evidence).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteCallerResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/callers/{caller_id}/conversations":{"get":{"operationId":"listConversations","summary":"List Conversations For Caller","description":"List one page of conversations belonging to the caller, newest\nstarted first. Same wire envelope as the workspace-wide\n`GET /v1/agents/conversations`, narrowed to one caller.\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCallerConversationsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/callers/{caller_id}/memories":{"get":{"operationId":"listMemories","summary":"List Memories For Caller","description":"List one page of memories belonging to the caller, newest first.\nSoft-deleted memories AND memories whose parent caller is\nsoft-deleted are hidden \u2014 the GDPR purge semantics require the\nAPI to behave as if those rows do not exist.\n","tags":["callers"],"parameters":[{"name":"caller_id","in":"path","description":"Caller ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCallerMemoriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/memories":{"get":{"operationId":"listMemories","summary":"List Agent Memories","description":"List per-caller memories extracted for an agent. Memories are\nwritten post-call by the built-in extractor when `memory_enabled`\nis true on the agent; the list is sorted newest-first.\nCursor-paginated: omit `cursor` for the first page; walk pages\nwhile `has_more` is true (default page size 50, max 200).","tags":["agent"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Memories for the agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMemoriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/memories/{memory_id}":{"delete":{"operationId":"delete","summary":"Delete Memory","description":"Soft-delete one memory row.","tags":["memories"],"parameters":[{"name":"memory_id","in":"path","description":"User memory id (prefixed external id, `memory_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Memory deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/audio-assets":{"get":{"operationId":"list","summary":"List Audio Assets","description":"List every non-deleted audio asset in the caller's workspace.\nAudio assets are pre-recorded WAV clips (intro jingles, legal\ndisclaimers, hold cues) referenced from `play_audio` flow nodes\nand the corresponding system builtin. Cursor-paginated: omit\n`cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200).","tags":["audioAssets"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of audio assets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAudioAssetsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"upload","summary":"Upload Audio Asset","description":"Upload a new audio asset. The body is a multipart/form-data\nrequest with a single `file` field carrying the WAV bytes.\n\nThe WAV is validated server-side against a strict format\ncontract \u2014 PCM 16-bit signed, mono, 48000 Hz, \u226430s, \u22644 MiB \u2014\nbefore any bytes hit storage. The format matches what voice\nagents play back natively, so clips stream with no transcoding;\nconvert MP3 sources with `ffmpeg -i in.mp3 -ar 48000 -ac 1\n-sample_fmt s16 out.wav`.\n","tags":["audioAssets"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The uploaded asset's metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioAsset"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"file":{"type":"string","format":"binary","description":"The WAV file bytes. Must be PCM 16-bit signed, mono,\n48000 Hz, \u226430s duration, \u22644 MiB total.\n"}},"required":["file"]}}}}}},"/v1/agents/audio-assets/{audio_asset_id}":{"delete":{"operationId":"delete","summary":"Delete Audio Asset","description":"Soft-delete an audio asset. The underlying GCS object is\nretained so any flow node or tool still referencing the asset\nkeeps working until the config is updated; the agent runtime logs\nand skips on a missing row at session start (fail-soft).\n","tags":["audioAssets"],"parameters":[{"name":"audio_asset_id","in":"path","description":"Audio asset ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Audio asset soft-deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"get","summary":"Get Audio Asset","description":"Fetch one audio asset's metadata. Returns 404 for missing,\nsoft-deleted, or foreign-tenant assets \u2014 existence information\nis never leaked across tenants.\n","tags":["audioAssets"],"parameters":[{"name":"audio_asset_id","in":"path","description":"Audio asset ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The audio asset's metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AudioAsset"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/audio-assets/{audio_asset_id}/bytes":{"get":{"operationId":"getBytes","summary":"Get Audio Asset Bytes","description":"Stream the raw WAV bytes for an audio asset. Byte-stream\nsibling of the metadata endpoint at /v1/agents/audio-assets/{audio_asset_id}.\nThe agent runtime fetches through here for the play_audio\nbuiltin; SDK consumers can also download originals. Returns 404\nfor missing / soft-deleted / foreign-tenant assets.\n","tags":["audioAssets"],"parameters":[{"name":"audio_asset_id","in":"path","description":"Audio asset ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The raw audio bytes (PCM 16-bit signed, mono, 48 kHz, WAV-wrapped).","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}}}}},"/v1/agents/teams":{"get":{"operationId":"list","summary":"List Teams","description":"List the teams in the caller's workspace, most recently updated\nfirst. Cursor-paginated: omit `cursor` to fetch the first page.\nDefault page size is 50 and max is 200. Walk pages while `has_more`\nis true.","tags":["teams"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of teams.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTeamsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Team","description":"Compose a team: a `manager_agent_id` that orchestrates plus the\n`members` it delegates to. The manager and every member must be an\nagent in the caller's workspace. Idempotent via `Idempotency-Key`.\n","tags":["teams"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTeamRequest"}}}}}},"/v1/agents/teams/{team_id}":{"get":{"operationId":"get","summary":"Get Team","description":"Retrieve a team by id.","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Team","description":"Update a team's name, description, manager, and/or members. Merge-patch\nsemantics: an omitted field is left unchanged, `manager_agent_id: null`\nclears the manager, and a `members` array replaces the whole roster.\n","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Team"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTeamRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Team","description":"Delete a team. Removes the saved composition.","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Team deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/teams/{team_id}/runs":{"post":{"operationId":"run","summary":"Run Team","description":"**Run a team**: start a durable async run of the team's manager agent,\nwhich pursues the given instruction and delegates sub-goals to the team\nmembers (each executing as its own bounded child run). Returns a run\nhandle immediately (`202`, status `queued`); poll it with `getRun` or\nwatch its step timeline - it is the same durable run primitive as\n`createRun`, so the returned handle addresses the manager run.\n\nThe team must have a manager assigned; a draft team without one is a\n`400`. Idempotent via `Idempotency-Key`: a retry replays the first run.\n\nEvery admission rule `createRun` describes applies here, on the manager\nagent: the publish gate (`422 agent_publish_gate_required` until the\nmanager's current configuration is published), the MCP transport check\n(`422 tool_transport_unsupported`), the 200-run workspace backlog\nceiling and the manager's project ceiling\n(`429 concurrency_limit_reached` - and a team run's children count\ntowards both as they start), and the spend gate, including the\nmanager's project (`402 project_spend_limit_exceeded`,\n`409 project_archived`).\n\nThis endpoint is in beta: it is available to workspaces granted\n`durable_runs_access`, and every other workspace receives\n`402 durable_runs_not_in_plan`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nKeys inside `variables` and `metadata`, and the contents of\n`output_schema`, are your own data rather than field names, and are\nnever refused.\n","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"202":{"description":"The team run was accepted and queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentRun"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTeamRunRequest"}}}}},"get":{"operationId":"listRuns","summary":"List Team Runs","description":"**List a team's runs**, newest first: the team's run history. Only the\nteam's ROOT runs are returned - the ones started with `runTeam`. Each\nmember's delegated child run is attributed to the member agent that ran\nit; read a run's children with `listRunChildren`.\n\nA run started directly against the team's manager agent (`createRun`) is\nNOT a team run and never appears here.\n","tags":["teams"],"parameters":[{"name":"team_id","in":"path","description":"Team id (prefixed external id, `team_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The team's runs, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentRunsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/triggers":{"get":{"operationId":"list","summary":"List Agent Triggers","description":"List a task agent's triggers, most recently updated first.\nCursor-paginated: omit `cursor` to fetch the first page. Default page\nsize is 50 and max is 200. Walk pages while `has_more` is true.","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A list of triggers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentTriggersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Agent Trigger","description":"Create a trigger that starts a durable run of a task agent. A\n`schedule` trigger fires on a cron expression or an interval; a\n`webhook` trigger returns a one-time `secret` plus a `fire_path` your\nbackend POSTs to. Idempotent via `Idempotency-Key`.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nKeys inside `run.variables` and `run.metadata`, and the contents of\n`run.output_schema`, are your own data rather than field names, and\nare never refused.\n","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created trigger. For a webhook trigger the plaintext `secret`\nis present in this response only.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTrigger"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAgentTriggerRequest"}}}}}},"/v1/agents/{agent_id}/triggers/{trigger_id}":{"get":{"operationId":"get","summary":"Get Agent Trigger","description":"Retrieve a trigger by id.","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"trigger_id","in":"path","description":"Trigger id (prefixed external id, `trigger_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The requested trigger.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTrigger"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Agent Trigger","description":"Update a trigger. Merge-patch semantics: an omitted field is left\nunchanged. `type` is immutable. Changing a schedule's cadence or\n`enabled` reschedules its next fire.\n\nA field this endpoint does not define is refused with `400\nvalidation_failed` naming every unknown field, rather than accepted\nand silently dropped.\n\nKeys inside `run.variables` and `run.metadata`, and the contents of\n`run.output_schema`, are your own data rather than field names, and\nare never refused.\n","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"trigger_id","in":"path","description":"Trigger id (prefixed external id, `trigger_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated trigger.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTrigger"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentTriggerRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Agent Trigger","description":"Delete a trigger. A schedule stops firing; a webhook fire URL stops accepting.","tags":["triggers"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"trigger_id","in":"path","description":"Trigger id (prefixed external id, `trigger_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Trigger deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent-triggers/{trigger_id}/fire":{"post":{"operationId":"fire","summary":"Fire Agent Trigger","description":"Inbound fire URL for a webhook trigger. Authenticate with the trigger's\none-time fire token as `Authorization: Bearer `. The JSON body\nbecomes the started run's payload variable. Supply an `Idempotency-Key`\n(or `Speechify-Delivery-Id`) header to make a redelivery replay the\noriginal run instead of starting a second one.\n\n**Reacting to something that happened, for one person.** Set the\ntrigger's `webhook.user_identity_source` to `fire` and send\n`Speechify-User-Identity` on each POST. The run then acts for that\nperson - their memory, and every tool call told who it is for - so one\ntrigger serves every person your connector watches and you hold no\ntrigger id per person. Use the plain form (`user_identity_source:\ntrigger`) when the work is the same whoever it is for: a nightly\nreconciliation, an alert fan-out, a build that finished.\n\n**Rate.** A trigger admits 60 fires a minute, and 12 a minute for any\none person; past either the endpoint answers `429` with `Retry-After`.\nA fire starts a durable run against your credit balance, so a burst is\ndeferred rather than fanned out. Retry the refused delivery under the\nsame delivery id and it runs exactly once.\n","tags":["triggers"],"parameters":[{"name":"trigger_id","in":"path","description":"Trigger id (prefixed external id, `trigger_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Speechify-User-Identity","in":"header","description":"The person this event is for, in your own vocabulary - the same value\n`POST /v1/agents/{agent_id}/runs` takes and the same one the platform\nsends your connector on a tool call.\n\nAccepted only when the trigger sets `webhook.user_identity_source` to\n`fire`, and **required** on such a trigger: sending it to a trigger\nthat pins its own person, or omitting it on one that expects it, is a\n`400`. Must not begin with `user_`, `embed_` or `anon_`, which name\nidentities the platform derives.\n\nA delivery id may be reused only for the same person. The same\n`Idempotency-Key` arriving for a different person is a `409` rather\nthan a replay of the first person's run.\n","required":false,"schema":{"type":"string","maxLength":256}}],"responses":{"202":{"description":"The fire was accepted and a run was started (or replayed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTriggerFireResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit or concurrency limit exceeded. `error.code` distinguishes\nrequest-rate limiting (`rate_limited`) from concurrency exhaustion\n(`concurrency_limit_reached`). Carries `Retry-After` and the\nrequest-rate budget headers; a concurrency-exhaustion 429 also carries\n`RateLimit-Remaining-Calls: 0`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"description":"An arbitrary JSON payload, passed to the run as its payload variable.","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"description":"Any type"}}}}}}},"/v1/skills":{"get":{"operationId":"list","summary":"List Skills","description":"List the skills in the caller's workspace, most recently updated first.\nA skill is a versioned procedure - an instruction block plus the tools\nit needs - that you write once and attach to many agents. Each item\ncarries the body of its latest version.\nCursor-paginated; walk pages while `has_more` is true.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSkillsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Skill","description":"Create a skill at version 1. Names are unique per workspace. Every\n`tool_ids` and `knowledge_base_ids` entry must already exist in the\nsame project as the skill.\nBounded by the workspace's skill limit (409 `skill_limit_reached`).\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created skill.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Skill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSkillRequest"}}}}}},"/v1/skills/{skill_id}":{"get":{"operationId":"get","summary":"Get Skill","description":"Retrieve one skill at its latest version, or at the version named by\n`?version=`. An unknown version is a 404 `skill_version_not_found`\nrather than a fall back to the latest - a caller asking for one body\nis never handed a different one.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"version","in":"query","description":"Read this version instead of the latest.","required":false,"schema":{"type":"integer","minimum":1}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Skill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Skill","description":"Update a skill. The two halves behave differently on purpose:\n`name` and `description` are labels and edit in place, while\n`instructions`, `tool_ids`, `knowledge_base_ids` and `variables` are\nthe body - touching any of them MINTS A NEW VERSION and leaves every\nexisting attachment on the version it pinned. Agents move when someone\nmoves them, never because the catalog changed.\nA present list replaces rather than appends.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The skill, at the version this call left it on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Skill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSkillRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Skill","description":"Delete a skill. Refused with 409 `skill_in_use` while any agent still\nhas it attached: deleting it would change every one of their prompts at\nonce, which is what the version pin exists to prevent. Detach it first.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/skills/{skill_id}/versions":{"get":{"operationId":"list_versions","summary":"List Skill Versions","description":"List a skill's version history, newest first. Each entry summarises its\nbody rather than returning it; read a full body with\n`GET /v1/skills/{skill_id}?version=`. Versions are minted by updating\nthe skill, never created directly.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["skills"],"parameters":[{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSkillVersionsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/skills":{"get":{"operationId":"list_skills","summary":"List Agent Skills","description":"List the skills attached to this agent, oldest attachment first - the\norder dispatch renders them in. Each carries the version it is pinned to\nand the version the catalog currently holds; when the two differ an\nupgrade is available, and nothing upgrades on its own.\nCursor-paginated; walk pages while `has_more` is true.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["agents"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentSkillsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/skills/{skill_id}":{"put":{"operationId":"attach_skill","summary":"Attach Skill To Agent","description":"Attach a skill to this agent, pinned to one version. Omit `version` to\npin whatever is latest AT THIS MOMENT - the pin is a number, never a\nlive pointer, so a later edit to the skill does not reach this agent.\nIdempotent: attaching again re-pins.\n\nThree refusals worth knowing. The agent and the skill must be in the\nsame project (409 `cross_project_reference`). The skill's tools must\nnot collide by name with anything the agent already answers to\n(409 `skill_tool_name_conflict`) - the model picks a tool by name.\nAnd any knowledge base the skill names must already be attached to the\nagent (409), because retrieval re-authorizes against the agent's own\nattachments and would refuse a skill-contributed base mid-call.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["agents"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The attachment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSkill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachSkillRequest"}}}}},"patch":{"operationId":"update_skill","summary":"Update Agent Skill","description":"Move this agent's pin to another version, or switch the skill off\nwithout detaching it (the pin survives, so switching it back on\nrestores the same body). Moving the pin re-runs the tool-name and\nknowledge checks against the version being moved to.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["agents"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The attachment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentSkill"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentSkillRequest"}}}}},"delete":{"operationId":"detach_skill","summary":"Detach Skill From Agent","description":"Detach a skill from this agent. The skill itself survives, as do its\nattachments to other agents.\nDark launch: requires the `skills_access` entitlement (402 `skills_not_in_plan` otherwise).\n","tags":["agents"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"skill_id","in":"path","description":"Skill id (prefixed external id, `skill_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Detached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores":{"get":{"operationId":"list","summary":"List Stores","description":"List the stores in the caller's workspace, most recently updated\nfirst. A store is the durable document + asset store a durable run\nwrites to (through its store tools) and a hosted-API route serves.\nCursor-paginated; walk pages while `has_more` is true.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListStoresResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Store","description":"Create a store. Names are unique per workspace among live stores.\nBounded by the workspace's store limit (409 `store_limit_reached`).\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStoreRequest"}}}}}},"/v1/stores/{store_id}":{"get":{"operationId":"get","summary":"Get Store","description":"Retrieve one store.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Store","description":"Rename or re-describe a store (merge-patch: absent fields are unchanged).\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Store"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStoreRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Store","description":"Delete a store and everything in it. The store disappears from every\nread at once; its objects are purged durably afterwards.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores/{store_id}/collections":{"get":{"operationId":"collections","summary":"Get Collections","description":"The collections a store holds with their live document counts: a\nbounded roll-up derived from the documents, not a resource list.\nCollections are implicit - writing a document names one.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListStoreCollectionsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores/{store_id}/collections/{collection}/documents":{"get":{"operationId":"listDocuments","summary":"List Documents","description":"List a collection's documents, most recently updated first, bodies\nincluded unless `include_data=false`. For filters and ordering use\n`queryDocuments`.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_data","in":"query","description":"Set `false` to return index rows only (no `data`).","required":false,"schema":{"type":"boolean"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryStoreDocumentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createDocument","summary":"Create Document","description":"Write a document, minting an id when none is given. Prefer `putDocument`\nwith a stable id you derive from the content, so a retry never\nduplicates. Bounded by the store's document limit (409\n`store_document_limit_reached`).\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The written document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteStoreDocumentRequest"}}}}}},"/v1/stores/{store_id}/collections/{collection}/documents/query":{"post":{"operationId":"queryDocuments","summary":"Query Documents","description":"Query a collection: filter on top-level scalar fields with `where`,\norder on one with `order_by`, page with `limit` + `cursor`. Values\ncompare with JSON semantics (numbers as numbers, strings lexically, so\nISO-8601 timestamps order as dates). A read carrying a body, hence a\nPOST on a literal sub-path; `query` and `batch` are reserved document ids.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryStoreDocumentsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocumentQuery"}}}}}},"/v1/stores/{store_id}/collections/{collection}/documents/batch":{"post":{"operationId":"batchDocuments","summary":"Batch Write Documents","description":"Apply up to 50 writes (`set`, `update`, `delete`) in order. Not atomic\nacross writes: it stops at the first failure, whose index the error\nnames, and reports every write it applied.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreBatchResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreBatchRequest"}}}}}},"/v1/stores/{store_id}/collections/{collection}/documents/{document_id}":{"get":{"operationId":"getDocument","summary":"Get Document","description":"Read one document with its body.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Document id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"putDocument","summary":"Put Document","description":"Write (create or replace) a document at this id. 201 for a new\ndocument, 200 for a new version of an existing one. `data` must be a\nJSON object of at most 256 KiB; its top-level scalar fields become the\nqueryable projection.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Document id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A new version of the existing document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteStoreDocumentRequest"}}}}},"patch":{"operationId":"updateDocument","summary":"Update Document","description":"Shallow-merge `data` into the stored document (a JSON null removes a\nfield) and write the result as a new version.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Document id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreDocument"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteStoreDocumentRequest"}}}}},"delete":{"operationId":"deleteDocument","summary":"Delete Document","description":"Delete one document.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["documents"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"collection","in":"path","description":"Collection name (lowercase letters, digits, `_`, `-`).","required":true,"schema":{"type":"string"}},{"name":"document_id","in":"path","description":"Document id.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores/{store_id}/assets":{"get":{"operationId":"listAssets","summary":"List Assets","description":"List a store's assets, newest first.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListStoreAssetsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"uploadAsset","summary":"Upload Asset","description":"Upload one file (multipart form field `file`, at most 25 MiB). The\npart's content type is stored and served back from `bytes_path`.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The stored asset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreAsset"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body exceeded a per-endpoint size limit (e.g. KB\ndocument upload cap, batch-call CSV cap, audio-asset WAV cap).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"The file to store (at most 25 MiB)."}},"required":["file"]}}}}}},"/v1/stores/{store_id}/assets/{asset_id}":{"get":{"operationId":"getAsset","summary":"Get Asset","description":"Read an asset's metadata.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"asset_id","in":"path","description":"Asset id (prefixed external id, `asset_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreAsset"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteAsset","summary":"Delete Asset","description":"Delete an asset.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"asset_id","in":"path","description":"Asset id (prefixed external id, `asset_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/stores/{store_id}/assets/{asset_id}/bytes":{"get":{"operationId":"getAssetBytes","summary":"Get Asset Bytes","description":"Stream the asset's bytes with its stored content type. Private and uncacheable.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["assets"],"parameters":[{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"asset_id","in":"path","description":"Asset id (prefixed external id, `asset_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The asset bytes.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/stores":{"get":{"operationId":"listAttached","summary":"List Agent Stores","description":"List the stores attached to an agent, in attach order. A durable run\nof the agent is offered the store tools for exactly these.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAgentStoresResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agents/{agent_id}/stores/{store_id}":{"post":{"operationId":"attach","summary":"Attach Store","description":"Attach a store to an agent (idempotent). The agent and the store must\nshare a project (409 `cross_project_reference` otherwise).\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Attached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"detach","summary":"Detach Store","description":"Detach a store from an agent.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["stores"],"parameters":[{"name":"agent_id","in":"path","description":"Agent id (prefixed external id, `agent_...`).","required":true,"schema":{"type":"string"}},{"name":"store_id","in":"path","description":"Store id (prefixed external id, `store_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Detached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apis":{"get":{"operationId":"list","summary":"List Hosted APIs","description":"List the hosted APIs in the caller's workspace, most recently updated\nfirst. A hosted API is the API you assemble: a slug that becomes\n`https://.`, the routes it answers, and the\nconsumer keys your own callers present.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter by workspace project: omit for every project you can reach,\npass the literal `default` for resources in the implicit Default\nproject only, or a `proj_...` id for that project's resources.\nReturns 404 project_not_found for a malformed id and for any filter\noutside your reach: a project-pinned API key or service-account key\nreaches only its pinned project, and a member holding project grants\nreaches only the granted projects, so neither can name `default`.\nThat 404 is the same in every case and does not reveal whether such\na project exists - outside your reach a project is answered as\nnonexistent, never as forbidden. Inside it, a well-formed id that\nmatches no project yields an empty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListHostedAPIsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Hosted API","description":"Create a hosted API. The slug is a DNS label, globally unique on the\nshared domain (409 `hosted_api_slug_taken`) and immutable afterwards.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPI"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHostedAPIRequest"}}}}}},"/v1/apis/{api_id}":{"get":{"operationId":"get","summary":"Get Hosted API","description":"Retrieve one hosted API.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPI"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Hosted API","description":"Update a hosted API (merge-patch). Switching to `public` is refused\nwhile a `run` route exists: an anonymous caller must not start runs\nthat spend the workspace's budget.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPI"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHostedAPIRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Hosted API","description":"Delete a hosted API; its host stops answering at once.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apis/{api_id}/openapi":{"get":{"operationId":"getOpenApi","summary":"Get Hosted API OpenAPI Document","description":"The OpenAPI 3.1 document describing the hosted API's routes - the same\ndocument the API serves to its consumers at `/openapi.json`.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["hostedApis"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The OpenAPI document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"description":"Any type"}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apis/{api_id}/routes":{"get":{"operationId":"listRoutes","summary":"List Routes","description":"List a hosted API's routes in creation order.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListHostedAPIRoutesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createRoute","summary":"Create Route","description":"Add a route: a method + path answered by a resolver. `store_query` and\n`store_document` serve a store; `run_latest` serves the newest\nstructured output of a schedule trigger's runs; `run` starts a run\nthrough a webhook trigger per request (POST only, never on a public\nAPI) and waits up to `wait_seconds` before answering 202 with a\nhandle to poll at `/_runs/{run_id}`. Where-clause values and the\ndocument id may be `{{query.x}}`, `{{path.x}}` or `{{body.x}}`\ntemplates bound from the consumer's request; a clause whose template\nis absent is skipped.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created route.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIRoute"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHostedAPIRouteRequest"}}}}}},"/v1/apis/{api_id}/routes/{route_id}":{"get":{"operationId":"getRoute","summary":"Get Route","description":"Retrieve one route.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"route_id","in":"path","description":"Route id (prefixed external id, `route_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIRoute"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateRoute","summary":"Update Route","description":"Update a route (merge-patch); a changed method or resolver is re-validated.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"route_id","in":"path","description":"Route id (prefixed external id, `route_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIRoute"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHostedAPIRouteRequest"}}}}},"delete":{"operationId":"deleteRoute","summary":"Delete Route","description":"Delete a route.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["routes"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"route_id","in":"path","description":"Route id (prefixed external id, `route_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apis/{api_id}/keys":{"get":{"operationId":"listKeys","summary":"List Consumer Keys","description":"List a hosted API's consumer keys, newest first, revoked ones included.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["keys"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListHostedAPIKeysResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createKey","summary":"Create Consumer Key","description":"Mint a consumer key (`ck_...`) for the API's own callers. The plaintext\n`secret` is present in this response only; every later read shows the\nmasked `key_hint`.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["keys"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The created key, with its one-time secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIKey"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateHostedAPIKeyRequest"}}}}}},"/v1/apis/{api_id}/keys/{consumer_key_id}/revoke":{"post":{"operationId":"revokeKey","summary":"Revoke Consumer Key","description":"Revoke a consumer key; idempotent. Requests carrying it are refused from now on.\nDark launch: requires the `hosted_apis_access` entitlement (402 `hosted_apis_not_in_plan` otherwise).\n","tags":["keys"],"parameters":[{"name":"api_id","in":"path","description":"Hosted API id (prefixed external id, `api_...`).","required":true,"schema":{"type":"string"}},{"name":"consumer_key_id","in":"path","description":"Consumer key id (prefixed external id, `ckey_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostedAPIKey"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The workspace has insufficient credits, or the request needs a\nplan tier the workspace is not on (e.g. voice cloning). Distinct\nfrom `Forbidden` so SDK consumers can drive upgrade UX.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts":{"get":{"operationId":"list","summary":"List Contacts","description":"List the workspace's contacts, most-recently-seen first, each with its\nlive identifier set. Cursor-paginated: omit `cursor` for the first\npage; walk pages while `has_more` is true (default page size 50, max\n200).\n\nContacts are workspace-level, not per-agent: the same person reached\non two agents and on two channels is one row here, while\n`/v1/agents/callers` stays the per-agent observation of them. Filter\nby `agent_id` for the people one agent has reached; each row names\nthe agents that reached it in `agent_ids`.\n","tags":["contacts"],"parameters":[{"name":"agent_id","in":"query","description":"Narrow to the people this agent has reached: contacts with a\nper-agent caller row on it. Prefixed `agent_...` id.\n","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Prefix search across the display name AND the identifier values, so\nthe same box finds \"Sarah\" and \"+1202555\". `%`/`_` in the input are\nescaped as literals.\n","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"One page of the workspace's contacts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListContactsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/{contact_id}":{"get":{"operationId":"get","summary":"Get Contact","description":"Fetch one contact by id, scoped to the caller's workspace, with its\nlive identifiers. Returns 404 for missing, merged-away, or\nforeign-workspace contacts - contact existence is never leaked across\nworkspaces.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Contact","description":"Rename a contact or replace its metadata. Omitted fields are\nunchanged; an empty `display_name` clears the column; `metadata`\nreplaces the stored blob in full.\n\nIdentifiers are NOT editable here - each one is an assertion of record\nwith its own provenance, so they are attached and detached through\ntheir own sub-resource.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated contact.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Contact (GDPR purge)","description":"Erase a person: the contact, every identifier that resolves to them,\nevery per-agent caller record of them, and every memory ever extracted\nabout them - on every agent and every channel, in one call.\n\nThis is the right-to-be-forgotten path. `DELETE\n/v1/agents/callers/{caller_id}` erases one agent's record of someone\nand has to be fired once per agent and once per channel; this reaches\nall of them, because \"everything about this person\" is a question the\ncontact model can answer.\n\nTheir calls, chats and durable runs are scrubbed rather than deleted,\nthe same way and for the same reason: the row keeps its id, status,\ntimings and usage - the accounting the workspace was billed on, which\nis not the person's to erase - and loses everything that describes\nthem. For a call that means the transcript, the identity, the phone\nnumbers, the frozen prompt and variables; for a run, the instruction,\nthe variables, the output and every journalled step. The facts\nextracted from either, and the copies frozen into past prompts, go\nwith them.\n\nThe transcript and the recording of a call both stop being readable\nfrom the moment of the request, and a call still in progress can no\nlonger append to its transcript, so a turn that lands after the\nerasure cannot put the person back.\n\nBoth are destroyed 30 days later, not immediately. This is\ndeliberate: an erasure that destroyed them on request would be a way\nto erase evidence of what happened on the call, so they stop being\nreachable at once and stay recoverable by us for the same 30 days a\ndeleted agent or a purged project does. Absent an erasure, a\nrecording is kept for one year from the call and removed after that.\n\n`conversations_erased` and `messages_erased` count what stopped being\nreadable, which is what the request asked for; the rows behind them\nare removed on the schedule above.\n\nThe identifiers are tombstoned as part of the purge, which is what\nstops the person's phone number ever resolving again: a later merge\nnaming the purged contact moves rows that are already dead rather than\nbringing a link back to life.\n\nIdempotent: re-deleting an already-purged contact returns all zeros\nrather than a 404, so a client that never saw the first response can\nsafely repeat it.\n\nReturns `200` with the row counts rather than a bodiless `204`: the\ncounts are the audit evidence a privacy operator needs, and re-reading\na corpus you just asked us to erase is not a way to obtain them. Same\ndeliberate exception `DELETE /v1/agents/callers/{caller_id}` takes.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Purge completed; row counts in the body (GDPR audit evidence).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteContactResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/{contact_id}/identifiers":{"post":{"operationId":"attachIdentifier","summary":"Attach Contact Identifier","description":"Assert that a typed identifier denotes this person - the mechanism by\nwhich a phone number becomes linked to the application user it belongs\nto, so a call from that number resolves to everything the agent\nalready knows.\n\nThe server stamps `asserted_by: api` and `verified_at` because the\nrequest authenticated: your backend identified the end user before\ntelling us. Neither can be supplied by the client.\n\nAttaching an identifier another contact already holds is refused with\n`409 contact_identifier_conflict` naming the holder - never a silent\nre-point, because that would be a merge nobody asked for, and a wrong\nmerge discloses one person's facts to another. Re-attaching an\nidentifier this same contact already holds succeeds unchanged.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The attached identifier.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactIdentifier"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachContactIdentifierRequest"}}}}}},"/v1/contacts/{contact_id}/identifiers/{identifier_id}":{"delete":{"operationId":"detachIdentifier","summary":"Detach Contact Identifier","description":"Reverse a link. A wrong link is a disclosure, so undoing one is a\nfirst-class operation rather than a support ticket: the identifier\nstops resolving to this contact immediately, and a future session on\nthat channel mints a fresh contact instead.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"Contact id (prefixed external id, `contact_...`).","required":true,"schema":{"type":"string"}},{"name":"identifier_id","in":"path","description":"Contact identifier id (prefixed external id, `ident_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Identifier detached.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contacts/{contact_id}/merge":{"post":{"operationId":"merge","summary":"Merge Contacts","description":"Fold one contact into another: every identifier, caller projection,\nconversation and memory that pointed at `from_contact_id` is\nre-pointed at the contact in the URL, the survivor's first/last-seen\nwindow widens to span both, and the merged-away contact is\ntombstoned.\n\nThis is the deliberate counterpart to the refusal on identifier\nattach. Merging is destructive and one-way, so it is never inferred -\nthe customer is the only party who knows two records are one person.\n\nAll-or-nothing, and safe to retry: a repeat call finds nothing left\npointing at the merged-away contact and returns the same survivor with\na zeroed tally. Send an `Idempotency-Key` header to have a retry\nreplay the first response verbatim.\n","tags":["contacts"],"parameters":[{"name":"contact_id","in":"path","description":"The SURVIVING contact's id (prefixed external id, `contact_...`).\nEverything from the contact named in the body lands here.\n","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"The surviving contact plus the row counts that moved onto it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeContactsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MergeContactsRequest"}}}}}},"/v1/workspaces/current/entitlements":{"get":{"operationId":"getEntitlements","summary":"Get Workspace Entitlements","description":"The per-tier entitlements catalog plus the caller's RESOLVED entitlements\nfor the current workspace (tier defaults composed with any per-tenant\noverride). Readable with an API key as well as a console session: it is\nhow an integration learns what it may use before a feature endpoint\nanswers `402`. Branch on `current.durable_runs_access`,\n`current.text_agent_access`, `current.hosted_apis_access` and\n`current.skills_access`, and size a run from `current.max_run_turns`.\nThe console renders quota affordances and upgrade-card limits from the\nsame single server-authoritative source instead of a hardcoded mirror.\n","tags":["workspaces"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The entitlements catalog + the current workspace's resolved entitlements.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntitlementsResponse"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/current/contact-resolver":{"get":{"operationId":"getResolver","summary":"Get Contact Resolver","description":"Read the workspace's contact-resolver configuration - the endpoint\nSpeechify asks \"who is on this call\" at session start.\n\nThe signing secret is never returned here. It is revealed once, by the\ncall that mints it, and again by a rotation.\n","tags":["contacts"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The configured resolver.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResolver"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"setResolver","summary":"Set Contact Resolver","description":"Configure the endpoint Speechify asks to identify an inbound caller.\n\nOn an inbound call from a number nobody has linked yet, Speechify\nPOSTs `{channel, identifier, agent_id, conversation_id}` to this URL,\nsigned with the same `Speechify-Signature` scheme as webhooks, and\nexpects `{external_id, display_name?, variables?}` back. The contact\nattaches before the first token is generated, so `{{memory}}` is\npopulated on the agent's opening line.\n\nThe call FAILS OPEN on a sub-second deadline. A slow, unreachable or\nmalformed response degrades to a call with no contact and no memory\nblock - never a failed call and never a delayed answer. Answers are\ncached briefly per (workspace, identifier kind, identifier value), so a\nrepeat caller does not re-hit your backend; they are scoped to the\nconfiguration that produced them, so changing the URL takes effect at\nonce rather than after the cache window.\n\nOne resolver per workspace: this endpoint sets it, and re-sending the\nsame body is a no-op. Configuring for the first time mints a signing\nsecret and returns it once; later edits keep the secret you already\ndeployed a verifier against and return it blank.\n\nThe URL must be `https` and must resolve to a public address. A\nloopback, private-range or cloud-metadata host is rejected here, and\nblocked again at dial time.\n","tags":["contacts"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The stored configuration. `secret` is present only when this call\ncreated the configuration; store it, it is not shown again.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResolver"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutContactResolverRequest"}}}}},"delete":{"operationId":"deleteResolver","summary":"Delete Contact Resolver","description":"Remove the workspace's resolver. Calls stop asking at the next\ndispatch and behave exactly as they did before it was configured;\ncontacts already linked by earlier answers are untouched.\n","tags":["contacts"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/workspaces/current/contact-resolver/rotate-secret":{"post":{"operationId":"rotateResolverSecret","summary":"Rotate Contact Resolver Secret","description":"Mint a new signing secret for the workspace's resolver and return it\nonce. The previous secret stops signing immediately, so deploy the new\none to your verifier before rotating - a request your endpoint cannot\nverify should be rejected, and a rejected resolver call degrades to a\ncall with no contact.\n","tags":["contacts"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The configuration carrying the new secret (shown once).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResolver"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/endpoints":{"get":{"operationId":"list","summary":"List webhook endpoints","description":"The caller's workspace's registered webhook endpoints. Cursor-paginated:\nomit `cursor` for the first page; walk pages while `has_more` is true\n(default page size 50, max 200). The signing `secret` is never returned\nhere \u2014 it is shown only when an endpoint is created or its secret is\nrotated. Filter by delivery scope with `project_id`.","tags":["endpoints"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"project_id","in":"query","description":"Filter endpoints by project scope: omit for everything the caller\nmay see, pass the literal `shared` for workspace-wide endpoints\nonly, or a `proj_...` id for endpoints scoped to that project.\nEndpoints have no Default project - a null `project_id` means\nworkspace-wide, so the literal here is `shared`, never `default`.\nReturns 404 project_not_found for a malformed id and for any\nproject outside your reach: a project-pinned API key or\nservice-account key reaches only its pinned project, and a member\nholding project grants reaches only the granted projects. That 404\nis the same in every case and does not reveal whether such a\nproject exists - outside your reach a project is answered as\nnonexistent, never as forbidden. `shared` is always inside your\nreach. Inside it, a well-formed id that matches nothing yields an\nempty page.\n","required":false,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The workspace's webhook endpoints.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhookEndpointsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create webhook endpoint","description":"Register a webhook endpoint. Speechify mints an HMAC signing secret\nand returns it in the response `secret` field \u2014 exactly once. Store it\nthen; subsequent reads omit it (rotate it with the rotate-secret action\nif lost). Select events via `enabled_events`: a list of catalog event\nnames or `[\"*\"]` for every event. Optionally scope delivery to one\nproject with `project_id`; omit it for a workspace-wide endpoint that\nreceives every project's events. Limited to 50 endpoints per workspace.\n","tags":["endpoints"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created endpoint, including its one-time signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The request was well-formed but semantically rejected -\ntypically a referential integrity violation (e.g. flow node\nreferences an audio asset in another workspace) or a state\nmachine refusal.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookEndpointRequest"}}}}}},"/v1/webhooks/endpoints/{webhook_endpoint_id}":{"get":{"operationId":"get","summary":"Get webhook endpoint","description":"Fetch a webhook endpoint by id. The signing secret is never returned.","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The webhook endpoint (without the signing secret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update webhook endpoint","description":"Partial update; omitted fields are left unchanged. Set `disabled` to pause delivery without deleting the endpoint.","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated endpoint (without the signing secret).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookEndpointRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete webhook endpoint","description":"Delete a webhook endpoint. In-flight deliveries stop; returns 204 on success.","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Endpoint deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/endpoints/{webhook_endpoint_id}/rotate-secret":{"post":{"operationId":"rotateSecret","summary":"Rotate webhook endpoint secret","description":"Mint a new HMAC signing secret for the endpoint and return it in the\nresponse `secret` field (shown exactly once). The previous secret stops\nsigning immediately, so accept both during your cutover window.\n","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint, including its new one-time signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/endpoints/{webhook_endpoint_id}/deliveries":{"get":{"operationId":"listDeliveries","summary":"List webhook endpoint deliveries","description":"Delivery attempts for one webhook endpoint, newest first. One row per\n(endpoint, event, resource), updated in place across retries. Each row\nincludes the exact request payload and signed headers Speechify sent\n(`request_body`, `request_headers`) and the response your server returned\n(`last_status_code`, `last_response_body`, `last_response_headers`), so\nyou can verify the signature and debug failures. Cursor-paginated: omit\n`cursor` for the first page; walk pages while `has_more` is true (default\npage size 50, max 200).\n\nAn endpoint that was not a target of an event has no row for it: a\nproject-scoped endpoint records nothing for another project's events.\nAn empty list therefore means either nothing matched or nothing\nhappened. To tell them apart, list the project's own activity first\n(`GET /v1/agents/runs?project_id=\u2026`, the conversations list): activity\nthere with no delivery here is a defect to report; none there means\nthere was nothing to deliver.","tags":["endpoints"],"parameters":[{"name":"webhook_endpoint_id","in":"path","description":"Webhook endpoint id (prefixed `whe_\u2026`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The endpoint's delivery attempts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListWebhookEndpointDeliveriesResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects":{"get":{"operationId":"list","summary":"List Projects","description":"List the workspace's projects, newest first. The implicit Default\nproject is not a row and is never listed; resources with no\n`project_id` live in it. Archived projects are hidden unless\n`include_archived=true`, and purged ones unless\n`include_purged=true`. Cursor-paginated: omit `cursor` for the\nfirst page; walk pages while `has_more` is true (default page size\n50, max 200).\n","tags":["projects"],"parameters":[{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"include_archived","in":"query","description":"Include archived projects. Defaults to `false`, so the list shows\nonly live projects; archived ones stay readable by id.\n","required":false,"schema":{"type":"boolean","default":false}},{"name":"include_purged","in":"query","description":"Include purged projects that are still inside their 30-day restore\nwindow, each carrying the `purged_at` stamp its deadline is\nmeasured from. Defaults to `false`, so the list shows only projects\nthat still exist. This is the only read that returns a purged\nproject: the by-id read answers 404 for one, and a project past its\nwindow is never listed, because it is awaiting permanent deletion\nand a restore would refuse it. Independent of `include_archived`:\na purge runs only from the archived state, so asking for purged\nprojects never means asking for every archived one as well.\n","required":false,"schema":{"type":"boolean","default":false}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The workspace's projects.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListProjectsResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create","summary":"Create Project","description":"Create a project in the caller's workspace. Names are unique per\nworkspace (case-insensitive). A workspace holds at most 100 live\nprojects; at the cap the create refuses with\n`409 project_limit_reached` until one is deleted.\n","tags":["projects"],"parameters":[{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProjectRequest"}}}}}},"/v1/projects/{project_id}":{"get":{"operationId":"get","summary":"Get Project","description":"Fetch one project by id, scoped to the caller's workspace. Returns\n404 for missing or foreign-workspace projects \u2014 project existence\nis never leaked across workspaces.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update","summary":"Update Project","description":"Edit a project in place - its name, its monthly spend limit, or its\ncapacity ceilings - keeping the same id so every grouped resource\nfollows the edit with no re-pointing. Names are unique per\nworkspace (case-insensitive). The limit fields require\n`billing.manage`; a capacity ceiling above the workspace's own is\nrefused, since it could never apply.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The updated project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProjectRequest"}}}}},"delete":{"operationId":"delete","summary":"Delete Project","description":"Delete a project in one of two modes.\n\n**Detach** (the default, no body or `mode: detach`): only the\ngrouping row is removed; every resource in the project moves to the\nimplicit Default project. Refused with 409\n`project_has_scoped_credentials` while an API key, service account,\nvault credential, webhook endpoint, member grant or pending invite is\nscoped to the project, because detaching any of those would silently\nwiden it.\n\n**Purge** (`mode: purge` with `confirm` equal to the project's name):\navailable only on an ARCHIVED project, because a teardown needs a\nstate you can sit in and reverse first; a live project is refused with\nthe coded `409 project_not_archived`. Archive the project, confirm it\nis the one you mean, then purge. The project is removed WITH its\ncontents in one transaction. Agents\n(with their tests), knowledge bases (with their documents), tools,\naudio assets, scoped webhook endpoints and scoped vault credentials\nare deleted; API keys and service accounts pinned to the project are\nrevoked; member grants and pending-invite scopes on the project are\ncleared. The runs and conversations of a deleted agent go with that\nagent, transcripts and recordings included, and stop being readable\nthe moment the purge lands - a detach leaves them alone, because a\ndetached project's agents are still live. Callers, suite runs and\nmemories are operational records and survive either mode, moving to\nthe Default project; a batch call survives the project the same way,\nbut not the agent it dialled for, so a purge's agents take their\ncampaigns with them once they are permanently removed. Refused with 409 while a phone number is attached\n(release or move it first), while a member's only project grant is\nthis one, or while a live invite carries only this project (clearing\neither would widen that person to the whole workspace, the invite one\nacceptance earlier). Preview either mode with\n`GET /v1/projects/{project_id}/teardown`.\n\n**A purge is recoverable for 30 days.** The project disappears from\nevery list and read immediately, and its name is freed for reuse, but\nthe project and its resources are kept and permanently deleted only\nonce the window closes. `POST /v1/projects/{project_id}/restore`\nbrings the project and its resources back inside that window; the\ncredentials the purge revoked and the grants it cleared stay that way.\n\nThe 409 carries the blockers under `error.details.blockers` (`kind`,\ntyped `id`, `name`, and the `blocks` modes each refuses), their total\nunder `error.details.blocker_count`, and, for existing clients, the\nsame rows under `error.details.credentials`. The lists are capped at\n50 rows; the counts are not, and the refusal is decided on the count.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Project deleted. In detach mode its resources moved to the Default\nproject; in purge mode they went with it.\n","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteProjectRequest"}}}}}},"/v1/projects/{project_id}/archive":{"post":{"operationId":"archive","summary":"Archive Project","description":"Archive a project. From then on nothing new starts or bills inside\nit: conversation and widget sessions, outbound and inbound calls,\nbatch calls, test runs, knowledge-base imports, and synthesis or\ngateway requests on a credential pinned to the project are all\nrefused with the coded `409 project_archived`. Work already in\nflight is left to finish. Everything in the project stays readable\nand its configuration stays editable, and the project still answers\nby id.\n\nIdempotent: archiving an archived project is a no-op. Reverse it\nwith the unarchive operation.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The project is archived.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/unarchive":{"post":{"operationId":"unarchive","summary":"Unarchive Project","description":"Lift a project's archive so work and spend resume inside it.\nIdempotent: unarchiving a live project is a no-op.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The project is live.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/restore":{"post":{"operationId":"restore","summary":"Restore Project","description":"Walk back a purge. A purged project is recoverable for 30 days: its\nrow and its contents are kept, hidden from every list and read, and\npermanently deleted only once the window expires.\n\n**What comes back:** the project and its resources - agents (with\ntheir tests), knowledge bases, tools and audio assets - exactly the\nrows the purge removed. A resource you had deleted yourself before the\npurge stays deleted.\n\n**What does NOT come back, on purpose:** every credential the purge\nrevoked stays revoked, and every grant it cleared stays cleared. API\nkeys and service accounts pinned to the project are not re-issued,\nvault credentials and webhook endpoints scoped to it are not\nundeleted, and member grants and pending-invite scopes are not\nrestored. Bringing a credential or a grant back would re-grant access\nsomebody deliberately ended, so the restore reports them under\n`still_revoked` instead. Re-create the credentials and re-grant the\nmembers the project still needs.\n\nThe project returns ARCHIVED, the state it was purged from, so nothing\ndispatches or bills inside it until you unarchive it.\n\nRefused with `409 project_not_purged` when the project was never\npurged, `409 project_restore_window_expired` once the 30 days have\npassed, and `409 project_name_taken` when another project has taken\nthis one's name since the purge (a purge frees the name immediately -\nrename the project holding it, then restore).\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The project is restored, archived, and holds its resources again.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectRestore"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/teardown":{"get":{"operationId":"teardown","summary":"Project Teardown Plan","description":"What deleting this project would do, in either mode, without doing\nit: whether a purge is available right now (`archived` and\n`purge_available`), every row holding the project open and which\ndelete modes it refuses, what a purge would remove (a count per kind\nand a bounded sample of names), and the operational records a purge\nkeeps. Nothing is mutated. Takes the same permission as the delete.\nReturns 404 for missing or foreign-workspace projects.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The teardown plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectTeardown"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/stats":{"get":{"operationId":"stats","summary":"Project Stats","description":"What this project contains: a live count of every resource kind\ngrouped under it. Credentials pinned to the project are deliberately\nnot counted here, because listing API keys and service accounts is an\naccount-level operation with its own authentication; use those\nendpoints directly. A derived resource (a conversation, caller, agent\ntest, or batch call) counts against the project it was created in,\neven if its agent has since moved to another project. Returns 404 for\nmissing or foreign-workspace projects.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The project's live resource counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectStats"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/promote":{"post":{"operationId":"promote","summary":"Promote Project","description":"Copy this project's configuration into another project in the same\nworkspace, creating equivalent resources there. Use it to move a\nstaging environment into production, or to seed one client's project\nfrom another's.\n\nCopied: agents with every setting (prompt, voice, model, memory and\nconsent settings, builtin tools, tool and knowledge-base bindings, the\ndraft and published flow versions, test definitions), tool\ndefinitions, knowledge bases with their folders (URL sources are\nre-imported in the target through the normal import path, so the\nresponse lists the import jobs to poll), audio assets (the object is\ncopied), and the webhook endpoints scoped to the source.\n\nNever copied: vault credentials, API keys and service accounts, phone\nnumbers and SIP trunks, conversations, callers, memories, call and\ntest history, batch calls, member grants, spend limits, uploaded or\npasted knowledge-base documents, and every secret. A copied tool or\nagent that referenced a credential has the reference cleared, every\nserver-minted signing secret is minted fresh, and each such item is\nlisted under `needs_attention` so nothing is silently half-configured.\n\nA copy keeps its name. Where the target already holds a resource of\nthe same kind and name, the copy is suffixed (`name (2)`, or `name_2`\nfor tools) and listed under `needs_attention` with reason `renamed`;\na target tool whose definition is identical to the source's is bound\ninstead of duplicated and appears under `reused`.\n\nThe copy runs in one transaction. A project holding more than 200\nresources (agents, tools, knowledge bases, URL sources, audio assets,\nwebhook endpoints and tests together) answers `409\nproject_too_large_to_promote`. Send an `Idempotency-Key` header to\nmake a retry safe: the first manifest is replayed rather than the\nproject copied twice. Both projects must be reachable to the caller;\na project-pinned credential cannot promote.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"The source project whose configuration is copied.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A client-generated key (an opaque string, max 255 chars) that makes a\nside-effect POST safe to retry: the server runs the operation exactly\nonce and replays the first response (its status and body) for 24 hours.\nReusing a key with a different request body, or while the first request\nis still in flight, returns `409 idempotency_conflict`. A replayed\nresponse carries the `Idempotent-Replayed: true` header.\n","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"What the promote created, reused, and left for a person to finish.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoteProjectResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current resource state - e.g.\nduplicate, optimistic-concurrency mismatch, or last-owner\nguard.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromoteProjectRequest"}}}}}},"/v1/projects/{project_id}/audit":{"get":{"operationId":"audit","summary":"Project Audit Trail","description":"Who changed this project's access or its lifecycle state, and when.\nNewest first. Covers the last 90 days; paginate by passing `cursor`\nfrom the previous response.\n\nEach entry names the SUBJECT (whose access changed) and the ACTOR (who\nchanged it), with the role the actor held at the time. When a change\nwas made by Speechify support acting on the workspace's behalf, the\nentry also carries that admin's email, so a support-initiated change\nnever reads as one a colleague made.\n\nRequires `members.manage_project_scope` (owner or admin): who widened\na member's access is a stronger fact than who currently holds it.\nReturns 404 for missing or foreign-workspace projects.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project id (prefixed external id, `proj_...`).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"One page of this project's audit trail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectAuditResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"A downstream dependency is degraded or the endpoint is\nintentionally disabled (e.g. phone-number purchase before\nops setup).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/projects/{project_id}/members":{"get":{"operationId":"listMembers","summary":"List Project Members","description":"List the workspace members granted access to this project, oldest\ngrant first. Paginate by passing `cursor` from the previous response.\n\nA member with no grants anywhere is workspace-wide and does not appear\nhere: this lists people who have been narrowed to specific projects,\nnot everyone who can reach this one.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project ID.","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor from a previous response.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Max items per page (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"One page of the members granted this project.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectMembersResponse"}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"grantMember","summary":"Grant Project Access","description":"Grant a workspace member access to this project. Once a member holds\nany grant, they see and touch only the projects they have been granted.\n\nRequires `members.manage_project_scope` (owner or admin). An owner or\nbilling admin cannot be scoped: both already hold workspace-wide access\nthat a project scope would contradict without restricting anything.\nIdempotent: granting a project the member already holds is a no-op.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project ID.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The member now holds this project.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"400":{"description":"The request was malformed or failed validation. The response\nbody is the standard `Error` envelope; for validation\nfailures `error.fields` enumerates the offending fields as\na `path -> message` map (code = `validation_failed`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantProjectMemberRequest"}}}}}},"/v1/projects/{project_id}/members/{user_id}":{"delete":{"operationId":"revokeMember","summary":"Revoke Project Access","description":"Remove a member's access to this project.\n\nA member who loses their last grant is not locked out - they return\nto workspace-wide access, because holding no grants is the unrestricted\nstate. To restrict someone, grant them the projects they should keep\nrather than revoking everything. Requires `members.manage_project_scope`.\n","tags":["projects"],"parameters":[{"name":"project_id","in":"path","description":"Project ID.","required":true,"schema":{"type":"string"}},{"name":"user_id","in":"path","description":"The member's prefixed user id, as returned by the members list.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.","required":true,"schema":{"type":"string"}},{"name":"Speechify-Version","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"The grant was removed.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Authentication is missing or invalid. The request did not\ncarry a recognised credential (console session token, API key,\nor worker JWT).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The credential authenticated, but is not authorised for this\nresource - typically a workspace-role gate (owner / admin\nrequired) or a cross-tenant access attempt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The referenced resource does not exist or is not visible to\nthe caller's workspace.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"servers":[{"url":"https://api.speechify.ai","description":"https://api.speechify.ai"}],"components":{"schemas":{"GetSpeechRequestAudioFormat":{"type":"string","enum":["wav","mp3","ogg","aac","pcm"],"default":"wav","description":"The format for the output audio. Note, that the current default is \"wav\", but there's no guarantee it will not change in the future. We recommend always passing the specific param you expect.","title":"GetSpeechRequestAudioFormat"},"GetSpeechRequestModel":{"type":"string","enum":["simba-3.0","simba-3.2"],"default":"simba-3.0","description":"Model used for audio synthesis. Defaults to `simba-3.0`, which is streaming-native and multilingual: it officially supports English plus `de-DE`, `es-ES`, `es-MX`, `fr-FR`, `it-IT` and `pt-BR`, and routes each request to its English or its multilingual training based on `language` (falling back to the voice's locale when `language` is omitted). `simba-3.2` is the streaming-native model with the lowest TTFB and richest expressivity, and the recommended Simba 3 model; it is English only, so a non-English voice returns 400.\n\nThe legacy Simba 1.6 models `simba-english` and `simba-multilingual` are retired from API version `2026-09-21`: naming one returns 400 `model_retired`. Pinning your API version to a date before `2026-09-21` keeps them working until **2026-11-21**, when both are switched off for every API version. Migrate to `simba-3.2` (English) or `simba-3.0` before then; call GET /v1/audio/models to see the set your workspace can select today.","title":"GetSpeechRequestModel"},"GetSpeechOptionsRequest":{"type":"object","properties":{"loudness_normalization":{"type":"boolean","default":false,"description":"Determines whether to normalize the audio loudness to a standard level.\nWhen enabled, loudness normalization aligns the audio output to the following standards:\nIntegrated loudness: -14 LUFS\nTrue peak: -2 dBTP\nLoudness range: 7 LU\nIf disabled, the audio loudness will match the original loudness of the selected voice, which may vary significantly and be either too quiet or too loud.\nEnabling loudness normalization can increase latency due to additional processing required for audio level adjustments."},"text_normalization":{"type":"boolean","default":true,"description":"Determines whether to normalize the text. If enabled, it will transform numbers, dates, etc. into words. For example, \"55\" is normalized into \"fifty five\".\nThis can increase latency due to additional processing required for text normalization."}},"description":"GetSpeechOptionsRequest is the wrapper for request parameters to the client","title":"GetSpeechOptionsRequest"},"AudioOutputFormat":{"type":"string","enum":["pcm_8000","pcm_16000","pcm_22050","pcm_24000","pcm_44100","pcm_48000","mp3_22050_32","mp3_22050_64","mp3_22050_96","mp3_22050_128","mp3_22050_160","mp3_22050_192","mp3_24000_32","mp3_24000_64","mp3_24000_96","mp3_24000_128","mp3_24000_160","mp3_24000_192","wav_24000","wav_48000","ulaw_8000","ogg_24000","aac_24000"],"description":"Audio output format as a `codec_sampleRate_bitrate` string, giving explicit control over sample rate and bitrate. `pcm_*` and `ulaw_8000` are headerless raw audio; `pcm_16000` and `ulaw_8000` are the telephony formats Twilio/LiveKit SIP expect.\n\n160 kbps is the highest bitrate an mp3 can carry at 22.05 and 24 kHz, so `mp3_22050_160` and `mp3_24000_160` are the maximum-fidelity mp3 formats; a request for `mp3_*_192` is encoded at 160 kbps and reported as the matching `mp3_*_160`. The two `mp3_*_160` formats are served by the Simba 3 models only.","title":"AudioOutputFormat"},"GetSpeechRequest":{"type":"object","properties":{"audio_format":{"$ref":"#/components/schemas/GetSpeechRequestAudioFormat","default":"wav","description":"The format for the output audio. Note, that the current default is \"wav\", but there's no guarantee it will not change in the future. We recommend always passing the specific param you expect."},"input":{"type":"string","maxLength":2000,"description":"Plain text or SSML to be synthesized to speech.\nRefer to https://docs.speechify.ai/docs/api-limits for the input size limits.\nEmotion, Pitch and Speed Rate are configured in the ssml input, please refer to the ssml documentation for more information: https://docs.speechify.ai/docs/ssml#prosody"},"language":{"type":"string","description":"Language of the input. Follow the format of an ISO 639-1 language code and an ISO 3166-1 region code, separated by a hyphen, e.g. en-US.\nPlease refer to the list of the supported languages and recommendations regarding this parameter: https://docs.speechify.ai/docs/language-support."},"model":{"$ref":"#/components/schemas/GetSpeechRequestModel","default":"simba-3.0","description":"Model used for audio synthesis. Defaults to `simba-3.0`, which is streaming-native and multilingual: it officially supports English plus `de-DE`, `es-ES`, `es-MX`, `fr-FR`, `it-IT` and `pt-BR`, and routes each request to its English or its multilingual training based on `language` (falling back to the voice's locale when `language` is omitted). `simba-3.2` is the streaming-native model with the lowest TTFB and richest expressivity, and the recommended Simba 3 model; it is English only, so a non-English voice returns 400.\n\nThe legacy Simba 1.6 models `simba-english` and `simba-multilingual` are retired from API version `2026-09-21`: naming one returns 400 `model_retired`. Pinning your API version to a date before `2026-09-21` keeps them working until **2026-11-21**, when both are switched off for every API version. Migrate to `simba-3.2` (English) or `simba-3.0` before then; call GET /v1/audio/models to see the set your workspace can select today."},"options":{"$ref":"#/components/schemas/GetSpeechOptionsRequest"},"output_format":{"$ref":"#/components/schemas/AudioOutputFormat","description":"The output audio format as a `codec_sampleRate_bitrate` string. Takes precedence over `audio_format` when set."},"voice_id":{"type":"string","description":"Id of the voice to be used for synthesizing speech. Refer to /v1/voices endpoint for available voices"}},"required":["input","voice_id"],"description":"Request body for POST /v1/audio/speech.","title":"GetSpeechRequest"},"GetSpeechResponseAudioFormat":{"type":"string","enum":["wav","mp3","ogg","aac","pcm","ulaw"],"description":"The codec of the audio data","title":"GetSpeechResponseAudioFormat"},"NestedChunk":{"type":"object","properties":{"end":{"type":"integer","format":"int64"},"end_time":{"type":"number","format":"double"},"start":{"type":"integer","format":"int64"},"start_time":{"type":"number","format":"double"},"type":{"type":"string"},"value":{"type":"string"}},"description":"It details the type of segment, its start and end points in the text, and its start and end times in the synthesized speech audio.","title":"NestedChunk"},"SpeechMarks":{"type":"object","properties":{"chunks":{"type":"array","items":{"$ref":"#/components/schemas/NestedChunk"},"description":"Array of NestedChunk, each providing detailed segment information within the synthesized speech."},"end":{"type":"integer","format":"int64"},"end_time":{"type":"number","format":"double"},"start":{"type":"integer","format":"int64"},"start_time":{"type":"number","format":"double"},"type":{"type":"string"},"value":{"type":"string"}},"required":["chunks","end","end_time","start","start_time","type"],"description":"It is used to annotate the audio data with metadata about the synthesis process, like word timing or phoneme details.","title":"SpeechMarks"},"GetSpeechResponse":{"type":"object","properties":{"audio_data":{"type":"string","format":"byte","description":"Synthesized speech audio, Base64-encoded"},"audio_format":{"$ref":"#/components/schemas/GetSpeechResponseAudioFormat","description":"The codec of the audio data"},"billable_characters_count":{"type":"integer","format":"int64","description":"The number of billable characters processed in the request."},"output_format":{"$ref":"#/components/schemas/AudioOutputFormat","description":"The full `codec_sampleRate_bitrate` format the audio was encoded in, returned when the request set `output_format`. It is the requested value unless the request named a bitrate above the mp3 ceiling, in which case it reports the bitrate actually delivered."},"speech_marks":{"$ref":"#/components/schemas/SpeechMarks"}},"required":["audio_data","audio_format","billable_characters_count","speech_marks"],"title":"GetSpeechResponse"},"ErrorCode":{"type":"string","enum":["bad_request","validation_failed","unauthorized","payment_required","forbidden","not_found","method_not_allowed","conflict","idempotency_conflict","payload_too_large","unsupported_media_type","rate_limited","concurrency_limit_reached","invalid_api_version","internal_error","upstream_failure","service_unavailable","caller_not_found","contact_not_found","contact_identifier_not_found","contact_identifier_conflict","contact_resolver_not_found","credential_not_found","credential_in_use","agent_not_found","agent_in_use","agent_run_not_found","kb_not_found","kb_document_not_found","kb_folder_not_found","tool_not_found","channel_instance_not_found","team_not_found","trigger_not_found","store_not_found","store_document_not_found","store_asset_not_found","hosted_api_not_found","api_route_not_found","consumer_key_not_found","skill_not_found","skill_version_not_found","store_limit_reached","store_document_limit_reached","store_bytes_limit_reached","store_not_configured","hosted_apis_not_in_plan","skills_not_in_plan","skill_in_use","skill_tool_name_conflict","skill_limit_reached","agent_skill_limit_reached","hosted_api_slug_taken","api_route_conflict","route_output_unavailable","route_run_timeout","route_run_failed","route_run_limit_reached","conversation_not_found","phone_number_not_found","sip_trunk_not_found","voice_not_found","audio_asset_not_found","builtin_not_found","batch_not_found","agent_test_not_found","workspace_not_found","invite_not_found","project_not_found","cross_project_reference","project_has_scoped_credentials","project_limit_reached","agent_limit_reached","project_too_large_to_promote","call_not_found","message_not_found","thread_not_found","call_not_active","relay_displaces_agent","brain_not_found","brain_in_use","insufficient_scope","purchased_numbers_not_included","phone_number_quota_reached","batch_calls_not_included","voice_cloning_not_included","consent_challenge_not_found","consent_challenge_expired","consent_challenge_already_used","consent_phrase_mismatch","consent_speaker_mismatch","consent_recording_unusable","consent_verification_unavailable","watermark_audio_unusable","watermark_detection_unavailable","workspace_last_owner","workspace_last_workspace","account_deletion_blocked","workspace_free_limit","workspace_single_owner","invite_email_mismatch","invite_already_pending","service_account_limit_reached","service_accounts_not_in_plan","speech_marks_unsupported","model_retired","too_many_voices","content_policy_violation","topup_not_in_plan","credit_purchase_unpaid","tool_config_shared","spend_cap_exceeded","spend_budget_exceeded","project_spend_limit_exceeded","project_archived","project_not_archived","project_not_purged","project_restore_window_expired","project_name_taken","funded_balance_required","agent_publish_gate_failed","agent_publish_gate_required","agent_publish_gate_unavailable","text_channel_not_in_plan","channel_not_in_plan","text_turn_failed","text_message_quota_exceeded","durable_runs_not_in_plan","tool_transport_unsupported","agent_run_not_pending","agent_run_action_stale","share_link_not_found","share_link_exhausted","share_link_limit_reached","destination_not_allowed","international_dialing_not_enabled","number_not_sms_capable","verification_required","intended_use_required"],"description":"Stable machine-readable error code. Additive only: codes are\nnever renamed, only deprecated. SDKs may map each code to a\ntyped exception class. Status-code semantics:\n4xx codes describe caller-fixable issues; 5xx codes describe\nserver-side failures and are safe to retry with backoff for\nidempotent requests.\n","title":"ErrorCode"},"ErrorDetail":{"type":"object","properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable explanation of this specific occurrence.\nSafe to surface in UI banners or pass to support. The\nwording can change between releases; clients should\nmatch on `code`, not on the message string.\n"},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Per-field validation errors as `path -> message`. Only\npresent on 400 responses caused by request validation\n(typically code=`validation_failed`). Keys are field\npaths in dotted/bracket notation; values are short\nhuman explanations safe to inline-surface next to the\noffending form field.\n"},"details":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Structured, endpoint-specific context beyond the flat\n`fields` map. Present only on the few errors that carry\nit (e.g. the `used_by` referrer list on a credential\ndelete-conflict); its shape depends on the error `code`.\nClients that don't recognise a `details` shape can ignore\nit - the `code` + `message` contract is unchanged.\n"},"docs_url":{"type":"string","format":"uri","description":"Link to the documentation that resolves this class of\nerror, when a stable page exists. Rate and concurrency\n429s link the API limits reference, which lists each\nplan's limits and how to raise them.\n"}},"required":["code","message"],"title":"ErrorDetail"},"Error":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"},"request_id":{"type":"string","description":"Server-side request identifier. Echoes the\n`Speechify-Request-Id` response header. Stable across the\nrequest's lifetime, written to structured logs, and\nuseful when reporting issues.\n"}},"required":["error"],"description":"Standard error envelope returned on every non-2xx response.\nContent-Type is `application/json`. The shape mirrors OpenAI /\nAnthropic / Stripe style: a machine-readable `error.code` for\nSDK consumers to switch on, a human `error.message` for UI,\nand an optional `error.fields` map for per-field validation\nerrors. `request_id` matches the `Speechify-Request-Id` response\nheader and is what customers quote when filing support\ntickets.\n","title":"Error"},"V1AudioStreamPostParametersAccept":{"type":"string","enum":["audio/mpeg","audio/ogg","audio/aac","audio/pcm"],"title":"V1AudioStreamPostParametersAccept"},"GetStreamRequestModel":{"type":"string","enum":["simba-3.0","simba-3.2"],"default":"simba-3.0","description":"Model used for audio synthesis. Defaults to `simba-3.0`, which is streaming-native and multilingual: it officially supports English plus `de-DE`, `es-ES`, `es-MX`, `fr-FR`, `it-IT` and `pt-BR`, and routes each request to its English or its multilingual training based on `language` (falling back to the voice's locale when `language` is omitted). `simba-3.2` is the streaming-native model with the lowest TTFB and richest expressivity, and the recommended Simba 3 model; it is English only, so a non-English voice returns 400.\n\nThe legacy Simba 1.6 models `simba-english` and `simba-multilingual` are retired from API version `2026-09-21`: naming one returns 400 `model_retired`. Pinning your API version to a date before `2026-09-21` keeps them working until **2026-11-21**, when both are switched off for every API version. Migrate to `simba-3.2` (English) or `simba-3.0` before then; call GET /v1/audio/models to see the set your workspace can select today.","title":"GetStreamRequestModel"},"GetStreamOptionsRequest":{"type":"object","properties":{"loudness_normalization":{"type":"boolean","default":false,"description":"Determines whether to normalize the audio loudness to a standard level.\nWhen enabled, loudness normalization aligns the audio output to the following standards:\nIntegrated loudness: -14 LUFS\nTrue peak: -2 dBTP\nLoudness range: 7 LU\nIf disabled, the audio loudness will match the original loudness of the selected voice, which may vary significantly and be either too quiet or too loud.\nEnabling loudness normalization can increase latency due to additional processing required for audio level adjustments."},"text_normalization":{"type":"boolean","default":false,"description":"Determines whether to normalize the text. If enabled, it will transform numbers, dates, etc. into words. For example, \"55\" is normalized into \"fifty five\".\nThis can increase latency due to additional processing required for text normalization."}},"description":"GetStreamOptionsRequest is the wrapper for request parameters to the client","title":"GetStreamOptionsRequest"},"AudioStreamOutputFormat":{"type":"string","enum":["pcm_8000","pcm_16000","pcm_22050","pcm_24000","pcm_44100","pcm_48000","mp3_22050_32","mp3_22050_64","mp3_22050_96","mp3_22050_128","mp3_22050_160","mp3_22050_192","mp3_24000_32","mp3_24000_64","mp3_24000_96","mp3_24000_128","mp3_24000_160","mp3_24000_192","ulaw_8000","ogg_24000","aac_24000"],"description":"Audio output format for the streaming endpoint (`POST /v1/audio/stream`), as a `codec_sampleRate_bitrate` string. Same as `AudioOutputFormat` minus the `wav_*` formats: wav is only available on `POST /v1/audio/speech`. `pcm_*` and `ulaw_8000` are headerless raw audio; `pcm_16000` and `ulaw_8000` are the telephony formats Twilio/LiveKit SIP expect.\n\n160 kbps is the highest bitrate an mp3 can carry at 22.05 and 24 kHz, so `mp3_22050_160` and `mp3_24000_160` are the maximum-fidelity mp3 formats; a request for `mp3_*_192` is encoded at 160 kbps. The two `mp3_*_160` formats are served by the Simba 3 models only.","title":"AudioStreamOutputFormat"},"GetStreamRequest":{"type":"object","properties":{"input":{"type":"string","maxLength":20000,"description":"Plain text or SSML to be synthesized to speech.\nRefer to https://docs.speechify.ai/docs/api-limits for the input size limits.\nEmotion, Pitch and Speed Rate are configured in the ssml input, please refer to the ssml documentation for more information: https://docs.speechify.ai/docs/ssml#prosody"},"language":{"type":"string","description":"Language of the input. Follow the format of an ISO 639-1 language code and an ISO 3166-1 region code, separated by a hyphen, e.g. en-US.\nPlease refer to the list of the supported languages and recommendations regarding this parameter: https://docs.speechify.ai/docs/language-support."},"model":{"$ref":"#/components/schemas/GetStreamRequestModel","default":"simba-3.0","description":"Model used for audio synthesis. Defaults to `simba-3.0`, which is streaming-native and multilingual: it officially supports English plus `de-DE`, `es-ES`, `es-MX`, `fr-FR`, `it-IT` and `pt-BR`, and routes each request to its English or its multilingual training based on `language` (falling back to the voice's locale when `language` is omitted). `simba-3.2` is the streaming-native model with the lowest TTFB and richest expressivity, and the recommended Simba 3 model; it is English only, so a non-English voice returns 400.\n\nThe legacy Simba 1.6 models `simba-english` and `simba-multilingual` are retired from API version `2026-09-21`: naming one returns 400 `model_retired`. Pinning your API version to a date before `2026-09-21` keeps them working until **2026-11-21**, when both are switched off for every API version. Migrate to `simba-3.2` (English) or `simba-3.0` before then; call GET /v1/audio/models to see the set your workspace can select today."},"options":{"$ref":"#/components/schemas/GetStreamOptionsRequest"},"output_format":{"$ref":"#/components/schemas/AudioStreamOutputFormat","description":"The output audio format as a `codec_sampleRate_bitrate` string. Takes precedence over the `Accept` header when set, so you can request formats the `Accept` enum does not cover (e.g. `pcm_16000`, `ulaw_8000`). `wav_*` formats are not supported on streaming - use `POST /v1/audio/speech` for wav."},"voice_id":{"type":"string","description":"Id of the voice to be used for synthesizing speech. Refer to /v1/voices endpoint for available voices"}},"required":["input","voice_id"],"description":"GetStreamRequest is the wrapper for request parameters to the client","title":"GetStreamRequest"},"V1AudioStreamWithTimestampsPostParametersAccept":{"type":"string","enum":["audio/mpeg","audio/ogg","audio/aac","audio/pcm"],"title":"V1AudioStreamWithTimestampsPostParametersAccept"},"SpeechStreamEvent":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["speech.chunk"]},"audio":{"type":"string","description":"A run of the synthesized audio, Base64-encoded, in the format the\nrequest selected (echoed on the `Speechify-Audio-Content-Type`\nresponse header). Absent on a marks-only chunk."},"speech_marks":{"type":"array","items":{"$ref":"#/components/schemas/NestedChunk"},"description":"Word timings addressing the original input text, with absolute\nmillisecond times from the start of the synthesis. Absent when the\nchunk carries only audio."}},"required":["type"],"description":"A run of synthesized audio, the speech marks that became final with it,\nor both - a chunk may carry only one of the two, and the last chunk of\na stream is often marks-only. Mark times are absolute milliseconds from\nthe start of the synthesis: concatenate the audio chunks into one\nstream and apply the marks against that single timeline. Which chunk a\nmark arrives on is a delivery detail and carries no meaning."},{"type":"object","properties":{"type":{"type":"string","enum":["speech.done"]},"billable_characters_count":{"type":"integer","description":"Number of billable characters processed."},"audio_duration_ms":{"type":"integer","description":"Duration of the synthesized audio in milliseconds."}},"required":["type","billable_characters_count","audio_duration_ms"],"description":"Terminal event: the synthesis completed and no further events follow.\nThere is no `[DONE]` sentinel."},{"type":"object","properties":{"type":{"type":"string","enum":["speech.error"]},"error":{"$ref":"#/components/schemas/ErrorDetail"},"request_id":{"type":"string","description":"Server-side request identifier. Echoes the `Speechify-Request-Id`\nresponse header."}},"required":["type","error"],"description":"Terminal event carrying the standard error envelope, emitted when a\nfailure happens after the stream has started and the status code is\nalready committed."}],"discriminator":{"propertyName":"type"},"description":"One event on the POST /v1/audio/stream/with-timestamps stream. The\n`type` field discriminates the variants and mirrors the SSE `event:`\nname, so an event is identifiable from its `data:` payload alone.","title":"SpeechStreamEvent"},"V1VoicesGetParametersType":{"type":"string","enum":["personal","shared"],"title":"V1VoicesGetParametersType"},"V1VoicesGetParametersGender":{"type":"string","enum":["male","female","not_specified"],"title":"V1VoicesGetParametersGender"},"GetVoiceGender":{"type":"string","enum":["male","female","not_specified"],"title":"GetVoiceGender"},"GetVoiceLanguage":{"type":"object","properties":{"locale":{"type":"string"},"preview_audio":{"type":["string","null"]}},"required":["locale"],"title":"GetVoiceLanguage"},"GetVoicesModelName":{"type":"string","enum":["simba-english","simba-multilingual","simba-3.0","simba-3.2"],"description":"A model this voice can be synthesized with. The set is filtered to\nwhat YOUR workspace's API version can select, so a voice never\nadvertises a model your own synthesis request would reject. The\nlegacy `simba-english` / `simba-multilingual` values appear only for\na workspace pinned before API version `2026-09-21`.\n","title":"GetVoicesModelName"},"GetVoicesModel":{"type":"object","properties":{"languages":{"type":"array","items":{"$ref":"#/components/schemas/GetVoiceLanguage"}},"name":{"$ref":"#/components/schemas/GetVoicesModelName","description":"A model this voice can be synthesized with. The set is filtered to\nwhat YOUR workspace's API version can select, so a voice never\nadvertises a model your own synthesis request would reject. The\nlegacy `simba-english` / `simba-multilingual` values appear only for\na workspace pinned before API version `2026-09-21`.\n"}},"required":["languages","name"],"title":"GetVoicesModel"},"GetVoiceType":{"type":"string","enum":["shared","personal"],"title":"GetVoiceType"},"GetVoice":{"type":"object","properties":{"avatar_image":{"type":["string","null"]},"display_name":{"type":"string"},"gender":{"$ref":"#/components/schemas/GetVoiceGender"},"locale":{"type":"string"},"id":{"type":"string"},"models":{"type":"array","items":{"$ref":"#/components/schemas/GetVoicesModel"}},"preview_audio":{"type":["string","null"]},"project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The workspace project this cloned voice is filed under, set when\na project-pinned key created it. Returned wherever a cloned voice\nis: the list, a single-voice read, and the create response.\n\nOmitted for a shared-catalog voice and for a cloned voice no\nproject filed, which is shared with the whole workspace and\nlisted for every member of it.\n"},"tags":{"type":["array","null"],"items":{"type":"string"}},"type":{"$ref":"#/components/schemas/GetVoiceType"}},"required":["display_name","gender","locale","id","models","type"],"title":"GetVoice"},"ListVoicesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"voices":{"type":"array","items":{"$ref":"#/components/schemas/GetVoice"}}},"required":["next_cursor","has_more","voices"],"description":"Payload for `GET /v1/voices` - the shared voice catalog plus the\nworkspace's personal cloned voices. Carries the shared pagination\nmetadata so the list can be paged when `limit` is supplied;\n`next_cursor` is null and `has_more` is false when the full\ncatalogue is returned in one response.\n","title":"ListVoicesResponse"},"V1VoicesPostRequestBodyContentMultipartFormDataSchemaGender":{"type":"string","enum":["male","female","not_specified"],"description":"Gender marker for the personal voice\nmale GenderMale\nfemale GenderFemale\nnot_specified GenderNotSpecified","title":"V1VoicesPostRequestBodyContentMultipartFormDataSchemaGender"},"CreateConsentChallengeRequest":{"type":"object","properties":{"full_name":{"type":"string","description":"Full name of the person consenting to have their voice cloned.\nSpeechify binds it to the challenge and stores it with the consent\nrecord, so the create that consumes the challenge does not carry it\nand cannot change it.\n\nAt most 120 bytes once UTF-8 encoded, which is 120 characters of\nLatin script but around 40 of Chinese, Japanese or Korean. Stated in\nbytes rather than as a `maxLength` because the two only agree on\nsingle-byte scripts, and a character count that never over-accepts\nwould have to refuse Latin names at 30. A name over the limit comes\nback as `validation_failed` reporting its measured length."}},"required":["full_name"],"title":"CreateConsentChallengeRequest"},"ConsentChallenge":{"type":"object","properties":{"id":{"type":"string","description":"Identifier for this challenge, sent back as `consent_challenge_id`\non the create. Treat it as an opaque string - the format is not part\nof the contract and will not stay stable."},"phrase":{"type":"string","description":"The sentence the speaker must read aloud. Show it exactly as\nreturned - the recording is transcribed and matched against this\ntext, so re-wording, re-casing or re-punctuating it will fail the\ncheck."},"expires_at":{"type":"string","format":"date-time","description":"When the challenge stops being usable. This is the only authority on\nthe window - do not hard-code a duration. Past it, create a new\nchallenge and record the new phrase."}},"required":["id","phrase","expires_at"],"title":"ConsentChallenge"},"Model":{"type":"object","properties":{"id":{"type":"string","description":"Model identifier. Pass this as the `model` parameter to\nPOST /v1/audio/speech or /v1/audio/stream.\n"},"name":{"type":"string","description":"Human-readable model name, for a model picker."},"default":{"type":"boolean","description":"Whether this is the model used when a synthesis request omits\n`model`. Exactly one model in the list is the default. Distinct\nfrom `recommended`: the default accepts every voice in every\nlanguage, while the recommended model may be English-only.\n"},"recommended":{"type":"boolean","description":"Whether this is the model we recommend for new integrations.\nExactly one model in the list is recommended, and it may differ\nfrom the `default`.\n"},"deprecated":{"type":"boolean","description":"Whether this is a legacy model. De-emphasise it in a picker and\nsteer new integrations to a current model. Read `retired_at` for\nwhether it also has a withdrawal date.\n"},"retired_at":{"type":"string","format":"date","description":"The API version at which this model stops being selectable, as\n`YYYY-MM-DD`. Absent when the model has no withdrawal date.\n\nIt appears only while your workspace is pinned BELOW that version -\nat or after it the model is absent from this catalog entirely, and\nnaming it returns 400 `model_retired`. So a present value means \"you\ncan still use this, and this is the date you lose it\". Pinning your\nAPI version before this date keeps the model working, up to\n`sunset_at`.\n"},"sunset_at":{"type":"string","format":"date","description":"The date this model is switched off, as `YYYY-MM-DD`. Absent when no\nshutdown is scheduled.\n\nThis is the deadline `retired_at`'s version pin runs out against:\nfrom `sunset_at` the model is unreachable on EVERY API version,\nincluding a workspace pinned below its retirement. Read the two\ntogether - a pin buys time to migrate, not a permanent exemption.\n"},"description":{"type":"string","description":"One-line summary of the model, for a model picker."},"languages":{"type":"array","items":{"type":"string"},"description":"Languages the model can synthesize, as BCP-47 locale strings\nmatching the `language` request parameter (e.g. `en`, `fr-FR`).\nEnglish-only models return `[\"en\"]`. This set reflects current\ncapability and can grow over time.\n"},"endpoints":{"type":"array","items":{"type":"string"},"description":"The synthesis routes this model may be passed to. Only the\nstreaming-native models serve `/v1/audio/stream/with-timestamps`;\npassing a model this list omits is a 400 rather than a degraded\nresponse, so branch on it instead of discovering it at call time.\n"},"curated_voices":{"type":"boolean","description":"Deprecated and always `false`. No model restricts synthesis to a\nregistered voice set: every training conditions on the voice's own\nprompt audio, so every catalogue voice and every cloned voice works\non every model, subject only to `english_voices_only`. Each voice's\n`models` array in GET /v1/voices stays the per-voice answer. The\nfield remains on the response for compatibility.\n","deprecated":true},"english_voices_only":{"type":"boolean","description":"Whether the model rejects a non-English voice. Independent of\n`languages`: a model can publish English only and still accept any\nvoice.\n"}},"required":["id","name","default","recommended","deprecated","description","languages","endpoints","curated_voices","english_voices_only"],"description":"One selectable text-to-speech model.","title":"Model"},"ModelsResponse":{"type":"object","properties":{"models":{"type":"array","items":{"$ref":"#/components/schemas/Model"},"description":"The models selectable on the single-utterance synthesis endpoints.\n"},"dialogue_models":{"type":"array","items":{"$ref":"#/components/schemas/Model"},"description":"The multi-speaker models selectable on POST /v1/audio/dialogue.\nDisjoint from `models`: a dialogue model consumes a\nspeaker-attributed script rather than one utterance, so it is\nrejected on the single-utterance endpoints and vice versa. Its\n`default` marks the model that endpoint resolves to when a request\nomits `model`, independently of the `models` default.\n"}},"required":["models","dialogue_models"],"description":"The catalog of text-to-speech models available for synthesis. The list\nis scoped to your workspace's API version: a model retired at or before\nit is absent here and returns 400 `model_retired` from the synthesis\nendpoints, so what this returns is exactly what you can call. The\nexample below is the catalog at the current version - a workspace pinned\nbefore `2026-09-21` also sees `simba-english` and `simba-multilingual`,\neach carrying `retired_at` and the `sunset_at` date its pin expires\nagainst.\n","title":"ModelsResponse"},"WatermarkDetectionResponse":{"type":"object","properties":{"watermarked":{"type":"boolean","description":"Whether the clip carries the watermark, i.e. whether `confidence`\ncleared the service's configured bar."},"confidence":{"type":"number","format":"double","minimum":0,"maximum":1,"description":"The detector's score for this clip, in [0, 1]. Comparable only\nbetween checks made against the same detector version."}},"required":["watermarked","confidence"],"description":"The watermark detector's answer for one clip.\n\n`watermarked: true` is positive evidence the audio came from Speechify\nsynthesis. `watermarked: false` is the absence of that evidence, not\nproof of a negative - see the endpoint description for the three reasons\ngenuinely Speechify-generated audio can come back unmarked.\n","title":"WatermarkDetectionResponse"},"WatermarkVerificationResponse":{"type":"object","properties":{"watermarked":{"type":"boolean","description":"Whether the clip carries the Speechify watermark."}},"required":["watermarked"],"description":"The verification answer for one clip.\n\nOne field, and no confidence score: a score is a gradient, and a public\ngradient turns \"does this carry the mark\" into \"adjust the clip until\nthe number drops\". Use `POST /v1/audio/watermark/detect` if you need it.\n\n`watermarked: true` is positive evidence the audio came from Speechify\nsynthesis. `watermarked: false` is the absence of that evidence, not\nproof of a negative - see the endpoint description.\n","title":"WatermarkVerificationResponse"},"AgentChannel":{"type":"string","enum":["voice","text"],"description":"A delivery surface an agent can serve. One agent may serve several,\nso `channels` is a set. `voice` runs over telephony + web (requires a\n`tts.voice_id`); `text` runs over the chat/widget surface. An agent\nwith an empty set has no front door and is reached by its triggers\nonly. There is no agent type: what an agent is (its prompt, model,\nknowledge and tools) is one thing, and this set is how it is reached.\n","title":"AgentChannel"},"AgentAdditionalLanguage":{"type":"object","properties":{"language":{"type":"string","description":"BCP-47 language tag, e.g. 'es' or 'pt-BR'. The supported set\nis served by `multilingual_languages` on GET\n/v1/agents/voices - read it rather than hard-coding a list,\nsince coverage widens over time. Must be unique per agent and\ndifferent from the default `language`.\n"},"voice_id":{"type":["string","null"],"description":"Optional per-language voice slug from the catalog (see GET\n/v1/agents/voices). Null or omitted serves the language with\nthe agent's default voice.\n"},"first_message":{"type":["string","null"],"description":"Optional greeting used when a session starts in this\nlanguage. Null or omitted falls back to the agent's default\n`first_message`.\n"}},"required":["language"],"description":"One extra language an agent serves beyond its default `language`.\nWithout `voice_id` the agent's own voice speaks the language\n(voice continuity through a mid-call switch); with `voice_id`\nthe language is served by that voice instead, e.g. a native\nspeaker of the language. `first_message` overrides the greeting\nonly when a session starts in this language.\n","title":"AgentAdditionalLanguage"},"AgentLlmConfigProvider":{"type":"string","enum":["openai","speechify","custom"],"description":"LLM backend. Null (or omit) uses the platform default,\nresolved server-side at dispatch. `openai` and `speechify`\npair with a `model` from the allowed table. `custom` points\nthe worker at any OpenAI / vLLM-compatible endpoint - see\n`base_url`, `credential_id`, `extra_body`. Must be paired with\na non-null `model`; setting one without the other is rejected.\n","title":"AgentLlmConfigProvider"},"AgentLLMConfig":{"type":"object","properties":{"provider":{"$ref":"#/components/schemas/AgentLlmConfigProvider","description":"LLM backend. Null (or omit) uses the platform default,\nresolved server-side at dispatch. `openai` and `speechify`\npair with a `model` from the allowed table. `custom` points\nthe worker at any OpenAI / vLLM-compatible endpoint - see\n`base_url`, `credential_id`, `extra_body`. Must be paired with\na non-null `model`; setting one without the other is rejected.\n"},"model":{"type":["string","null"],"description":"Chat model slug. Null (or omit) uses the platform default.\nFor `openai` / `speechify` it must be a slug from the allowed\ntable; for `custom` it is free-form.\n"},"base_url":{"type":["string","null"],"description":"Custom OpenAI/vLLM-compatible endpoint base URL. Required\nwhen `provider` is `custom`, rejected otherwise.\n"},"credential_id":{"type":["string","null"],"description":"`cred_` id of a `bearer` credential in the\nworkspace vault holding the custom endpoint's API key. The\nsecret lives in the vault and is resolved server-side at\ndispatch, never inlined on the agent. On write, send a value\nto point at a credential, an empty string to clear it, or omit\nit to keep the stored reference unchanged. On read, the\nreferenced `cred_` id, or null when none is referenced. Create\nthe credential first via `POST /v1/credentials`. Valid only\nwhen `provider` is `custom`.\n"},"extra_body":{"type":["object","null"],"additionalProperties":{"description":"Any type"},"description":"JSON object forwarded verbatim to the custom endpoint as the\nchat.completions `extra_body` (reasoning / sampling knobs).\nValid only when `provider` is `custom`.\n"},"temperature":{"type":"string","title":"float","description":"Sampling temperature in the range 0.0-1.0. Defaults to 0.5."}},"description":"Language-model configuration. Omit the whole block on create to\nrun on the platform default model. On update (merge-patch) send\nonly the sub-fields you want to change: an explicit null clears a\nnullable field to its default, a value sets it, and anything\nomitted is left unchanged. `provider`/`model` are validated as a\npair, inheriting the omitted half from the stored value.\n","title":"AgentLLMConfig"},"AgentTTSConfig":{"type":"object","properties":{"voice_id":{"type":"string","description":"Voice slug (see GET /v1/agents/voices for the recommended set). The server rejects an empty slug, and any voice it cannot synthesize."},"speed":{"type":["string","null"],"title":"float","description":"Post-process, pitch-preserving time-stretch applied to the\nsynthesized audio before playback (0.5 = half speed, 2.0 =\ndouble, 1.0 = unchanged). Null means no time-stretch.\n"},"model":{"type":["string","null"],"description":"Pins the Simba model this agent synthesizes on. **Null is the\ndefault and the recommended setting**: the agent follows the\nmodel VMS proposes for its voice (`default_model` on\nGET /v1/agents/voices), so a voice promoted to a newer training\nmoves with it and no configuration goes stale.\n\nSet it to override that choice in either direction - onto an\nexperimental training, or back down off one. Call\nGET /v1/agents/tts-models for the catalog. Every model there\nserves every voice we speak ourselves; a partner voice takes none\nof them, because its provider selects its own model.\n\nA write is rejected when the agent could not actually be served\nwith the model: an English-only model on an agent that resolves\nthrough the multilingual serving (it declares\n`additional_languages`, or its own `language` is not English), or\nany Simba model on a partner voice. It is re-checked whenever\n`language` or `additional_languages` change, so a stored pin\ncannot be orphaned by an edit elsewhere.\n\nSend `null` (or `\"\"`) to clear it back to automatic.\n"}},"required":["voice_id"],"description":"Text-to-speech voice and delivery configuration.","title":"AgentTTSConfig"},"AgentTurnHandlingConfigInterruptionSensitivity":{"type":"string","enum":["low","medium","high"],"description":"How easily the caller takes the floor from a speaking agent,\nand how patiently the agent waits before treating a pause as\nthe end of the caller's turn. Null follows the platform and\nbehaves identically to `medium`.\n\n`low` makes the agent hard to interrupt: background noise,\nhold music, and one- or two-word sounds no longer stop it\nmid-sentence (it needs 1.5s of speech and at least 3 words),\nand it waits 0.4s longer before taking a pause as the end of\na turn. Use it for noisy lines and for callers who pause\nmid-thought. `high` is the opposite: the agent yields after\n0.4s of caller speech and takes the turn sooner.\n\nNeither setting changes ordinary turn-taking - a one-word\nanswer still replies normally when the agent is silent; the\nword threshold only applies to interrupting speech already in\nprogress. Two limits worth knowing: the word threshold needs\ntranscription, so it does not apply to agents running the\nrealtime audio model, and setting `response_delay_seconds`\ntakes over the silence window entirely - the level no longer\nlengthens or shortens how long the agent waits, though it still\ngoverns how easily the caller interrupts.\n","title":"AgentTurnHandlingConfigInterruptionSensitivity"},"AgentTurnHandlingConfig":{"type":"object","properties":{"response_delay_seconds":{"type":["string","null"],"title":"float","description":"Minimum silence, in seconds, the agent waits for after the\ncaller stops talking before it starts replying. Applies to\nevery turn on the call. Raise it when the agent talks over a\ncaller who pauses mid-sentence, or over an IVR menu that\nbreaks between options. It is a floor on listening rather\nthan a pause before speaking, so it adds to the time every\nreply takes to arrive. Null lets turn detection decide, which\nwaits 0.3s when it is confident the caller finished and up to\n1.2s when it is not.\n"},"interruption_sensitivity":{"$ref":"#/components/schemas/AgentTurnHandlingConfigInterruptionSensitivity","description":"How easily the caller takes the floor from a speaking agent,\nand how patiently the agent waits before treating a pause as\nthe end of the caller's turn. Null follows the platform and\nbehaves identically to `medium`.\n\n`low` makes the agent hard to interrupt: background noise,\nhold music, and one- or two-word sounds no longer stop it\nmid-sentence (it needs 1.5s of speech and at least 3 words),\nand it waits 0.4s longer before taking a pause as the end of\na turn. Use it for noisy lines and for callers who pause\nmid-thought. `high` is the opposite: the agent yields after\n0.4s of caller speech and takes the turn sooner.\n\nNeither setting changes ordinary turn-taking - a one-word\nanswer still replies normally when the agent is silent; the\nword threshold only applies to interrupting speech already in\nprogress. Two limits worth knowing: the word threshold needs\ntranscription, so it does not apply to agents running the\nrealtime audio model, and setting `response_delay_seconds`\ntakes over the silence window entirely - the level no longer\nlengthens or shortens how long the agent waits, though it still\ngoverns how easily the caller interrupts.\n"},"inactivity_timeout_seconds":{"type":["integer","null"],"minimum":0,"description":"How long the agent tolerates silence before ending the call,\nin seconds. Null uses the platform default.\n"}},"description":"Turn-handling and silence-timeout configuration.","title":"AgentTurnHandlingConfig"},"AgentMemoryConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"When true, the post-call extractor writes durable facts about\neach caller and the retriever injects the top matches into the\nsystem prompt via the `{{memory}}` variable. Defaults to false.\n"},"retention_days":{"type":"integer","minimum":0,"description":"Maximum age (in days) of memories kept and surfaced. 0\ndisables the cap. Defaults to 90.\n"}},"description":"Per-caller long-term memory configuration.","title":"AgentMemoryConfig"},"AgentNavigatorConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Tunes worker turn handling for autonomous outbound IVR\nnavigation - longer endpointing and no barge-in. The\nnavigation goal itself lives in the agent's prompt; this flag\nis the behaviour switch only. Defaults to false.\n"},"ivr_cache_enabled":{"type":"boolean","description":"Controls the IVR-menu cache consulted during navigation.\nDefaults to true so a navigator agent reuses learned menu\nlayouts across calls. Set to false to force every outbound\ndial on this agent to navigate from scratch.\n"}},"description":"Autonomous IVR-navigation configuration for outbound calls.","title":"AgentNavigatorConfig"},"AgentGuardrailsConfig":{"type":"object","properties":{"observer_enabled":{"type":"boolean","description":"Arms the out-of-band injection guardrail. A second, isolated\nmodel reads the caller's turns off the live conversation and\ncorrects the agent when it detects a privileged-identity\nclaim, a prompt-extraction probe, an instruction override, or\nthe caller pulling the call off its purpose. It runs off the\nturn critical path, so it does not add caller-facing latency,\nand it never receives the agent's own instructions - so it\ncannot be talked out of running by anything the caller says.\nAdds one inference call per caller turn. Defaults to false.\n"}},"description":"Runtime safety controls that are opt-in per agent rather than\nplatform defaults, because each one bills work an ordinary call\ndoes not do.\n","title":"AgentGuardrailsConfig"},"AgentBackgroundNoiseConfigPreset":{"type":"string","enum":["office","city","forest","crowded_room","keyboard_typing","hold_music"],"description":"Pre-mixed ambient bed. Null disables background noise.","title":"AgentBackgroundNoiseConfigPreset"},"AgentBackgroundNoiseConfig":{"type":"object","properties":{"preset":{"$ref":"#/components/schemas/AgentBackgroundNoiseConfigPreset","description":"Pre-mixed ambient bed. Null disables background noise."},"volume":{"type":["string","null"],"title":"float","description":"Volume of the background-noise bed (0.0-1.0). Null disables."}},"description":"Optional ambient background-noise bed mixed into the call.","title":"AgentBackgroundNoiseConfig"},"WidgetConfigStyle":{"type":"string","enum":["pill","fab"],"title":"WidgetConfigStyle"},"WidgetConfigTheme":{"type":"string","enum":["dark","light","auto"],"title":"WidgetConfigTheme"},"WidgetConfigAvatarType":{"type":"string","enum":["orb","image"],"title":"WidgetConfigAvatarType"},"WidgetConfigAvatar":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/WidgetConfigAvatarType"},"image_url":{"type":"string"},"orb_color_1":{"type":"string"},"orb_color_2":{"type":"string"}},"title":"WidgetConfigAvatar"},"WidgetConfigText":{"type":"object","properties":{"start_call":{"type":"string"},"end_call":{"type":"string"},"listening":{"type":"string"},"thinking":{"type":"string"},"speaking":{"type":"string"}},"title":"WidgetConfigText"},"WidgetConfigTerms":{"type":"object","properties":{"enabled":{"type":"boolean"},"content":{"type":"string"}},"title":"WidgetConfigTerms"},"WidgetConfigTranscript":{"type":"object","properties":{"enabled":{"type":"boolean"}},"title":"WidgetConfigTranscript"},"WidgetConfigTelemetry":{"type":"object","properties":{"enabled":{"type":"boolean"}},"description":"Controls whether the embedded widget reports anonymous\noperational events (mounted, connected, first audio, failure\ncodes) back to Speechify. Omit the object to leave telemetry\non, which is the default: it is how browser-side failures a\nserver log cannot see - a denied microphone prompt, a blocked\nautoplay, a visitor who gave up mid-connect - become\ndiagnosable. No page URL, page content, transcript, or\ncross-site identifier is ever collected.\n","title":"WidgetConfigTelemetry"},"WidgetConfig":{"type":"object","properties":{"version":{"type":"integer"},"style":{"$ref":"#/components/schemas/WidgetConfigStyle"},"theme":{"$ref":"#/components/schemas/WidgetConfigTheme"},"avatar":{"$ref":"#/components/schemas/WidgetConfigAvatar"},"text":{"$ref":"#/components/schemas/WidgetConfigText"},"terms":{"$ref":"#/components/schemas/WidgetConfigTerms"},"transcript":{"$ref":"#/components/schemas/WidgetConfigTranscript"},"telemetry":{"$ref":"#/components/schemas/WidgetConfigTelemetry","description":"Controls whether the embedded widget reports anonymous\noperational events (mounted, connected, first audio, failure\ncodes) back to Speechify. Omit the object to leave telemetry\non, which is the default: it is how browser-side failures a\nserver log cannot see - a denied microphone prompt, a blocked\nautoplay, a visitor who gave up mid-connect - become\ndiagnosable. No page URL, page content, transcript, or\ncross-site identifier is ever collected.\n"}},"description":"Customer-editable appearance + behaviour payload for the\nembedded `` pill: button text, avatar style,\norb colours, terms-and-conditions markdown, transcript display.\nEvery field is optional - empty fields fall back to the\nwidget's compile-time defaults.\n","title":"WidgetConfig"},"AmdConfigOnVoicemailAction":{"type":"string","enum":["hangup","leave_message"],"title":"AmdConfigOnVoicemailAction"},"AmdConfigOnVoicemail":{"type":"object","properties":{"action":{"$ref":"#/components/schemas/AmdConfigOnVoicemailAction"},"message":{"type":"string","maxLength":4000,"description":"Spoken before terminating when action=leave_message.\nSupports {{variable}} substitution. Required (non-empty)\nwhen action=leave_message; rejected by the validator\notherwise.\n"}},"required":["action"],"description":"Action when AMD returns category=machine-vm.","title":"AmdConfigOnVoicemail"},"AmdConfigOnIvrAction":{"type":"string","enum":["proceed","hangup","navigate"],"description":"proceed: hand control to the agent's flow as if the\ncalled party were human. hangup: terminate immediately.\nnavigate: hand control to the IVR Navigator subagent\nwith menu-memoization-aware session config (cache hit\nseeds the agent context; cache miss triggers cold\ndiscovery and the post-call pipeline extracts the\nmenu for future calls).\n","title":"AmdConfigOnIvrAction"},"AmdConfigOnIvr":{"type":"object","properties":{"action":{"$ref":"#/components/schemas/AmdConfigOnIvrAction","description":"proceed: hand control to the agent's flow as if the\ncalled party were human. hangup: terminate immediately.\nnavigate: hand control to the IVR Navigator subagent\nwith menu-memoization-aware session config (cache hit\nseeds the agent context; cache miss triggers cold\ndiscovery and the post-call pipeline extracts the\nmenu for future calls).\n"}},"required":["action"],"description":"Action when AMD returns category=machine-ivr.","title":"AmdConfigOnIvr"},"AmdConfigOnUnavailableAction":{"type":"string","enum":["hangup"],"title":"AmdConfigOnUnavailableAction"},"AmdConfigOnUnavailable":{"type":"object","properties":{"action":{"$ref":"#/components/schemas/AmdConfigOnUnavailableAction"}},"required":["action"],"description":"Action when AMD returns category=machine-unavailable (mailbox full or disconnected).","title":"AmdConfigOnUnavailable"},"AmdConfigTuning":{"type":"object","properties":{"human_speech_threshold_seconds":{"type":"number","format":"double","minimum":0.5,"maximum":10},"no_speech_threshold_seconds":{"type":"number","format":"double","minimum":1,"maximum":30},"timeout_seconds":{"type":"number","format":"double","minimum":5,"maximum":30},"classification_prompt":{"type":"string","maxLength":8000}},"description":"Optional overrides for LiveKit's detection thresholds and\ntimeouts. Cross-field rule (enforced at the application\nvalidator): `timeout_seconds` must be greater than or equal\nto `no_speech_threshold_seconds` when both are set.\n","title":"AmdConfigTuning"},"AMDConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"When false, the worker skips AMD entirely. When true, the\nworker runs AMD on the called party's greeting before\ndelivering the agent's first message and dispatches per\nresult.category. The per-route fields below are still\nrequired by the schema regardless of `enabled` state so a\ncustomer flipping `enabled: false \u2192 true` ships coherent\nroute configuration immediately.\n"},"on_voicemail":{"$ref":"#/components/schemas/AmdConfigOnVoicemail","description":"Action when AMD returns category=machine-vm."},"on_ivr":{"$ref":"#/components/schemas/AmdConfigOnIvr","description":"Action when AMD returns category=machine-ivr."},"on_unavailable":{"$ref":"#/components/schemas/AmdConfigOnUnavailable","description":"Action when AMD returns category=machine-unavailable (mailbox full or disconnected)."},"tuning":{"$ref":"#/components/schemas/AmdConfigTuning","description":"Optional overrides for LiveKit's detection thresholds and\ntimeouts. Cross-field rule (enforced at the application\nvalidator): `timeout_seconds` must be greater than or equal\nto `no_speech_threshold_seconds` when both are set.\n"}},"required":["enabled","on_voicemail","on_ivr","on_unavailable"],"description":"Answering Machine Detection routing config for outbound voice\nagents. AMD classifies the called party's first ~3-15 seconds of\naudio into one of LiveKit's categories (human, uncertain,\nmachine-vm, machine-ivr, machine-unavailable) and dispatches per\ncategory to the configured action. Stored on the agent row;\nflowed onto outbound dispatch metadata under the `amd` key.\n","title":"AMDConfig"},"AIDisclosure":{"type":"object","properties":{"enabled":{"type":"boolean","description":"When true, `line` is spoken at the start of the call. Defaults to false."},"line":{"type":"string","maxLength":500,"description":"The disclosure spoken before the greeting (e.g. \"This call is\nhandled by an AI assistant.\"). Required when `enabled` is true.\n"}},"required":["enabled"],"description":"Opt-in AI-disclosure opening line. When `enabled` is true, `line` is\nspoken at the very start of the call, before the greeting - ahead of\njurisdictions that require callers to be told they are speaking with an\nautomated system. Off by default; existing agents and numbers are\nunaffected until they turn it on.\n","title":"AIDisclosure"},"Agent":{"type":"object","properties":{"id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`).\nThis is the sole customer-facing\nidentifier. URL paths accept only this prefixed form; legacy\nUUID path parameters are rejected with 404.\n"},"name":{"type":"string"},"slug":{"type":"string"},"channels":{"type":"array","items":{"$ref":"#/components/schemas/AgentChannel"},"description":"The delivery channels this agent serves. Empty means no front door: the agent is reached by its triggers only. Mutable via PATCH."},"prompt":{"type":"string"},"knowledge_instruction":{"type":"string","maxLength":1000,"description":"A short steer for how the agent uses its knowledge bases: what to\nlook up first, what never to quote, how to answer when nothing\nmatches. It reaches the model as guidance under the prompt and\nshapes the per-turn retrieval query on every channel: live\ncalls, text conversations and agent tests. Empty means none.\n"},"first_message":{"type":"string","description":"Spoken verbatim at session start when present in the customer's flow graph."},"language":{"type":"string","description":"ISO 639-1 code, e.g. 'en'. The agent's default language."},"additional_languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentAdditionalLanguage"},"description":"Extra languages this agent serves in the same session beyond\nits default `language`. When non-empty the agent can switch\nlanguage mid-call (triggered when the caller speaks one of\nthese languages or asks for it). Always present on reads;\nempty array for single-language agents.\n"},"llm":{"$ref":"#/components/schemas/AgentLLMConfig"},"tts":{"$ref":"#/components/schemas/AgentTTSConfig"},"turn_handling":{"$ref":"#/components/schemas/AgentTurnHandlingConfig"},"memory":{"$ref":"#/components/schemas/AgentMemoryConfig"},"navigator":{"$ref":"#/components/schemas/AgentNavigatorConfig"},"guardrails":{"$ref":"#/components/schemas/AgentGuardrailsConfig"},"background_noise":{"$ref":"#/components/schemas/AgentBackgroundNoiseConfig"},"widget_config":{"$ref":"#/components/schemas/WidgetConfig"},"is_public":{"type":"boolean","description":"When true, the `` web component can start a\nsession against this agent without an API key, subject to\nthe `allowed_origins` allowlist. When false (default), only\nauthenticated callers can start sessions.\n"},"allowed_origins":{"type":"array","items":{"type":"string"},"description":"Exact `Origin` header values (e.g. `https://example.com`)\nthat are allowed to start public sessions. Empty array\nwith `is_public = true` means any origin is accepted \u2014\nintended for open demos. No subdomain wildcards.\n"},"hostname_allowlist":{"type":["array","null"],"items":{"type":"string"},"description":"Optional per-agent hostname allowlist enforced at\nsession-creation time. When set and non-empty, the\n`Origin` header's hostname must be an exact member.\nBare hostnames only \u2014 no scheme, port, or path. Up to\n10 entries. Omit (null) or leave empty for no\nenforcement (public agents accept any hostname).\n"},"webhook_url":{"type":"string","description":"Per-agent override of the workspace webhook endpoints for this\nagent's post-call event. When non-empty, the control plane POSTs the\nstandard signed `WebhookEvent` envelope here once the conversation\ncompletes. This is the same shape workspace endpoints receive:\n`data.object` is the completed Conversation (equal to a direct GET),\nwith the full transcript under `data.messages` and evaluations +\ndata extractors under `data.evaluations`. The workspace endpoints'\n`conversation.completed` is suppressed for this agent (the override\nwins, so the two planes no longer both fire). Empty routes\n`conversation.completed` to your workspace webhook endpoints instead.\n`conversation.started` / `conversation.failed` always ride the\nworkspace endpoints regardless of this field.\n"},"webhook_secret_set":{"type":"boolean","description":"True when an HMAC-SHA256 webhook secret is configured. The\nsecret itself is write-only \u2014 supplied on PATCH and never\nechoed back on reads.\n"},"amd":{"$ref":"#/components/schemas/AMDConfig"},"max_call_duration_seconds":{"type":["integer","null"],"minimum":1,"description":"Hard cap on the wall-clock length of a single call on this\nagent, in seconds. When a call reaches it the agent ends the\ncall automatically. Voice agents only. Null means no\nper-agent cap: the call is bounded only by your plan's call\nceiling, which is also the hard upper bound for this field -\na value above it is rejected.\n"},"save_audio_recording":{"type":"boolean","description":"When true, every conversation produces a room-composite\nOGG egress uploaded to the recordings bucket. Defaults\nFALSE for new agents (privacy by default).\n"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"ai_disclosure":{"$ref":"#/components/schemas/AIDisclosure"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","slug","channels","prompt","first_message","language","llm","tts","turn_handling","memory","navigator","guardrails","background_noise","is_public","allowed_origins","amd","save_audio_recording","ai_disclosure","created_at","updated_at"],"title":"Agent"},"ListAgentsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"agents":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}}},"required":["next_cursor","has_more","agents"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentsResponse"},"CreateAgentRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"name":{"type":"string","minLength":1,"maxLength":100},"slug":{"type":"string","maxLength":64,"description":"Optional. Server derives slug from name with a random suffix when omitted; if you supply your own, a collision returns 400 'slug already taken'."},"channels":{"type":"array","items":{"$ref":"#/components/schemas/AgentChannel"},"description":"The delivery channels the agent serves. Defaults to `[voice]` when\nomitted. Pass an explicit `[]` for an agent with no front door,\nreached by its triggers only. Serving `voice` requires a\n`tts.voice_id`.\n"},"prompt":{"type":"string"},"knowledge_instruction":{"type":"string","maxLength":1000,"description":"A short steer for how the agent uses its knowledge bases: what to\nlook up first, what never to quote, how to answer when nothing\nmatches. It reaches the model as guidance under the prompt and\nshapes the per-turn retrieval query on every channel: live\ncalls, text conversations and agent tests. Empty means none.\n"},"first_message":{"type":"string","description":"Greeting spoken verbatim at session start when included in the agent's flow graph. Omit for an agent with no channels, which opens no live turn."},"language":{"type":"string","description":"ISO 639-1 code. Defaults to 'en' when omitted. The agent's default language."},"additional_languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentAdditionalLanguage"},"description":"Extra languages this agent serves in the same session.\nEach entry's language must be supported, unique, and\ndifferent from the default `language`.\n"},"llm":{"$ref":"#/components/schemas/AgentLLMConfig"},"tts":{"$ref":"#/components/schemas/AgentTTSConfig"},"turn_handling":{"$ref":"#/components/schemas/AgentTurnHandlingConfig"},"memory":{"$ref":"#/components/schemas/AgentMemoryConfig"},"navigator":{"$ref":"#/components/schemas/AgentNavigatorConfig"},"guardrails":{"$ref":"#/components/schemas/AgentGuardrailsConfig"},"background_noise":{"$ref":"#/components/schemas/AgentBackgroundNoiseConfig"},"widget_config":{"$ref":"#/components/schemas/WidgetConfig"},"is_public":{"type":"boolean","description":"Defaults to false when omitted."},"allowed_origins":{"type":"array","items":{"type":"string"}},"hostname_allowlist":{"type":"array","items":{"type":"string"},"description":"Optional per-agent hostname allowlist (see Agent schema)."},"webhook_url":{"type":"string","description":"Customer-facing post-call webhook URL."},"webhook_secret":{"type":"string","description":"HMAC-SHA256 secret seed. Write-only \u2014 never echoed back on\nreads; clients see `webhook_secret_set: true` instead.\n"},"amd":{"$ref":"#/components/schemas/AMDConfig","description":"AMD routing config. Optional on create; omitted means AMD off. See AMDConfig schema."},"max_call_duration_seconds":{"type":["integer","null"],"minimum":1,"description":"Hard cap on the wall-clock length of a single call on this\nagent, in seconds. When a call reaches it the agent ends the\ncall automatically. Voice agents only. Null means no\nper-agent cap: the call is bounded only by your plan's call\nceiling, which is also the hard upper bound for this field -\na value above it is rejected.\n"},"save_audio_recording":{"type":"boolean","description":"When set, opts the agent into per-conversation audio recording. Defaults to false when omitted."},"ai_disclosure":{"$ref":"#/components/schemas/AIDisclosure","description":"Opt-in AI-disclosure opening line. Optional on create; omitted means off. See AIDisclosure schema."}},"required":["name","prompt"],"title":"CreateAgentRequest"},"AgentVoiceType":{"type":"string","enum":["shared","personal"],"description":"Voice provenance. `shared` is the curated catalogue every workspace\nsees; `personal` is a voice this workspace cloned itself (workspace-\nowned clones only). Both can be assigned to an agent. Treat this as an\nopen set - new values may be added, so branch on the ones you know and\nfall through for the rest.\n","title":"AgentVoiceType"},"AgentVoiceModelName":{"type":"string","enum":["simba-3.0","simba-3.2","simba-3.5-turbo"],"title":"AgentVoiceModelName"},"AgentVoiceLanguage":{"type":"object","properties":{"locale":{"type":"string","description":"BCP-47-ish locale tag (e.g. `en-US`, `de-DE`)."},"preview_audio":{"type":["string","null"],"description":"URL to a short audio preview for this locale, or null if\nno preview is available.\n"}},"required":["locale","preview_audio"],"title":"AgentVoiceLanguage"},"AgentVoiceModel":{"type":"object","properties":{"name":{"$ref":"#/components/schemas/AgentVoiceModelName"},"languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentVoiceLanguage"}}},"required":["name","languages"],"description":"One Simba model version the voice can be synthesised through, and\ntherefore one an agent on this voice may pin with `tts.model`.\nEvery voice we speak ourselves supports every model listed here -\n`simba-3.0` plus the English-only tiers `simba-3.2` and\n`simba-3.5-turbo` - because each training conditions on the voice's\nown prompt audio, cloned voices included. A partner voice lists no\ntier at all - Simba model selection does not apply to it.\n\nA pin outside this array is a 400. Which of these `tts.model: null`\nactually resolves to is `default_model`, not the order here.\n\nLike `default_model`, the tiers listed here are the ENGLISH answer,\nand they are a fact about the voice rather than about any one agent.\nAn agent that resolves through the multilingual serving (it declares\n`additional_languages`, or its own `language` is not English) can\nselect none of them, whatever this array says - no upgrade tier has a\nmultilingual deployment. That is `english_only` on\nGET /v1/agents/tts-models, and a picker has to apply it as well as\nthis array. The voice's own `locale` is not what decides: the bucket\nfollows the AGENT's language, so an English agent may pin an\nEnglish-only tier on a voice of any locale.\nThe English-vs-multilingual split is otherwise an internal routing\ndetail the worker resolves per call, not a selectable model.\n\nTreat this as an open set - new model versions are added here as\nthey ship, so branch on the ones you know and fall through for\nthe rest.\n","title":"AgentVoiceModel"},"AgentVoiceGender":{"type":"string","enum":["male","female","not_specified"],"description":"Speaker gender classification for the voice. `not_specified` is\nused when the source dataset didn't carry the metadata; treat it\nas a neutral display label rather than a filter gap.\n","title":"AgentVoiceGender"},"AgentVoice":{"type":"object","properties":{"id":{"type":"string","description":"Voice slug. Passed verbatim as `voice_id` on agent writes."},"type":{"$ref":"#/components/schemas/AgentVoiceType"},"display_name":{"type":"string"},"models":{"type":"array","items":{"$ref":"#/components/schemas/AgentVoiceModel"}},"default_model":{"type":"string","description":"The model an agent on this voice synthesizes on when\n`tts.model` is null - what \"automatic\" resolves to here, stated\noutright rather than inferred from the order of `models`.\n\nThis is the English answer. An agent that resolves through the\nmultilingual serving (it declares `additional_languages`, or\nits own `language` is not English) always runs on `simba-3.0`,\nbecause no other model has a multilingual deployment.\n"},"gender":{"$ref":"#/components/schemas/AgentVoiceGender"},"locale":{"type":"string","description":"Default locale for the voice (BCP-47-ish, e.g. `en-US`)."},"preview_audio":{"type":["string","null"],"description":"Preferred preview clip URL, locale-matched when possible."},"avatar_image":{"type":["string","null"],"description":"Avatar URL for the picker UI. Null when no avatar is\nconfigured; the wire is intentionally `null` rather than\n`\"\"` so the picker doesn't render a broken ` `.\n"},"tags":{"type":["array","null"],"items":{"type":"string"},"description":"VMS-defined tags (e.g. `narrator`, `young`)."}},"required":["id","type","display_name","models","default_model","gender","locale","preview_audio","avatar_image"],"description":"One row in the curated voice catalogue returned by\n`GET /v1/agents/voices`, which is the recommended set to pick from.\nAgent writes may additionally accept a Speechify voice this listing\ndoes not merchandise, so a slug missing here is not necessarily\nrejected.\n","title":"AgentVoice"},"MultilingualLanguage":{"type":"object","properties":{"language":{"type":"string","description":"BCP-47 tag to declare, at the granularity that changes what a\ncaller hears: a locale where the accent is a real choice\n(`es-MX`, `pt-BR`), a plain subtag where it is not (`pl`). The\ntag reaches the speech model as-is. Only one entry per language\nmay be declared on an agent, so `es-MX` and `es-ES` are\nalternatives rather than a pair.\n"},"realtime_supported":{"type":"boolean","description":"False for languages the audio-native realtime model cannot\nserve. Declaring one on a realtime agent is rejected at write\ntime, so pickers should disable it rather than let the save\nfail.\n"},"default_voice_id":{"type":"string","description":"Voice used when the language carries no per-language\n`voice_id`. Omitted when the agent's own voice speaks the\nlanguage, which is what preserves voice identity across a\nswitch.\n"}},"required":["language","realtime_supported"],"description":"One language available to multilingual agents. Which backend\nserves a language is an internal routing decision and is not\nexposed; what callers need is whether it survives on the realtime\npipeline and which voice speaks it by default.\n","title":"MultilingualLanguage"},"ListAgentVoicesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"voices":{"type":"array","items":{"$ref":"#/components/schemas/AgentVoice"}},"multilingual_languages":{"type":"array","items":{"$ref":"#/components/schemas/MultilingualLanguage"},"description":"Every language an agent may declare in `additional_languages`.\nRender language pickers from this rather than a hard-coded\nlist: it is derived from the same routing the write path\nvalidates against, so it can never offer a language a save\nwould reject.\n"}},"required":["next_cursor","has_more","voices","multilingual_languages"],"description":"Payload for `GET /v1/agents/voices`: the curated shared set (the\n`ai-api-agents` VMS scope plus a handful of builtins) followed by the\ncalling workspace's cloned voices. Cursor-paginated - walk pages while\n`has_more` is true.\n","title":"ListAgentVoicesResponse"},"AgentTTSModelInfo":{"type":"object","properties":{"id":{"type":"string","description":"The value to send as `tts.model`."},"display_name":{"type":"string"},"description":{"type":"string"},"beta":{"type":"boolean","description":"A training still under evaluation. Fully servable and supported;\nsimply not one agents are steered onto by default.\n"},"default":{"type":"boolean","description":"True for the model an agent lands on with no pin. The floor of\nthe catalog, and the only entry selectable in every language.\n"},"english_only":{"type":"boolean","description":"The model has no multilingual deployment. An agent that resolves\nthrough the multilingual serving cannot select it at all.\n"},"requires_voice_curation":{"type":"boolean","description":"Deprecated and always `false`. No model requires a voice to be\nregistered for it: every training conditions on the voice's own\nprompt audio, so every model is selectable on every voice we speak\nourselves. The field stays on the response for compatibility and\ncan be ignored.\n","deprecated":true}},"required":["id","display_name","description","beta","default","english_only","requires_voice_curation"],"description":"One Simba model selectable with `tts.model`, plus the facts that\ndecide whether a given agent may select it.\n","title":"AgentTTSModelInfo"},"AgentTTSModelsResponse":{"type":"object","properties":{"models":{"type":"array","items":{"$ref":"#/components/schemas/AgentTTSModelInfo"}}},"required":["models"],"description":"Payload for `GET /v1/agents/tts-models`.","title":"AgentTTSModelsResponse"},"AgentPhoneNumberReferenceResourceType":{"type":"string","enum":["phone_number"],"description":"Always `phone_number`.","title":"AgentPhoneNumberReferenceResourceType"},"AgentPhoneNumberReference":{"type":"object","properties":{"resource_type":{"$ref":"#/components/schemas/AgentPhoneNumberReferenceResourceType","description":"Always `phone_number`."},"id":{"type":"string","pattern":"^[a-z][a-z_]*_[0-9a-hjkmnp-tv-z]{26}$","description":"The number's prefixed external id."},"name":{"type":"string","description":"The number in E.164."}},"required":["resource_type","id","name"],"description":"One live phone number whose binding pins the agent.","title":"AgentPhoneNumberReference"},"AgentPhoneNumberUsedBy":{"type":"object","properties":{"count":{"type":"integer","description":"Number of live phone numbers bound to this agent."},"references":{"type":"array","items":{"$ref":"#/components/schemas/AgentPhoneNumberReference"}}},"required":["count","references"],"description":"The live phone numbers routing to the agent. `count` is the total;\n`references` lists them.\n","title":"AgentPhoneNumberUsedBy"},"AgentInUseErrorDetailDetails":{"type":"object","properties":{"used_by":{"$ref":"#/components/schemas/AgentPhoneNumberUsedBy"}},"required":["used_by"],"description":"Structured, endpoint-specific context beyond the flat\n`fields` map. Present only on the few errors that carry\nit (e.g. the `used_by` referrer list on a credential\ndelete-conflict); its shape depends on the error `code`.\nClients that don't recognise a `details` shape can ignore\nit - the `code` + `message` contract is unchanged.\n","title":"AgentInUseErrorDetailDetails"},"AgentInUseErrorDetail":{"type":"object","properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","description":"Human-readable explanation of this specific occurrence.\nSafe to surface in UI banners or pass to support. The\nwording can change between releases; clients should\nmatch on `code`, not on the message string.\n"},"fields":{"type":"object","additionalProperties":{"type":"string"},"description":"Per-field validation errors as `path -> message`. Only\npresent on 400 responses caused by request validation\n(typically code=`validation_failed`). Keys are field\npaths in dotted/bracket notation; values are short\nhuman explanations safe to inline-surface next to the\noffending form field.\n"},"details":{"$ref":"#/components/schemas/AgentInUseErrorDetailDetails","description":"Structured, endpoint-specific context beyond the flat\n`fields` map. Present only on the few errors that carry\nit (e.g. the `used_by` referrer list on a credential\ndelete-conflict); its shape depends on the error `code`.\nClients that don't recognise a `details` shape can ignore\nit - the `code` + `message` contract is unchanged.\n"},"docs_url":{"type":"string","format":"uri","description":"Link to the documentation that resolves this class of\nerror, when a stable page exists. Rate and concurrency\n429s link the API limits reference, which lists each\nplan's limits and how to raise them.\n"}},"required":["code","message","details"],"description":"The error block on an agent-in-use 409: the standard `ErrorDetail`\n(`code` = `agent_in_use`) whose structured `details.used_by` names\nthe numbers to unbind first.\n","title":"AgentInUseErrorDetail"},"AgentInUseError":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/AgentInUseErrorDetail"},"request_id":{"type":"string"}},"required":["error"],"description":"409 body when an agent delete is refused because a live phone number\nstill routes to it. The standard `Error` envelope (so clients read\n`error.code` = `agent_in_use` and `request_id`); the numbers to\nunbind ride the structured `error.details.used_by` extension.\n","title":"AgentInUseError"},"UpdateAgentRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"Move the resource between workspace projects: a `proj_...` id\nmoves it there, an explicit null moves it back to the implicit\nDefault project, omitted leaves it unchanged.\n"},"name":{"type":"string","minLength":1,"maxLength":100},"prompt":{"type":"string"},"knowledge_instruction":{"type":"string","maxLength":1000,"description":"A short steer for how the agent uses its knowledge bases: what to\nlook up first, what never to quote, how to answer when nothing\nmatches. It reaches the model as guidance under the prompt and\nshapes the per-turn retrieval query on every channel: live\ncalls, text conversations and agent tests. Empty means none.\n"},"first_message":{"type":"string"},"language":{"type":"string"},"channels":{"type":"array","items":{"$ref":"#/components/schemas/AgentChannel"},"description":"Replaces the agent's enabled delivery channels. Adding `voice`\nrequires the agent to have a `tts.voice_id` (already set or set in\nthe same request). Omit to leave channels unchanged.\n"},"additional_languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentAdditionalLanguage"},"description":"Replaces the stored list wholesale when present; an empty\narray clears it back to a single-language agent. Omit to\nleave unchanged.\n"},"llm":{"$ref":"#/components/schemas/AgentLLMConfig"},"tts":{"$ref":"#/components/schemas/AgentTTSConfig"},"turn_handling":{"$ref":"#/components/schemas/AgentTurnHandlingConfig"},"memory":{"$ref":"#/components/schemas/AgentMemoryConfig"},"navigator":{"$ref":"#/components/schemas/AgentNavigatorConfig"},"guardrails":{"$ref":"#/components/schemas/AgentGuardrailsConfig"},"background_noise":{"$ref":"#/components/schemas/AgentBackgroundNoiseConfig"},"widget_config":{"$ref":"#/components/schemas/WidgetConfig"},"is_public":{"type":"boolean"},"allowed_origins":{"type":"array","items":{"type":"string"}},"hostname_allowlist":{"type":"array","items":{"type":"string"},"description":"When supplied, replaces the stored list. Pass an empty\narray to clear enforcement (public agent is open again).\nOmit the field to leave the existing value unchanged.\n"},"webhook_url":{"type":"string"},"webhook_secret":{"type":"string","description":"Rotate the HMAC secret. Write-only."},"amd":{"$ref":"#/components/schemas/AMDConfig","description":"AMD routing config (PATCH-replace, wholesale). Omit to leave the stored config unchanged."},"max_call_duration_seconds":{"type":["integer","null"],"minimum":1,"description":"Hard cap on the wall-clock length of a single call on this\nagent, in seconds. When a call reaches it the agent ends the\ncall automatically. Voice agents only. Null means no\nper-agent cap: the call is bounded only by your plan's call\nceiling, which is also the hard upper bound for this field -\na value above it is rejected. On a PATCH, null clears a\npreviously set cap.\n"},"save_audio_recording":{"type":"boolean"},"ai_disclosure":{"$ref":"#/components/schemas/AIDisclosure","description":"Opt-in AI-disclosure opening line (PATCH-replace, wholesale). Omit to leave the stored config unchanged."}},"description":"Body for PATCH /v1/agents/{agent_id}. JSON merge-patch semantics: every\nfield is optional and omitting it leaves the stored value\nunchanged. Inside a nested config block (llm, tts,\nturn_handling, memory, navigator, background_noise, amd) send only\nthe sub-fields you want to change - an explicit null clears a\nnullable sub-field to its default, a value sets it, and omitted\nsub-fields are left as-is. `slug` is intentionally not patchable\n(changing it would break embed URLs).\n","title":"UpdateAgentRequest"},"agent_flow_getSchema_Response_200":{"type":"object","properties":{},"title":"agent_flow_getSchema_Response_200"},"PublishGateResultDecision":{"type":"string","enum":["passed","overridden","skipped_no_suite"],"description":"The recorded gate decision on a successful publish. A refused\npublish is a 422 (`agent_publish_gate_failed`), so `blocked` /\n`errored` never appear here.","title":"PublishGateResultDecision"},"EvaluationCaseVerdictOutcome":{"type":"string","enum":["passed","failed","errored"],"description":"`errored` = the run could not complete, so the behaviour was not judged (distinct from `failed`).","title":"EvaluationCaseVerdictOutcome"},"EvaluationCriterionVerdictStatus":{"type":"string","enum":["success","failure","unknown"],"description":"`unknown` = the condition did not apply to this run (distinct from `failure`).","title":"EvaluationCriterionVerdictStatus"},"EvaluationCriterionVerdict":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"$ref":"#/components/schemas/EvaluationCriterionVerdictStatus","description":"`unknown` = the condition did not apply to this run (distinct from `failure`)."},"score":{"type":"string","title":"float"},"rationale":{"type":"string"}},"required":["id","name","status","score","rationale"],"description":"One scored condition, uniform across case types.","title":"EvaluationCriterionVerdict"},"EvaluationCaseVerdict":{"type":"object","properties":{"case_id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"outcome":{"$ref":"#/components/schemas/EvaluationCaseVerdictOutcome","description":"`errored` = the run could not complete, so the behaviour was not judged (distinct from `failed`)."},"passed":{"type":"boolean"},"rationale":{"type":"string"},"duration_ms":{"type":"integer","format":"int64"},"criteria":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationCriterionVerdict"}},"result":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Full per-type run detail (transcript, tool calls, per-type\nresult), for a consumer that needs the synthetic conversation.\nPresent when the case ran; absent on an errored case."}},"required":["case_id","name","type","outcome","passed","rationale","duration_ms","criteria"],"description":"One case's outcome with a uniform per-criterion breakdown.","title":"EvaluationCaseVerdict"},"EvaluationVerdict":{"type":"object","properties":{"passed":{"type":"boolean"},"total":{"type":"integer"},"passed_count":{"type":"integer"},"failed_count":{"type":"integer"},"errored_count":{"type":"integer"},"cases":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationCaseVerdict"}}},"required":["passed","total","passed_count","failed_count","errored_count","cases"],"description":"The gate-branchable result: an overall pass/fail plus a per-case\nbreakdown. `passed` is true only when every case passed \u2014 a failed\nor errored case fails the verdict, so a gate fails safe when a case\ncould not be judged.","title":"EvaluationVerdict"},"PublishGateResult":{"type":"object","properties":{"decision":{"$ref":"#/components/schemas/PublishGateResultDecision","description":"The recorded gate decision on a successful publish. A refused\npublish is a 422 (`agent_publish_gate_failed`), so `blocked` /\n`errored` never appear here."},"message":{"type":"string","description":"Human-readable summary of the gate outcome."},"verdict":{"$ref":"#/components/schemas/EvaluationVerdict"},"suite_size":{"type":"integer","description":"How many behavioral cases were scored."},"duration_ms":{"type":"integer","format":"int64","description":"Wall-clock time the gate evaluation took."}},"required":["decision"],"description":"The behavioral-eval gate outcome recorded for a publish. Present only\nwhen the workspace is opted into the publish gate.","title":"PublishGateResult"},"FlowVersion":{"type":"object","properties":{"id":{"type":"string","pattern":"^fver_[0-9a-hjkmnp-tv-z]{26}$","description":"Flow version id (prefixed external id, `fver_...`)."},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"version":{"type":"integer","description":"Monotonic revision number within the agent."},"parent_version_id":{"type":["string","null"],"pattern":"^fver_[0-9a-hjkmnp-tv-z]{26}$","description":"Parent flow version id (prefixed external id, `fver_...`); null for the first version."},"is_active":{"type":"boolean"},"is_draft":{"type":"boolean"},"name":{"type":"string"},"notes":{"type":"string"},"published_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"gate":{"$ref":"#/components/schemas/PublishGateResult"}},"required":["id","agent_id","version","is_active","is_draft"],"description":"One published or draft revision of an agent's flow graph.","title":"FlowVersion"},"FlowGraphNodesItems":{"type":"object","properties":{},"title":"FlowGraphNodesItems"},"FlowGraphEdgesItems":{"type":"object","properties":{},"title":"FlowGraphEdgesItems"},"FlowGraphVariablesItems":{"type":"object","properties":{},"title":"FlowGraphVariablesItems"},"FlowGraph":{"type":"object","properties":{"version":{"$ref":"#/components/schemas/FlowVersion"},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphNodesItems"}},"edges":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphEdgesItems"}},"variables":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphVariablesItems"}}},"required":["version","nodes","edges"],"description":"A flow graph: an ordered set of typed nodes connected by edges,\nplus flow variables. The node, edge, and variable shapes are\ngoverned by the live JSON Schema at GET /v1/agents/flow/schema\nand are intentionally opaque here so this spec cannot drift\nfrom that authoritative definition.\n","title":"FlowGraph"},"GetFlowResponse":{"type":"object","properties":{"draft":{"$ref":"#/components/schemas/FlowGraph"},"active":{"$ref":"#/components/schemas/FlowGraph"}},"description":"Response for GET /v1/agents/{agent_id}/flow.","title":"GetFlowResponse"},"PutFlowRequestNodesItems":{"type":"object","properties":{},"title":"PutFlowRequestNodesItems"},"PutFlowRequestEdgesItems":{"type":"object","properties":{},"title":"PutFlowRequestEdgesItems"},"PutFlowRequestVariablesItems":{"type":"object","properties":{},"title":"PutFlowRequestVariablesItems"},"PutFlowRequest":{"type":"object","properties":{"name":{"type":"string"},"notes":{"type":"string"},"nodes":{"type":"array","items":{"$ref":"#/components/schemas/PutFlowRequestNodesItems"}},"edges":{"type":"array","items":{"$ref":"#/components/schemas/PutFlowRequestEdgesItems"}},"variables":{"type":"array","items":{"$ref":"#/components/schemas/PutFlowRequestVariablesItems"}}},"required":["nodes","edges"],"description":"Request body for PUT /v1/agents/{agent_id}/flow. Replaces the draft graph.","title":"PutFlowRequest"},"FlowValidationIssue":{"type":"object","properties":{"path":{"type":"string","description":"Node / edge / field path the issue applies to; drives editor highlighting."},"code":{"type":"string","description":"Stable per-issue code, e.g. `tool_call.tool_id.invalid`."},"message":{"type":"string"}},"required":["path","code","message"],"description":"One flow-graph validation problem, located by node/edge/field path.","title":"FlowValidationIssue"},"FlowValidationError":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"},"request_id":{"type":"string"},"issues":{"type":"array","items":{"$ref":"#/components/schemas/FlowValidationIssue"}}},"required":["error"],"description":"400 body for flow save / publish / template operations. The standard\n`Error` envelope (so clients read `error.code` = `validation_failed`\nand `request_id`) plus the per-issue `issues` array the flow editor\nuses for node highlighting and the Validator-tab list. `issues` is\nabsent on a plain bad request (e.g. an undecodable body, code\n`bad_request`).\n","title":"FlowValidationError"},"PublishFlowRequest":{"type":"object","properties":{"notes":{"type":"string","description":"Optional changelog note recorded on the published version."},"gate_override_reason":{"type":"string","description":"On a workspace with the behavioral-eval publish gate enabled, a\nnon-empty justification publishes past a failing behavioral suite.\nThe suite still runs and its verdict is recorded with this reason -\nan override bypasses blocking, not measurement."}},"description":"Optional body for POST /v1/agents/{agent_id}/flow/publish.","title":"PublishFlowRequest"},"RollbackFlowRequest":{"type":"object","properties":{"version_id":{"type":"string","description":"The flow version to roll back to (prefixed external id, `fver_...`)."},"gate_override_reason":{"type":"string","description":"On a workspace with the behavioral-eval publish gate enabled, a\nnon-empty justification re-activates a version past a failing\nbehavioral suite. The suite still runs and its verdict is recorded\nwith this reason - an override bypasses blocking, not measurement."}},"required":["version_id"],"title":"RollbackFlowRequest"},"ListFlowVersionsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"versions":{"type":"array","items":{"$ref":"#/components/schemas/FlowVersion"}}},"required":["next_cursor","has_more","versions"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListFlowVersionsResponse"},"FlowTemplate":{"type":"object","properties":{"id":{"type":"string","pattern":"^tmpl_[0-9a-hjkmnp-tv-z]{26}$","description":"Flow template id (prefixed external id, `tmpl_...`)."},"key":{"type":"string","description":"Stable unique key for the template."},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"graph":{"$ref":"#/components/schemas/FlowGraph"},"is_seed":{"type":"boolean","description":"True for platform-provided templates."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","key","name","category","graph","is_seed","created_at","updated_at"],"description":"A reusable flow graph that can be cloned onto an agent as a new draft.","title":"FlowTemplate"},"ListFlowTemplatesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"templates":{"type":"array","items":{"$ref":"#/components/schemas/FlowTemplate"}}},"required":["next_cursor","has_more","templates"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListFlowTemplatesResponse"},"FlowGraphInputNodesItems":{"type":"object","properties":{},"title":"FlowGraphInputNodesItems"},"FlowGraphInputEdgesItems":{"type":"object","properties":{},"title":"FlowGraphInputEdgesItems"},"FlowGraphInputVariablesItems":{"type":"object","properties":{},"title":"FlowGraphInputVariablesItems"},"FlowGraphInput":{"type":"object","properties":{"nodes":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphInputNodesItems"}},"edges":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphInputEdgesItems"}},"variables":{"type":"array","items":{"$ref":"#/components/schemas/FlowGraphInputVariablesItems"}}},"required":["nodes","edges"],"description":"Request-side flow graph: nodes, edges, and variables only.\nUnlike the response-side FlowGraph it carries no `version`\nblock - the server owns version metadata.\n","title":"FlowGraphInput"},"CreateFlowTemplateRequest":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string","description":"Defaults to \"custom\" when omitted."},"graph":{"$ref":"#/components/schemas/FlowGraphInput"}},"required":["key","name","graph"],"description":"Request body for creating (POST) or replacing (PATCH) a flow\ntemplate. PATCH replaces the whole template, it is not a\nfield-by-field patch.\n","title":"CreateFlowTemplateRequest"},"CloneFlowTemplateRequest":{"type":"object","properties":{"agent_id":{"type":"string","description":"The agent that receives the cloned graph as a new draft."}},"required":["agent_id"],"title":"CloneFlowTemplateRequest"},"EvaluationCriterion":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"}},"required":["id","name","description"],"description":"One LLM-scored assertion about the call (\"Did the agent confirm the customer's name?\").","title":"EvaluationCriterion"},"DataCollectionFieldType":{"type":"string","enum":["string","int","number","boolean"],"title":"DataCollectionFieldType"},"DataCollectionField":{"type":"object","properties":{"key":{"type":"string"},"description":{"type":"string"},"type":{"$ref":"#/components/schemas/DataCollectionFieldType"}},"required":["key","description","type"],"description":"A structured value the post-call evaluator should extract from the\ntranscript. `int` is distinct from `number` so downstream consumers\nreceive whole integers without a synthetic decimal.\n","title":"DataCollectionField"},"EvaluationConfig":{"type":"object","properties":{"criteria":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationCriterion"}},"data_collection":{"type":"array","items":{"$ref":"#/components/schemas/DataCollectionField"}}},"required":["criteria","data_collection"],"title":"EvaluationConfig"},"UpdateEvaluationConfigRequest":{"type":"object","properties":{"criteria":{"type":"array","items":{"$ref":"#/components/schemas/EvaluationCriterion"}},"data_collection":{"type":"array","items":{"$ref":"#/components/schemas/DataCollectionField"}}},"required":["criteria","data_collection"],"title":"UpdateEvaluationConfigRequest"},"DynamicVariableType":{"type":"string","enum":["string","number","boolean","json"],"description":"Declared type of a customer-scope variable. Enforced at save time\nand again at session-start when an override value is supplied.\n- `string` - plain text value; interpolated verbatim with `{{name}}`\n- `number` - numeric value; rendered as its decimal representation\n- `boolean` - `true` or `false`\n- `json` - any valid JSON value; use `{{name|json}}` to inject\n safely inside JSON tool bodies\n","title":"DynamicVariableType"},"DynamicVariable":{"type":"object","properties":{"key":{"type":"string","pattern":"^[a-zA-Z0-9_]+$","description":"Variable name. Must match `[a-zA-Z0-9_]+`. The `system__` prefix\nis reserved for platform-populated variables and will be rejected.\n"},"type":{"$ref":"#/components/schemas/DynamicVariableType"},"default":{"description":"Optional default value used when no per-session override is\nsupplied. Must conform to the declared `type`.\n"},"description":{"type":"string","description":"Human-readable note describing the variable."}},"required":["key","type"],"description":"One customer-scope variable definition on an agent. Referenced in\nprompts, first messages, and webhook tool configs via `{{key}}` or\n`{{key|json}}`. Missing variables render as empty string at dispatch\ntime - a typo never breaks a session.\n","title":"DynamicVariable"},"SystemVariableDoc":{"type":"object","properties":{"key":{"type":"string","description":"The reserved variable key (always starts with `system__`)."},"description":{"type":"string","description":"What the variable contains and when it is populated."}},"required":["key","description"],"description":"Documents one reserved `system__*` variable that the platform\nauto-populates at session start. Customers cannot define or\noverride these keys.\n","title":"SystemVariableDoc"},"ListDynamicVariablesResponse":{"type":"object","properties":{"variables":{"type":"array","items":{"$ref":"#/components/schemas/DynamicVariable"},"description":"Customer-defined variables for this agent."},"system_variables":{"type":"array","items":{"$ref":"#/components/schemas/SystemVariableDoc"},"description":"Platform-populated `system__*` variables, provided for\nreference. This list is the same for every agent.\n"}},"required":["variables","system_variables"],"description":"Response for `GET /v1/agents/{agent_id}/variables`. Returns both the\ncustomer-scope variable catalogue and the read-only `system__*`\ncatalogue so a single source of truth is available client-side.\n","title":"ListDynamicVariablesResponse"},"UpdateDynamicVariablesRequest":{"type":"object","properties":{"variables":{"type":"array","items":{"$ref":"#/components/schemas/DynamicVariable"},"description":"The new variable list. Replaces the existing list entirely."}},"required":["variables"],"description":"PATCH body for `PATCH /v1/agents/{agent_id}/variables`. Replaces the\nstored variable list wholesale. Pass an empty array to clear all\nvariables. Up to 20 variables per agent.\n","title":"UpdateDynamicVariablesRequest"},"CreateConversationOverrides":{"type":"object","properties":{"voice_id":{"type":"string","description":"Voice to use for this entire conversation; see\n`GET /v1/agents/voices` for the recommended set.\nFor a multilingual agent, this replaces every\nconfigured per-language voice. The selected voice must support\nthe agent's complete configured language set; otherwise the\nrequest is rejected. An unavailable voice is never silently\nreplaced with the default.\n"}},"description":"Authenticated, per-conversation configuration overrides. Overrides are\nvalidated and applied before the agent is dispatched; the effective\nconfiguration is captured in the conversation's `agent_snapshot`.\n","title":"CreateConversationOverrides"},"CreateConversationRequest":{"type":"object","properties":{"transport":{"type":["string","null"],"description":"Voice transport for the session: one of `web`, `sip_inbound`, or `sip_outbound`. Omit (or null) to use the default, `web`. This endpoint dispatches a LiveKit voice session, so any other value is rejected with a 400."},"user_identity":{"type":"string","description":"Stable opaque identifier for the end-user in your application.\nPersisted verbatim as `caller_identity` for caller correlation\nand memory, but never exposed as the realtime participant\nidentity. The same value sent to an authenticated\n`POST /v1/agents/{agent_id}/sessions` is the same caller record.\nOmit to preserve `user_` as the caller\nkey.\n\nMay not begin with `embed_`, `anon_` or `user_`: those namespaces\nare reserved for identities the platform derives rather than\ntakes on trust, and claiming one is rejected with a 400 naming\n`user_identity`.\n"},"language":{"type":"string","description":"Starts the conversation in one of the agent's configured\nlanguages (the default `language` or an `additional_languages`\nentry, matched by primary subtag). Omit for the agent's default\nlanguage; an unconfigured language is rejected with 400.\n"},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-session variable overrides that merge on top of the agent's\nstored variable defaults for this one conversation. Keys in the\nreserved `system__` namespace are rejected. Values must match the\ndeclared type of the corresponding variable definition on the agent.\n"},"overrides":{"$ref":"#/components/schemas/CreateConversationOverrides"}},"description":"Optional body for `POST /v1/agents/{agent_id}/conversations`.","title":"CreateConversationRequest"},"ConversationStatus":{"type":"string","enum":["pending","active","completed","failed"],"title":"ConversationStatus"},"ConversationTransport":{"type":"string","enum":["web","phone","whatsapp","sip_inbound","sip_outbound","text"],"description":"How the caller reached the agent. `web` is the browser /\nSDK realtime path; the `sip_*` and `phone` variants come\nfrom the telephony stack; `text` is the text/chat channel\n(turn-based, roomless, no call duration).\n","title":"ConversationTransport"},"ConversationEndReason":{"type":"string","enum":["voicemail_message_left","voicemail_hangup","ivr_hangup","unavailable_hangup","agent_ended","caller_hangup","inactivity_timeout","loop_detected","max_duration_reached","flow_budget_exhausted","over_capacity","dial_no_answer","dial_busy","dial_rejected","dial_failed","transferred"],"description":"Coarse termination category. Most reasons are assigned by the\nagent runtime as the call ends; `caller_hangup` may also be\napplied server-side as a post-call catch-all. The `dial_*`\nreasons are assigned server-side on a `failed` conversation\nfor an outbound call that never connected.\n* `voicemail_message_left` \u2014 AMD machine-vm + we spoke the configured drop-message.\n* `voicemail_hangup` \u2014 AMD machine-vm + we terminated silently (action=hangup or empty-message bypass).\n* `ivr_hangup` \u2014 AMD machine-ivr + action=hangup.\n* `unavailable_hangup` \u2014 AMD machine-unavailable (mailbox full / disconnected).\n* `agent_ended` \u2014 LLM-driven end_call builtin.\n* `inactivity_timeout` \u2014 the call ended after the configured silence window elapsed with no activity.\n* `loop_detected` \u2014 a loop guard force-ended the call after several consecutive near-identical user turns (typically an IVR replaying its menu while the agent kept reacting instead of ending the call).\n* `max_duration_reached` - the max-call-duration limit force-ended the call at the platform ceiling (a safety bound on runaway calls).\n* `flow_budget_exhausted` \u2014 the agent's flow ran for longer than the total per-call flow-execution budget and was ended by the runtime. A backstop above every call-duration ceiling; a call that reaches it has been looping rather than conversing.\n* `over_capacity` \u2014 inbound call refused because the workspace was over its active-call concurrency cap; the busy message played and the call hung up. Stamped server-side and excluded from billing.\n* `caller_hangup` \u2014 the caller's leg went away. Stamped immediately when a SIP disconnect is observed; otherwise applied server-side shortly after the call ends as a catch-all (web tab close, network blip, etc.).\n* `dial_no_answer` \u2014 outbound dial: callee did not pick up (SIP 408/480/487, the ringing timeout expired).\n* `dial_busy` \u2014 outbound dial: the line was busy (SIP 486/600).\n* `dial_rejected` \u2014 outbound dial: the call was actively refused (SIP 401/403/407 carrier auth/permission, or 603/607/608 callee decline).\n* `dial_failed` \u2014 outbound dial: any other failure to connect (invalid number, carrier 5xx, malformed trunk address, TLS requirement, transport error). On a `failed` conversation with NULL `duration_ms`.\n* `transferred` \u2014 the caller's leg was handed off to a phone number via SIP REFER (`transfer_to_number`); the carrier moved the leg and the agent's side ended.\n* `null` \u2014 the termination category was not recorded. Legacy calls only; current calls always carry a reason.\n","title":"ConversationEndReason"},"SkillPin":{"type":"object","properties":{"skill_id":{"type":"string","pattern":"^skill_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"version":{"type":"integer"}},"required":["skill_id","name","version"],"description":"Which procedure ran on a call, and at which version. A body is\nimmutable per version, so the pair reconstructs exactly what the model\nwas told however far the catalog has moved since.\n","title":"SkillPin"},"AgentSnapshotInterruptionSensitivity":{"type":"string","enum":["low","medium","high"],"description":"Interruption-sensitivity level the call ran under; null = the platform default.","title":"AgentSnapshotInterruptionSensitivity"},"AgentSnapshotBackgroundNoisePreset":{"type":"string","enum":["office","city","forest","crowded_room","keyboard_typing","hold_music"],"description":"Ambient-bed preset at call time; null = no background noise.","title":"AgentSnapshotBackgroundNoisePreset"},"AgentSnapshot":{"type":"object","properties":{"schema_version":{"type":"integer"},"captured_at":{"type":"string","format":"date-time"},"name":{"type":"string"},"prompt":{"type":"string"},"knowledge_instruction":{"type":"string","description":"The agent's knowledge instruction as captured, empty when none was set."},"first_message":{"type":"string"},"language":{"type":"string"},"channels":{"type":"array","items":{"$ref":"#/components/schemas/AgentChannel"},"description":"Delivery-channel set the agent served at call time."},"skills":{"type":"array","items":{"$ref":"#/components/schemas/SkillPin"},"description":"The procedures this call ran with, each pinned to the version whose\nbody was rendered into the prompt. `prompt` above is the agent's own\ninstructions only. Empty array when none were attached; an ABSENT\nkey means the snapshot pre-dates this field.\n"},"additional_languages":{"type":"array","items":{"$ref":"#/components/schemas/AgentAdditionalLanguage"},"description":"Multilingual config as of call start. Absent on snapshots that pre-date the field and on single-language agents."},"llm_provider":{"type":"string","description":"Resolved provider that actually ran (a \"Platform default\"\nagent freezes the concrete platform pair at call time).\n"},"llm_model":{"type":"string"},"llm_base_url":{"type":["string","null"],"description":"Custom-provider endpoint base URL; null for managed providers. The bearer key is never captured."},"llm_extra_body":{"type":["object","null"],"additionalProperties":{"description":"Any type"},"description":"Extra chat.completions body forwarded verbatim for custom-provider agents; null otherwise."},"voice_id":{"type":"string"},"tts_model":{"type":["string","null"],"description":"The TTS model pinned at call start; null means the call ran on\nthe automatic choice. Records the configured pin, not the model\ndispatch settled on - a pin that had gone stale degrades at\ndispatch, and rewriting the snapshot to the degraded value\nwould hide the drift this field is read to find.\n"},"temperature":{"type":"string","title":"float"},"memory_enabled":{"type":"boolean"},"memory_retention_days":{"type":"integer"},"tts_playback_rate":{"type":["string","null"],"title":"float","description":"Post-process time-stretch at call time; null = no time-stretch (1x)."},"response_delay_seconds":{"type":["string","null"],"title":"float","description":"Minimum silence the agent waited for before replying, at call time; null = turn detection decided."},"interruption_sensitivity":{"$ref":"#/components/schemas/AgentSnapshotInterruptionSensitivity","description":"Interruption-sensitivity level the call ran under; null = the platform default."},"amd":{"$ref":"#/components/schemas/AMDConfig"},"save_audio_recording":{"type":"boolean"},"navigator_mode":{"type":"boolean"},"ivr_memory_enabled":{"type":"boolean"},"observer_guardrail_enabled":{"type":"boolean","description":"Whether the out-of-band injection guardrail was armed when\nthis call started. A conversation is only interpretable\nagainst whether the check was running at the time.\n"},"inactivity_timeout_seconds":{"type":["integer","null"],"description":"Silence-tolerance override at call time; null = platform default."},"max_call_duration_seconds":{"type":["integer","null"],"description":"Per-agent call cap as configured at call time; null = bounded\nonly by the plan ceiling. This is the configured value, not the\neffective one - a plan downgrade can lower what was enforced.\n"},"background_noise_preset":{"$ref":"#/components/schemas/AgentSnapshotBackgroundNoisePreset","description":"Ambient-bed preset at call time; null = no background noise."},"background_noise_volume":{"type":["string","null"],"title":"float"}},"description":"Frozen copy of the agent's behavioral configuration captured at\nconversation-create time so the detail view can\nrender historical calls accurately even after the live agent\nhas been edited. Carries its own `schema_version` because the\nsnapshot shape evolves independently of the live Agent shape.\n\nField-presence contract: new snapshots emit every field\n(explicit null when unset). A key that is ABSENT from a stored\nsnapshot means the snapshot pre-dates that field's capture;\nreaders hide the value instead of guessing a default.\n","title":"AgentSnapshot"},"ConversationIvrSurrenderReason":{"type":"string","enum":["no_goal","no_cached_menu","below_threshold","fingerprint_mismatch","goal_ambiguous","child_cache_miss","dtmf_send_failure","matched_option_missing_dtmf","disabled","repeated_prompt_max_retries"],"description":"Canonical code the worker emits when the IVR\nnavigator gave up. NULL when the navigator completed\ncleanly OR never started a plan.\n* `no_goal` - the goal extractor returned empty.\n* `no_cached_menu` - AMD-time cache miss for the root fingerprint.\n* `below_threshold` - cached menu loaded but confidence < threshold.\n* `fingerprint_mismatch` - in-call prompt diverged from the cached menu fingerprint.\n* `goal_ambiguous` - cached options matched the goal more than once or not at all.\n* `child_cache_miss` - sub-menu fingerprint had no cached row.\n* `dtmf_send_failure` - DTMF press could not be delivered.\n* `matched_option_missing_dtmf` - defensive shape guard.\n* `disabled` - per-agent toggle off OR operator kill switch on.\n* `repeated_prompt_max_retries` - bounded press-retry on the same fingerprint hit its cap of 1.\n","title":"ConversationIvrSurrenderReason"},"Conversation":{"type":"object","properties":{"id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nfor the agent that answers this conversation.\n"},"status":{"$ref":"#/components/schemas/ConversationStatus"},"transport":{"$ref":"#/components/schemas/ConversationTransport"},"created_at":{"type":"string","format":"date-time","description":"When the conversation row was created (the call was\ninitiated). Always present, including for conversations\nthat never started \u2014 unlike `started_at` \u2014 so it is the\ntimestamp to display and sort pending calls by.\n"},"started_at":{"type":["string","null"],"format":"date-time","description":"Set when the first user participant joins the realtime\nvoice session. Null between CreateConversation and the\nparticipant-joined event, and stays null if no user ever\njoins.\n"},"ended_at":{"type":["string","null"],"format":"date-time"},"duration_ms":{"type":["integer","null"]},"cost_cents":{"type":["integer","null"]},"recording_url":{"type":["string","null"]},"recording_started_at":{"type":["string","null"],"format":"date-time","description":"When the recording file actually began capturing audio\n(LiveKit egress file started_at). Anchor transcript message\noffsets on this \u2014 not `started_at` \u2014 when seeking the\nrecording: the file's first frame trails the participant\njoin by the egress recorder's spin-up (~1-2s). Null when\nthere is no recording or the row pre-dates the field.\n"},"end_reason":{"$ref":"#/components/schemas/ConversationEndReason","description":"Coarse termination category. Most reasons are assigned by the\nagent runtime as the call ends; `caller_hangup` may also be\napplied server-side as a post-call catch-all. The `dial_*`\nreasons are assigned server-side on a `failed` conversation\nfor an outbound call that never connected.\n* `voicemail_message_left` \u2014 AMD machine-vm + we spoke the configured drop-message.\n* `voicemail_hangup` \u2014 AMD machine-vm + we terminated silently (action=hangup or empty-message bypass).\n* `ivr_hangup` \u2014 AMD machine-ivr + action=hangup.\n* `unavailable_hangup` \u2014 AMD machine-unavailable (mailbox full / disconnected).\n* `agent_ended` \u2014 LLM-driven end_call builtin.\n* `inactivity_timeout` \u2014 the call ended after the configured silence window elapsed with no activity.\n* `loop_detected` \u2014 a loop guard force-ended the call after several consecutive near-identical user turns (typically an IVR replaying its menu while the agent kept reacting instead of ending the call).\n* `max_duration_reached` - the max-call-duration limit force-ended the call at the platform ceiling (a safety bound on runaway calls).\n* `flow_budget_exhausted` \u2014 the agent's flow ran for longer than the total per-call flow-execution budget and was ended by the runtime. A backstop above every call-duration ceiling; a call that reaches it has been looping rather than conversing.\n* `over_capacity` \u2014 inbound call refused because the workspace was over its active-call concurrency cap; the busy message played and the call hung up. Stamped server-side and excluded from billing.\n* `caller_hangup` \u2014 the caller's leg went away. Stamped immediately when a SIP disconnect is observed; otherwise applied server-side shortly after the call ends as a catch-all (web tab close, network blip, etc.).\n* `dial_no_answer` \u2014 outbound dial: callee did not pick up (SIP 408/480/487, the ringing timeout expired).\n* `dial_busy` \u2014 outbound dial: the line was busy (SIP 486/600).\n* `dial_rejected` \u2014 outbound dial: the call was actively refused (SIP 401/403/407 carrier auth/permission, or 603/607/608 callee decline).\n* `dial_failed` \u2014 outbound dial: any other failure to connect (invalid number, carrier 5xx, malformed trunk address, TLS requirement, transport error). On a `failed` conversation with NULL `duration_ms`.\n* `transferred` \u2014 the caller's leg was handed off to a phone number via SIP REFER (`transfer_to_number`); the carrier moved the leg and the agent's side ended.\n* `null` \u2014 the termination category was not recorded. Legacy calls only; current calls always carry a reason.\n"},"metadata":{"type":"object","additionalProperties":{"description":"Any type"}},"caller_identity":{"type":"string","description":"Stable application-level caller key persisted at session start\nso the post-call memory extractor can pivot memories by\n`(agent_id, caller_identity)`. For authenticated conversation\ncreation, this is `user_identity` when supplied and otherwise\nfalls back to `user_`. It is deliberately\nindependent from the opaque realtime participant identity. Empty\nstring for anonymous widget sessions.\n"},"contact_id":{"type":"string","pattern":"^contact_[0-9a-hjkmnp-tv-z]{26}$","description":"The workspace person this conversation resolved to. Absent while\nthe conversation has no resolved contact.\n"},"from_number":{"type":["string","null"],"description":"E.164 of the phone number that placed the call. For\n`sip_outbound` this is the workspace number used as the\ncaller ID; for `sip_inbound` it is the external caller's\nnumber. Null for `web` conversations and for older\noutbound rows, where the dialing caller id was not recorded\nbefore this field existed.\n"},"to_number":{"type":["string","null"],"description":"E.164 of the phone number that received the call. For\n`sip_outbound` this is the external callee; for\n`sip_inbound` it is the workspace number the caller\ndialed. Null for `web` conversations and for legacy\ninbound rows whose room name did not embed the dialed\nnumber.\n"},"agent_snapshot":{"$ref":"#/components/schemas/AgentSnapshot","description":"Frozen snapshot of the agent's configuration at create\ntime. Populated only on detail responses; list responses\nintentionally skip the column to keep the row small.\n"},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Customer-facing dynamic variables this call ran with: the\nagent's stored variable defaults overlaid with the\nper-session `dynamic_variables` overrides, resolved to\ntheir values. Reserved `system__*` keys are excluded \u2014\nthey are runtime-derived and not part of the audit\nsnapshot. Omitted for SIP inbound calls (which take no\nper-session variables) and for legacy conversations\nrecorded before this field existed. Populated only on\ndetail responses; the\nlist endpoint skips it, mirroring `agent_snapshot`.\n"},"message_count":{"type":"integer","description":"Populated only on the list endpoint via a correlated\nsubquery. Zero on single-row reads where the join cost\nisn't paid.\n"},"ivr_menu_id":{"type":["string","null"],"pattern":"^menu_[0-9a-hjkmnp-tv-z]{26}$","description":"Audit pointer at the cached IVR menu the\nnavigator consulted on this call. NULL when the navigator\nnever engaged OR after the referenced menu was\ninvalidated (FK is ON DELETE SET NULL).\n"},"ivr_path_taken":{"type":["array","null"],"items":{"type":"object","additionalProperties":{"description":"Any type"}},"description":"Ordered log of the navigator's per-call presses:\n`[{fingerprint, dtmf, label}, ...]`. Empty array means\n\"navigator engaged but pressed nothing\" (distinct from\nNULL = \"navigator never engaged\").\n"},"ivr_surrender_reason":{"$ref":"#/components/schemas/ConversationIvrSurrenderReason","description":"Canonical code the worker emits when the IVR\nnavigator gave up. NULL when the navigator completed\ncleanly OR never started a plan.\n* `no_goal` - the goal extractor returned empty.\n* `no_cached_menu` - AMD-time cache miss for the root fingerprint.\n* `below_threshold` - cached menu loaded but confidence < threshold.\n* `fingerprint_mismatch` - in-call prompt diverged from the cached menu fingerprint.\n* `goal_ambiguous` - cached options matched the goal more than once or not at all.\n* `child_cache_miss` - sub-menu fingerprint had no cached row.\n* `dtmf_send_failure` - DTMF press could not be delivered.\n* `matched_option_missing_dtmf` - defensive shape guard.\n* `disabled` - per-agent toggle off OR operator kill switch on.\n* `repeated_prompt_max_retries` - bounded press-retry on the same fingerprint hit its cap of 1.\n"}},"required":["id","agent_id","status","transport","created_at","metadata","message_count"],"title":"Conversation"},"CreateConversationResponse":{"type":"object","properties":{"conversation":{"$ref":"#/components/schemas/Conversation"},"token":{"type":"string","description":"Short-lived realtime session access token (JWT)."},"url":{"type":"string","description":"Realtime session wss:// URL to connect to."}},"required":["conversation","token","url"],"description":"Returned when a conversation is created. The `token` + `url`\nlet the caller connect its browser/SDK directly to the\nrealtime voice session \u2014 the agent that answers is dispatched\nserver-side.\n","title":"CreateConversationResponse"},"CreateSessionRequest":{"type":"object","properties":{"user_identity":{"type":"string","description":"Opaque identifier for the end-user (e.g. your app's user ID),\npersisted as the conversation's `caller_identity` and used as the\nmemory key. Optional - an omitted identity means an anonymous\nsession, which is excluded from per-caller memory.\n\nHow it is stored depends on whether the request was\nauthenticated, because only then has anyone verified the claim:\n\n- **With an API key**: stored verbatim, and the same value on\n `POST /v1/agents/{agent_id}/conversations` is the same caller.\n- **Without credentials** (a public agent embedded in a page):\n stored in a separate `embed_`-prefixed namespace, so a\n self-asserted identity can never resolve to a caller record\n written by a verified surface such as a phone call. Mint\n sessions from your backend if you need one caller record\n across surfaces.\n"},"language":{"type":"string","description":"Starts the session in one of the agent's configured languages\n(the default `language` or an `additional_languages` entry,\nmatched by primary subtag). The greeting and any per-language\nvoice follow. Omit for the agent's default language; an\nunconfigured language is rejected with 400.\n"},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-session variable overrides that merge on top of the agent's\nstored variable defaults for this one session. Keys in the\nreserved `system__` namespace are rejected at this boundary.\nValues must match the declared type of the corresponding variable\ndefinition on the agent (a `string` type expects a JSON string,\n`number` expects a JSON number, etc.).\n"}},"description":"Optional body for `POST /v1/agents/{agent_id}/sessions`. Widget embeds usually pass nothing.","title":"CreateSessionRequest"},"ListConversationsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}},"required":["next_cursor","has_more","conversations"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListConversationsResponse"},"RecentCallee":{"type":"object","properties":{"phone":{"type":"string","description":"E.164 phone number that was dialled."},"last_called_at":{"type":"string","format":"date-time","description":"Timestamp of the most recent outbound call to this number."}},"required":["phone","last_called_at"],"description":"One distinct phone number this workspace has dialled, with the timestamp of the most recent outbound call to it.","title":"RecentCallee"},"ListRecentCalleesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"callees":{"type":"array","items":{"$ref":"#/components/schemas/RecentCallee"}}},"required":["next_cursor","has_more","callees"],"description":"Payload for GET /v1/agents/conversations/recent-callees.","title":"ListRecentCalleesResponse"},"ConversationStats":{"type":"object","properties":{"total":{"type":"integer","format":"int64"},"completed":{"type":"integer","format":"int64"},"failed":{"type":"integer","format":"int64"},"active":{"type":"integer","format":"int64"},"pending":{"type":"integer","format":"int64"},"avg_duration_ms":{"type":["number","null"],"format":"double"},"avg_cost_cents":{"type":["number","null"],"format":"double"}},"required":["total","completed","failed","active","pending","avg_duration_ms","avg_cost_cents"],"description":"Counts + averages over the caller's conversations matching the supplied filters. AVG fields are null when no rows match the FILTER predicate.","title":"ConversationStats"},"MessageRole":{"type":"string","enum":["user","assistant","system","tool","operator"],"description":"Who produced the turn: `user` is the caller, `assistant` the AI\nagent, `tool` a tool call, `system` a transcript annotation (such\nas the take-over window markers), and `operator` a human\noperator's speech while they have taken the call over.\n","title":"MessageRole"},"Message":{"type":"object","properties":{"id":{"type":"string","pattern":"^msg_[0-9a-hjkmnp-tv-z]{26}$"},"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`)\nof the parent conversation.\n"},"role":{"$ref":"#/components/schemas/MessageRole","description":"Who produced the turn: `user` is the caller, `assistant` the AI\nagent, `tool` a tool call, `system` a transcript annotation (such\nas the take-over window markers), and `operator` a human\noperator's speech while they have taken the call over.\n"},"content":{"type":"string"},"tool_name":{"type":["string","null"]},"tool_args":{"type":["object","null"],"additionalProperties":{"description":"Any type"}},"tool_result":{"description":"Arbitrary JSON value returned by the tool (object, array, string, or primitive)."},"started_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}},"required":["id","conversation_id","role","content","started_at"],"title":"Message"},"ListMessagesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}},"required":["next_cursor","has_more","messages"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListMessagesResponse"},"EvaluationKind":{"type":"string","enum":["criterion","summary","data"],"title":"EvaluationKind"},"EvaluationStatus":{"type":"string","enum":["success","failure","unknown"],"description":"Three-state criterion result. `unknown` means the criterion did not apply to this call.","title":"EvaluationStatus"},"Evaluation":{"type":"object","properties":{"id":{"type":"string","pattern":"^eval_[0-9a-hjkmnp-tv-z]{26}$"},"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`)\nof the conversation this evaluation is attached to.\n"},"kind":{"$ref":"#/components/schemas/EvaluationKind"},"criterion_id":{"type":["string","null"]},"name":{"type":"string"},"status":{"oneOf":[{"$ref":"#/components/schemas/EvaluationStatus"},{"type":"null"}],"description":"Three-state criterion result. `unknown` means the criterion did not apply to this call."},"passed":{"type":["boolean","null"]},"score":{"type":["string","null"],"title":"float"},"rationale":{"type":"string"},"data":{"description":"Structured data-collection payload (present only on `kind=data` rows)."},"created_at":{"type":"string","format":"date-time"}},"required":["id","conversation_id","kind","name","rationale","created_at"],"description":"Three flavours coexist, discriminated by `kind`:\n- `criterion` rows carry `status` + `passed` + `score` + `rationale` for one criterion\n- `summary` row carries overall sentiment + rationale in `rationale`\n- `data` row carries the structured data-collection payload in `data`\n\n`status` is the canonical three-state result. `passed` is a\nderived boolean kept for backwards compatibility with earlier\nwebhook consumers: success\u2192true, failure\u2192false, unknown\u2192null.\n","title":"Evaluation"},"ListEvaluationsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"evaluations":{"type":"array","items":{"$ref":"#/components/schemas/Evaluation"}}},"required":["next_cursor","has_more","evaluations"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListEvaluationsResponse"},"Memory":{"type":"object","properties":{"id":{"type":"string","pattern":"^memory_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`memory_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the owning agent.\n"},"caller_identity":{"type":"string","description":"Stable caller key (LiveKit participant identity) the memory is scoped to."},"fact":{"type":"string","description":"Short third-person statement about the caller."},"source_conversation_id":{"type":["string","null"],"description":"When set, the prefixed wire identifier\n(`conv_<26 char Crockford base32>`) of the conversation this\nmemory was extracted from. May be null if the source was\ndeleted.\n"},"source_run_id":{"type":["string","null"],"pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$","description":"When set, the prefixed wire identifier\n(`arun_<26 char Crockford base32>`) of the durable run this\nmemory was learned on. A memory has one source: exactly one of\n`source_conversation_id` and `source_run_id` is set, unless the\nsource was deleted.\n"},"confidence":{"type":"string","title":"float","description":"LLM self-reported 0-1 confidence in the fact's durability and relevance."},"score":{"type":"string","title":"float","description":"Populated only on retrieval hits \u2014 recency-weighted cosine similarity."},"created_at":{"type":"string","format":"date-time"}},"required":["id","agent_id","caller_identity","fact","confidence","created_at"],"description":"One salient fact extracted post-call about a specific caller on\na specific agent. Retrieved at the next conversation-start for\nthe same caller and injected into the agent's system prompt via\nthe `{{memory}}` template variable.","title":"Memory"},"ListMemoriesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"memories":{"type":"array","items":{"$ref":"#/components/schemas/Memory"}}},"required":["next_cursor","has_more","memories"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListMemoriesResponse"},"SearchHitRanking":{"type":"object","properties":{"fused_score":{"type":"string","title":"float","description":"Reciprocal-rank fusion score across the semantic and lexical lanes."},"lexical_rank":{"type":"string","title":"float","description":"Distinct query terms the passage holds, plus its full-text density; set when the lexical lane matched it."},"rerank_score":{"type":"string","title":"float","description":"The cross-encoder's relevance of the passage to the query, when a reranker ran."}},"description":"The component scores behind a hit's position, for a caller that\nwants to see why a passage ranked where it did. Each field is absent\nwhen the stage that produces it did not run. `score` on the hit keeps\nits cosine meaning regardless.\n","title":"SearchHitRanking"},"RetrievalLogResult":{"type":"object","properties":{"chunk_id":{"type":"string","pattern":"^chunk_[0-9a-hjkmnp-tv-z]{26}$"},"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`)\nof the knowledge base the matched chunk lives in.\n"},"filename":{"type":"string"},"chunk_index":{"type":"integer"},"content":{"type":"string"},"score":{"type":"string","title":"float"},"heading_path":{"type":"array","items":{"type":"string"},"description":"The headings the passage sat under when it was retrieved, outermost first."},"source_url":{"type":"string"},"ranking":{"$ref":"#/components/schemas/SearchHitRanking","description":"The component scores behind the position at the time of the search; absent when only the cosine ranked the hit."}},"required":["chunk_id","document_id","kb_id","filename","chunk_index","content","score","heading_path"],"description":"One ranked chunk inside a retrieval log row. Denormalised so\ndeleting a chunk or document after the call doesn't render\nhistorical logs unreadable.\n","title":"RetrievalLogResult"},"RetrievalCoverage":{"type":"string","enum":["sufficient","partial","insufficient"],"description":"Retrieval's verdict on whether the evidence it found answers the\nquestion. `sufficient`: answer from the hits. `partial`: something\nrelated was found; answer with care. `insufficient`: nothing found\nclears even the low bar, and the honest answer is that the knowledge\nbase does not cover this; the agent's `search_knowledge` tool then\nreports no relevant context rather than the weak hits.\n","title":"RetrievalCoverage"},"RetrievalLogEntry":{"type":"object","properties":{"id":{"type":"string","pattern":"^retlog_[0-9a-hjkmnp-tv-z]{26}$"},"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`)\nof the conversation.\n"},"query":{"type":"string"},"results":{"type":"array","items":{"$ref":"#/components/schemas/RetrievalLogResult"}},"limit":{"type":"integer"},"hit_count":{"type":"integer"},"node_key":{"type":"string","description":"Key of the flow node that was active when the agent ran this\nsearch. Empty when the call ran without a flow, or for rows\nrecorded before phase attribution existed.\n"},"retrieval_version":{"type":"string","description":"The retrieval contract that produced this row, so results can be\ncompared like with like across a rollout. Rows recorded before\nversioning carry `v1`.\n"},"coverage":{"$ref":"#/components/schemas/RetrievalCoverage","description":"The verdict the agent was given for this search. Absent on rows\nrecorded before coverage existed.\n"},"latency_ms":{"type":"integer","description":"How long the search took, in milliseconds. Absent on rows\nrecorded before it was measured.\n"},"searched_knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The knowledge bases this search actually ran against. Equal\nto the agent's full attached set unless the active subagent\nnode carried a `knowledge_base_filter`, in which case it is\nthe narrowed subset \u2014 so the log shows which sources each\nphase of the call was allowed to consult, not just which\nones returned a hit.\n"},"created_at":{"type":"string","format":"date-time"}},"required":["id","conversation_id","query","results","limit","hit_count","node_key","retrieval_version","searched_knowledge_base_ids","created_at"],"description":"One `search_knowledge` invocation recorded against a\nconversation. Powers the Retrieval panel on the conversation\ndetail view.\n","title":"RetrievalLogEntry"},"ListRetrievalLogsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"entries":{"type":"array","items":{"$ref":"#/components/schemas/RetrievalLogEntry"}}},"required":["next_cursor","has_more","entries"],"description":"Payload for `GET /v1/agents/conversations/{conversation_id}/retrieval-log`.","title":"ListRetrievalLogsResponse"},"WebhookDeliveryStatus":{"type":"string","enum":["pending","delivered","failed"],"description":"Lifecycle of a post-call webhook delivery row. The sender\nupdates the same row across retries so the UI always sees the\nlatest outcome.\n","title":"WebhookDeliveryStatus"},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","pattern":"^whd_[0-9a-hjkmnp-tv-z]{26}$"},"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`conv_<26 char Crockford base32>`)\nof the conversation that triggered this delivery.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent.\n"},"url":{"type":"string"},"event":{"type":"string"},"status":{"$ref":"#/components/schemas/WebhookDeliveryStatus"},"attempt_count":{"type":"integer"},"last_attempt_at":{"type":"string","format":"date-time"},"last_status_code":{"type":"integer"},"last_error":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"request_body":{"type":"string","description":"The exact JSON body Speechify POSTed to your webhook URL. This\nis the verbatim payload the `Speechify-Signature` HMAC was\ncomputed over (as `.`), so you can re-verify the\nsignature against it. Absent on deliveries that pre-date this\ncapture.\n"},"request_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"The HTTP headers Speechify sent with the delivery, including\n`Speechify-Signature` (`t=,v0=`),\n`Speechify-Event`, and `Speechify-Delivery-Id`. Reflects the\nmost recent attempt.\n"},"last_response_body":{"type":"string","description":"The response body your server returned on the most recent\nattempt, truncated to 8 KiB. Useful for debugging a non-2xx\nresponse. Absent when no response was received (transport\nerror) or on pre-capture deliveries.\n"},"last_response_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"The response headers your server returned on the most recent\nattempt.\n"}},"required":["id","conversation_id","agent_id","url","event","status","attempt_count","created_at"],"description":"Post-call webhook delivery log row. One row per\n`(conversation, webhook-url)`; updated in place across retry\nattempts.\n","title":"WebhookDelivery"},"ListWebhookDeliveriesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}},"required":["next_cursor","has_more","deliveries"],"description":"Payload for `GET /v1/agents/conversations/{conversation_id}/webhook-deliveries`.","title":"ListWebhookDeliveriesResponse"},"ShadowConversationResponse":{"type":"object","properties":{"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$"},"signaling_url":{"type":"string","description":"wss://\u2026 signaling URL the realtime client connects to."},"token":{"type":"string","description":"Short-lived realtime access token. The grant is listen-only\n(cannot publish audio or data) and hidden, so the observer can\nhear the call but cannot speak and is invisible to the caller\nand the agent.\n"},"identity":{"type":"string","description":"Opaque participant identity tag (e.g. shadow_). Visible only to admin tooling."},"expires_at":{"type":"string","format":"date-time","description":"When the token stops being accepted by the realtime provider. Re-mint past this point."}},"required":["conversation_id","signaling_url","token","identity","expires_at"],"description":"Connection details for an authorized observer (workspace owner or\nadmin) joining an active conversation as a hidden, listen-only\nparticipant. A realtime client consumes `signaling_url` + `token`\nto attach to the live room and play the agent + caller audio\ntracks.\n","title":"ShadowConversationResponse"},"PublishAgentRequest":{"type":"object","properties":{"gate_override_reason":{"type":"string","minLength":10,"description":"On a gated workspace, a non-empty justification publishes past failing\nchecks. The suite still runs and its verdict is recorded with this\nreason - an override bypasses blocking, not measurement. Must be at\nleast 10 characters after trimming."}},"description":"Optional body for POST /v1/agents/{agent_id}/publish.","title":"PublishAgentRequest"},"PublishAgentResponse":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"The agent that was published."},"config_fingerprint":{"type":"string","description":"Identifies the exact configuration this decision covers. A run is\nadmitted only against a fingerprint carrying a passing (or overridden)\ndecision, so any edit that changes the agent's behavior produces a new\nfingerprint and requires publishing again."},"published_at":{"type":"string","format":"date-time","description":"When the configuration was published."},"gate":{"$ref":"#/components/schemas/PublishGateResult","description":"The gate outcome. Absent when the workspace is not gated, in which case\nnothing was scored and runs are admitted without a gate check."}},"required":["agent_id","config_fingerprint","published_at"],"description":"The outcome of publishing a task agent's current configuration.","title":"PublishAgentResponse"},"PublishGateRunDecision":{"type":"string","enum":["passed","blocked","overridden","errored","skipped_no_suite"],"description":"What the gate decided. `overridden` means the checks did NOT pass and a\nhuman published anyway with a recorded justification - it is not a pass.\n`skipped_no_suite` means nothing was checked.","title":"PublishGateRunDecision"},"PublishGateRun":{"type":"object","properties":{"decision":{"$ref":"#/components/schemas/PublishGateRunDecision","description":"What the gate decided. `overridden` means the checks did NOT pass and a\nhuman published anyway with a recorded justification - it is not a pass.\n`skipped_no_suite` means nothing was checked."},"passed_count":{"type":"integer","description":"Behavioral cases that passed."},"total":{"type":"integer","description":"Behavioral cases scored."},"redteam_passed_count":{"type":"integer","description":"Red-team probes that passed."},"redteam_total":{"type":"integer","description":"Red-team probes scored."},"created_at":{"type":"string","format":"date-time","description":"When the decision was recorded."}},"required":["decision","passed_count","total","redteam_passed_count","redteam_total","created_at"],"description":"A recorded publish-gate decision, projected for display. The counts are\nreported per suite: the behavioral suite (the workspace's own tests) and\nthe adversarial red-team suite are scored independently.","title":"PublishGateRun"},"LatestPublishGateRunResponse":{"type":"object","properties":{"gate_run":{"oneOf":[{"$ref":"#/components/schemas/PublishGateRun"},{"type":"null"}],"description":"Null when the agent has never been gated."},"published":{"type":"boolean","description":"Whether the agent's current configuration carries a passing (or overridden)\ndecision, so a run on it is admitted. A trigger whose agent reads `false`\nhere will fail every fire until the agent is published again."},"config_fingerprint":{"type":"string","description":"The identity of the current configuration - what a publish records. It moves\nwith the prompt, model, guardrails, tools and their schemas, never with the\nknowledge bases attached or with per-run variables."}},"required":["gate_run","published","config_fingerprint"],"description":"The agent's most recent publish-gate decision, and whether the configuration\nit has RIGHT NOW is published. The two differ after an edit: the last decision\nstill reads `passed` while `published` is false, because the edit re-armed the\ngate. Attaching or detaching a knowledge base is not such an edit.","title":"LatestPublishGateRunResponse"},"CreateAgentRunRequest":{"type":"object","properties":{"instruction":{"type":"string","maxLength":8000,"description":"The task or goal to give the agent. The agent runs its brain against this over a short internal conversation and returns its result. The server's limit is 8000 **bytes**, so a mostly non-ASCII instruction reaches it before 8000 characters."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-run values that seed the agent's flow variables (override its\nstored defaults). The agent's prompt renders against the result\nbefore every step: a declared variable the run does not supply\ntakes its default, one the run supplies takes the run's value, and\na placeholder nothing supplies renders empty. The reserved\n`system__caller_id`, `system__agent_id`, `system__language` and\n`system__memory` keys are bound by the platform. The `system__*` namespace and the legacy `memory`\nalias belong to the platform and are rejected with a 400 naming\n`variables`, the same rule a conversation applies: the run binds its\nown values there, including `system__caller_id` for the person it\nacts for.\n"},"max_turns":{"type":"integer","description":"Upper bound on the run's internal turn budget - one turn is one\nplan-act-observe cycle, so a run that calls three tools uses at\nleast four. Defaults to 8 when omitted.\n\n**Clamped to the workspace's per-run ceiling** (5 on Free, 10\non Starter, 20 on Pro, 30 on Scale, 50 on Enterprise; per-workspace\noverrides apply): the run's `input.max_turns` echoes the budget it\nactually got, and `GET /v1/workspaces/current/entitlements`\n(`max_run_turns`) reports the ceiling up front, so plan against\nthat rather than the value you sent. An omitted `max_turns` takes\nthe default, clamped to the ceiling. On Free the\nceiling is *below* the default, so omitting this field there yields\n5, not 8.\n\nA run that exhausts its budget settles `succeeded` with\n`incomplete_reason: max_turns_exhausted` and whatever answer it had\nreached. Schema repairs count against this budget too - see\n`output_schema`.\n"},"user_identity":{"type":"string","maxLength":256,"description":"The person this run acts for, in your own vocabulary - the same\nfield a conversation and a widget session take, so one workspace\nnever has two answers to who a person is. The agent opens the run\nknowing what it has already learned about them, and what a run\nthat succeeds learns from its instruction and its reply is\nwritten back under this value, exactly as a call writes memory.\n\nOmit it to run the agent for nobody in particular, which is how a\nrun behaves with no memory of anyone and learns nothing. Must not begin with `user_`,\n`embed_` or `anon_`, which name identities the platform derives.\n\nEvery tool the run calls is told this value: a webhook receives\nit as `user_identity` inside the signed body, an MCP server as\nthe `Speechify-User-Identity` header, and it renders in a tool's\ntemplated URL or headers as `{{system__caller_id}}`. A connector\nyou wrote can therefore look up that person's own third-party\ntoken, which is how you integrate a system Speechify holds no\ncredentials for.\n"},"output_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional JSON Schema (2020-12) the run's final answer must satisfy.\nWhen set, the agent answers with a JSON object, the platform\nvalidates it, and the conforming object is returned as\n`output.data`.\n\nThe top level must be `type: object` - an array-typed or scalar\nschema is refused at create with `400`. At most 16 KiB.\n\nOn a mismatch the platform re-asks the agent, feeding back up to 8\nof the violations. **At most two repair attempts, and each one\nspends a turn from `max_turns`** - so a schema-constrained run on a\n5-turn ceiling has little room left for tool calls. Each attempt is\njournaled as an `observation` step whose `tool` is the reserved\nname `output_schema`, which a timeline renderer should expect\nalongside real tool names.\n\nA run that never produces a conforming object settles `succeeded`\nwith `incomplete_reason: output_schema_violation` and no\n`output.data` - its prose answer is still on `output.reply`. The\nplatform never returns an object the schema refused.\n"},"metadata":{"type":"object","additionalProperties":{"type":"string"},"description":"Up to 16 arbitrary key/value pairs echoed back on the run. Your own correlation ids belong here - the platform never reads them."}},"required":["instruction"],"description":"Body for `POST /v1/agents/{agent_id}/runs`.","title":"CreateAgentRunRequest"},"AgentRunStatus":{"type":"string","enum":["queued","running","requires_action","canceling","succeeded","failed","canceled","expired"],"description":"Lifecycle: `queued` -> `running` -> `succeeded` | `failed` | `canceled` | `expired`. `requires_action` (a pending human approval) and `canceling` are transient. Terminal set: succeeded, failed, canceled, expired.","title":"AgentRunStatus"},"AgentRunInputDelegationTargetsItems":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"role":{"type":"string"},"delegation":{"type":"string"}},"title":"AgentRunInputDelegationTargetsItems"},"AgentRunInput":{"type":"object","properties":{"instruction":{"type":"string"},"variables":{"type":"object","additionalProperties":{"description":"Any type"}},"max_turns":{"type":"integer"},"user_identity":{"type":"string","description":"The person the run acts for, as supplied at creation. Absent when the run was started for nobody in particular."},"output_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The JSON Schema the run's structured output must satisfy, frozen at creation. Absent for a prose-only run."},"delegation_targets":{"type":"array","items":{"$ref":"#/components/schemas/AgentRunInputDelegationTargetsItems"},"description":"The team members this run may delegate sub-goals to, present only on a team run (started via `runTeam`). Each is a member agent with its role and hand-off note. Absent for a solo agent run."}},"required":["instruction"],"description":"The frozen request the run was created with.","title":"AgentRunInput"},"AgentRunOutputTranscriptItems":{"type":"object","properties":{},"title":"AgentRunOutputTranscriptItems"},"AgentRunOutput":{"type":"object","properties":{"reply":{"type":"string","description":"The agent's final answer."},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The structured output, present only when the run carried an\n`output_schema` and the agent produced an object satisfying it.\nNever present alongside `incomplete_reason: output_schema_violation`.\n"},"transcript":{"type":"array","items":{"$ref":"#/components/schemas/AgentRunOutputTranscriptItems"},"description":"The per-step conversation record."}},"description":"The run's result, present only when `status` is `succeeded`.","title":"AgentRunOutput"},"AgentRunError":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"description":"Present only when `status` is `failed`.","title":"AgentRunError"},"AgentRunIncompleteReason":{"type":"string","enum":["max_turns_exhausted","budget_exhausted","output_schema_violation"],"description":"Why a non-failed run stopped short: `max_turns_exhausted`, `budget_exhausted`, or `output_schema_violation` (the agent never produced an object matching `input.output_schema`; `output.reply` keeps its prose and `output.data` is absent).","title":"AgentRunIncompleteReason"},"AgentRunUsageModelsItems":{"type":"object","properties":{"model":{"type":"string","description":"The model that spent these tokens."},"input_tokens":{"type":"integer","minimum":0},"cached_input_tokens":{"type":"integer","minimum":0},"output_tokens":{"type":"integer","minimum":0},"total_tokens":{"type":"integer","minimum":0},"cost_micro_usd":{"type":"integer","format":"int64","minimum":0}},"required":["model","input_tokens","cached_input_tokens","output_tokens","total_tokens","cost_micro_usd"],"title":"AgentRunUsageModelsItems"},"AgentRunUsage":{"type":"object","properties":{"duration_ms":{"type":"integer","minimum":0,"description":"Wall-clock time the agent spent working, summed across steps. Excludes time the run spent queued or parked on a human."},"input_tokens":{"type":"integer","minimum":0,"description":"Input tokens summed across every step, cached reads included."},"cached_input_tokens":{"type":"integer","minimum":0,"description":"The subset of `input_tokens` served from provider cache reads. Absent when the provider reported none."},"output_tokens":{"type":"integer","minimum":0,"description":"Output tokens summed across every step, reasoning tokens included."},"total_tokens":{"type":"integer","minimum":0,"description":"`input_tokens` + `output_tokens`."},"cost_micro_usd":{"type":"integer","format":"int64","minimum":0,"description":"What the run cost in millionths of a US dollar, the sum of `models[].cost_micro_usd`, priced at your plan's rates on the rate card named by `rate_card_version`. Informational; the invoice is authoritative."},"rate_card_version":{"type":"string","description":"The rate card version the cost was computed against."},"models":{"type":"array","items":{"$ref":"#/components/schemas/AgentRunUsageModelsItems"},"description":"The token usage and cost split per model, in the order the run first used each; the totals above are the sum over these entries. A run that never changed model has one entry. The model named is the one that actually served the steps, which can differ from the model the agent is configured with when the platform routes a step elsewhere; it is a report, not a promise that the same model serves the next run. An entry whose `model` is empty covers steps whose provider reported no model name."}},"description":"What the run spent, present only once it has settled: wall-clock, the tokens summed across every step, the same tokens split per model, and what they cost at your plan's rates. Written once at settle and never restated; the webhook payload carries the same values. `input_tokens` includes the cached reads and `cached_input_tokens` is that subset, so the two are never added. `cost_micro_usd` is an integer in millionths of a US dollar, computed from the rate card version named alongside it, and is informational: your invoice is authoritative. A delegated child run reports its own usage; the parent never sums it.","title":"AgentRunUsage"},"PendingActionDefaultDecision":{"type":"string","enum":["approve","deny"],"description":"The decision applied automatically if nobody responds by `expires_at`. Defaults to `deny`, so an unattended action is refused rather than taken.","title":"PendingActionDefaultDecision"},"PendingAction":{"type":"object","properties":{"key":{"type":"string","description":"Stable id of this pending action; pass as `action_key` to `submitRun`."},"type":{"type":"string","description":"The action's class (e.g. `send_email`, `financial`)."},"reason":{"type":"string","description":"Why this action needs human approval."},"summary":{"type":"string","description":"Human-readable rendering of the action."},"payload":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The exact structured action awaiting approval."},"approve_effect":{"type":"string","description":"What approving does."},"deny_effect":{"type":"string","description":"What denying does."},"timeout_effect":{"type":"string","description":"What happens if the deadline passes with no response."},"default_decision":{"$ref":"#/components/schemas/PendingActionDefaultDecision","description":"The decision applied automatically if nobody responds by `expires_at`. Defaults to `deny`, so an unattended action is refused rather than taken."},"requested_at":{"type":"string","format":"date-time","description":"When the run parked and started waiting for a person."},"expires_at":{"type":"string","format":"date-time","description":"When the run stops waiting and applies `default_decision` on its own. **The window is 4 hours from `requested_at` unless the action sets its own**, clamped to between 1 minute and 24 hours - so an approval raised in the evening is decided overnight without a human. Build your approvals inbox around that, or shorten the window on the actions that warrant it."}},"required":["key","type","reason","default_decision","requested_at","expires_at"],"description":"A human approval a run is durably parked on (present on `AgentRun` only while `status` is `requires_action`). Rendered VERBATIM for the approver - never a summary the agent wrote - so an injected agent cannot misrepresent what it is about to do. Resolve it with `submitRun`.","title":"PendingAction"},"AgentRun":{"type":"object","properties":{"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project this run belongs to; null when it belongs to none.\nCaptured when the run was created, so it does not move if the agent later does.\n"},"id":{"type":"string","description":"Run id (prefixed external id, `arun_...`)."},"agent_id":{"type":"string","description":"The agent that ran. On a delegated child this is the member agent, not the manager."},"status":{"$ref":"#/components/schemas/AgentRunStatus","description":"Lifecycle: `queued` -> `running` -> `succeeded` | `failed` | `canceled` | `expired`. `requires_action` (a pending human approval) and `canceling` are transient. Terminal set: succeeded, failed, canceled, expired."},"input":{"$ref":"#/components/schemas/AgentRunInput","description":"The frozen request the run was created with."},"output":{"$ref":"#/components/schemas/AgentRunOutput","description":"The run's result, present only when `status` is `succeeded`."},"error":{"$ref":"#/components/schemas/AgentRunError","description":"Present only when `status` is `failed`."},"incomplete_reason":{"$ref":"#/components/schemas/AgentRunIncompleteReason","description":"Why a non-failed run stopped short: `max_turns_exhausted`, `budget_exhausted`, or `output_schema_violation` (the agent never produced an object matching `input.output_schema`; `output.reply` keeps its prose and `output.data` is absent)."},"usage":{"$ref":"#/components/schemas/AgentRunUsage","description":"What the run spent, present only once it has settled: wall-clock, the tokens summed across every step, the same tokens split per model, and what they cost at your plan's rates. Written once at settle and never restated; the webhook payload carries the same values. `input_tokens` includes the cached reads and `cached_input_tokens` is that subset, so the two are never added. `cost_micro_usd` is an integer in millionths of a US dollar, computed from the rate card version named alongside it, and is informational: your invoice is authoritative. A delegated child run reports its own usage; the parent never sums it."},"pending_action":{"$ref":"#/components/schemas/PendingAction"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"parent_run_id":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$","description":"The run that delegated this one a sub-goal, absent on a root run. It is the upward half of lineage - what makes a child run of a member agent attributable to the team run that caused it (`listRunChildren` is the downward half)."},"created_at":{"type":"string","format":"date-time","description":"When the run was accepted and queued."},"started_at":{"type":"string","format":"date-time","description":"When a worker picked the run up. Absent while `queued`; the gap between this and `created_at` is queue wait, not agent time."},"ended_at":{"type":"string","format":"date-time","description":"When the run settled. Present for every terminal status - succeeded, failed, canceled and expired alike - and absent otherwise."}},"required":["id","agent_id","status","input","created_at"],"description":"A durable async agent run. Enqueue it, get this handle, follow it with the run event stream (or poll `status`) until terminal. Backed by a durable job, so it survives a deploy.","title":"AgentRun"},"V1AgentsAgentIdRunsGetParametersStatus":{"type":"string","enum":["queued","running","requires_action","succeeded","failed","canceled"],"title":"V1AgentsAgentIdRunsGetParametersStatus"},"ListAgentRunsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/AgentRun"}}},"required":["next_cursor","has_more","runs"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentRunsResponse"},"V1AgentsRunsGetParametersStatus":{"type":"string","enum":["queued","running","requires_action","succeeded","failed","canceled"],"title":"V1AgentsRunsGetParametersStatus"},"SubmitAgentRunRequestDecision":{"type":"string","enum":["approve","deny"],"description":"The approval disposition. `approve` resumes the run to execute the pending action; `deny` resumes it down its could-not-complete path.","title":"SubmitAgentRunRequestDecision"},"SubmitAgentRunRequest":{"type":"object","properties":{"decision":{"$ref":"#/components/schemas/SubmitAgentRunRequestDecision","description":"The approval disposition. `approve` resumes the run to execute the pending action; `deny` resumes it down its could-not-complete path."},"action_key":{"type":"string","description":"The pending action being decided (the run's `pending_action.key`). Required, so a decision always targets the specific action the human saw: it guards against resolving a stale action - a `409 agent_run_action_stale` if it no longer matches the run's current pending action."},"note":{"type":"string","maxLength":2000,"description":"Optional free-text rationale recorded on the immutable approval audit entry."}},"required":["decision","action_key"],"description":"Body for `POST /v1/agents/{agent_id}/runs/{run_id}/submit`.","title":"SubmitAgentRunRequest"},"AgentRunStepKind":{"type":"string","enum":["plan","tool_call","observation","delegation","output"],"description":"The step type. `output` is reserved for a future step kind - a run's final answer lives on the run object today, not in the journal.","title":"AgentRunStepKind"},"RunStepDelegationChildStatus":{"type":"string","enum":["queued","running","requires_action","canceling","succeeded","failed","canceled","expired"],"description":"The state the child settled in, present on the `observation` step that folded its result back. Absent on the `delegation` step, where the child has not settled yet.","title":"RunStepDelegationChildStatus"},"RunStepDelegation":{"type":"object","properties":{"child_run_id":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$","description":"The child run that executed the sub-goal."},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"The team member the sub-goal went to. Absent when a run delegated to ITSELF (a same-agent sub-goal), which involves no member hand-off."},"depth":{"type":"integer","description":"The child's depth in the delegation tree (a root run is 0, so its children are 1). The runtime refuses a delegation past a fixed depth, bounding recursion. Present on the `delegation` step."},"child_status":{"$ref":"#/components/schemas/RunStepDelegationChildStatus","description":"The state the child settled in, present on the `observation` step that folded its result back. Absent on the `delegation` step, where the child has not settled yet."},"batch_size":{"type":"integer","description":"How many sub-goals the manager delegated in the SAME turn. A manager may hand out several at once; they run concurrently and only read as peers of that one turn if you can tell which of the crew each is. Absent on a single-target delegation."},"batch_index":{"type":"integer","description":"0-based position of this hand-off within its concurrent batch."}},"required":["child_run_id"],"description":"The hand-off a step describes: which team member took a sub-goal, and the child run that pursued it.\nIt appears on BOTH halves of a hand-off, keyed by the same `child_run_id`: the `delegation` step that made it (carrying `depth`), and the later `observation` step that folded the child's result back (carrying `child_status`). Pair them on `child_run_id` to read a sub-goal and its outcome together. The sub-goal and the result themselves are those steps' `content`.\n`child_run_id` is a real run - fetch it with `getRun` (under `agent_id`) for the member's own status, output, and token usage. That is what makes a team run traversable: the parent's timeline links to each member's actual work rather than summarizing it.","title":"RunStepDelegation"},"AgentRunStep":{"type":"object","properties":{"seq":{"type":"integer","description":"1-based position in the run's append-only journal (ascending = chronological)."},"kind":{"$ref":"#/components/schemas/AgentRunStepKind","description":"The step type. `output` is reserved for a future step kind - a run's final answer lives on the run object today, not in the journal."},"content":{"type":"string","description":"The step's payload, per kind: a `plan`'s rationale, an `observation`'s result, or a `delegation`'s sub-goal (redacted)."},"tool":{"type":"string","description":"The tool a `tool_call` / `observation` step is about.\n\nIt is not always one of your own tools. Platform tools appear under\ntheir reserved names - `search_knowledge`, `fetch_url`, `run_code`,\n`store_query`, `store_get`, `store_put`, `store_delete`,\n`store_read_asset` - an MCP tool appears as\n`__`, and a schema-repair observation\ncarries the reserved pseudo-tool `output_schema`. Match against\nyour own toolbelt expecting misses.\n"},"args":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The rendered arguments of a `tool_call`, with credential-shaped values redacted."},"delegation":{"$ref":"#/components/schemas/RunStepDelegation"},"duration_ms":{"type":"integer","description":"Wall-clock the brain spent producing this step."},"model":{"type":"string","description":"The model that served this step, as the provider reported it; absent when it reported none. The run's `usage.models` is the sum over its steps by this value."},"input_tokens":{"type":"integer","description":"Input tokens this step spent, cached reads included. The run's `usage` is the sum over its steps."},"cached_input_tokens":{"type":"integer","description":"The subset of `input_tokens` served from provider cache reads. Absent when the provider reported none."},"output_tokens":{"type":"integer","description":"Output tokens this step spent."},"total_tokens":{"type":"integer","description":"This step's input plus output tokens."},"created_at":{"type":"string","format":"date-time","description":"When the step was committed to the journal."}},"required":["seq","kind","created_at"],"description":"One iteration of a durable run's plan-act-observe loop, journaled in order. A `plan` step carries the agent's rationale (`content`); a `tool_call` step carries the tool it invoked (`tool`) and its rendered arguments (`args`); an `observation` step carries the tool's result (`content`); a `delegation` step carries the sub-goal it handed to a team member (`content`) and the member + child run that took it (`delegation`). The run's final answer is not a step - read it from the run object. Credential-shaped values in `args` and `content` are redacted.","title":"AgentRunStep"},"ListAgentRunStepsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"steps":{"type":"array","items":{"$ref":"#/components/schemas/AgentRunStep"}}},"required":["next_cursor","has_more","steps"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentRunStepsResponse"},"AgentRunStepAddedEventKind":{"type":"string","enum":["plan","tool_call","observation","delegation","output"],"description":"The step type. `output` is reserved for a future step kind - a run's final answer lives on the run object today, not in the journal.","title":"AgentRunStepAddedEventKind"},"AgentRunStatusChangedEventStatus":{"type":"string","enum":["queued","running","requires_action","canceling","succeeded","failed","canceled","expired"],"title":"AgentRunStatusChangedEventStatus"},"AgentRunEndedEventStatus":{"type":"string","enum":["succeeded","failed","canceled","expired"],"title":"AgentRunEndedEventStatus"},"AgentRunEndedEventOutput":{"type":"object","properties":{"reply":{"type":"string","description":"The agent's final answer."},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The structured output, present only when the run carried an\n`output_schema` and the agent produced an object satisfying it.\n"}},"description":"The run's answer, as Get Agent Run returns it but without\n`transcript` - the stream has already delivered every step\nindividually, so repeating them here would send the run twice.\nAbsent on a run that produced no answer (failed, cancelled, or\nstopped short).","title":"AgentRunEndedEventOutput"},"AgentRunStreamEvent":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["run.step.added"]},"seq":{"type":"integer","description":"1-based position in the run's append-only journal (ascending = chronological)."},"kind":{"$ref":"#/components/schemas/AgentRunStepAddedEventKind","description":"The step type. `output` is reserved for a future step kind - a run's final answer lives on the run object today, not in the journal."},"content":{"type":"string","description":"The step's payload, per kind: a `plan`'s rationale, an `observation`'s result, or a `delegation`'s sub-goal (redacted)."},"tool":{"type":"string","description":"The tool a `tool_call` / `observation` step is about.\n\nIt is not always one of your own tools. Platform tools appear under\ntheir reserved names - `search_knowledge`, `fetch_url`, `run_code`,\n`store_query`, `store_get`, `store_put`, `store_delete`,\n`store_read_asset` - an MCP tool appears as\n`__`, and a schema-repair observation\ncarries the reserved pseudo-tool `output_schema`. Match against\nyour own toolbelt expecting misses.\n"},"args":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The rendered arguments of a `tool_call`, with credential-shaped values redacted."},"delegation":{"$ref":"#/components/schemas/RunStepDelegation"},"duration_ms":{"type":"integer","description":"Wall-clock the brain spent producing this step."},"model":{"type":"string","description":"The model that served this step, as the provider reported it; absent when it reported none. The run's `usage.models` is the sum over its steps by this value."},"input_tokens":{"type":"integer","description":"Input tokens this step spent, cached reads included. The run's `usage` is the sum over its steps."},"cached_input_tokens":{"type":"integer","description":"The subset of `input_tokens` served from provider cache reads. Absent when the provider reported none."},"output_tokens":{"type":"integer","description":"Output tokens this step spent."},"total_tokens":{"type":"integer","description":"This step's input plus output tokens."},"created_at":{"type":"string","format":"date-time","description":"When the step was committed to the journal."}},"required":["type","seq","kind","created_at"],"description":"A journal step was committed. Everything an AgentRunStep carries is\ninlined here, so this is the same shape List Run Steps returns with the\ndiscriminator added. The SSE `id:` is the step's `seq`, which is what a\nreconnect resumes from through `Last-Event-ID`."},{"type":"object","properties":{"type":{"type":"string","enum":["run.status.changed"]},"status":{"$ref":"#/components/schemas/AgentRunStatusChangedEventStatus"}},"required":["type","status"],"description":"The run moved to a new status. Not terminal: a run reporting\n`requires_action` is waiting on a human and the stream keeps tailing,\nwhich is precisely when a client most needs to be told."},{"type":"object","properties":{"type":{"type":"string","enum":["run.ended"]},"status":{"$ref":"#/components/schemas/AgentRunEndedEventStatus"},"incomplete_reason":{"type":"string","description":"Why the run stopped short. Absent when it finished its work."},"output":{"$ref":"#/components/schemas/AgentRunEndedEventOutput","description":"The run's answer, as Get Agent Run returns it but without\n`transcript` - the stream has already delivered every step\nindividually, so repeating them here would send the run twice.\nAbsent on a run that produced no answer (failed, cancelled, or\nstopped short)."}},"required":["type","status"],"description":"The run settled, and this event carries its answer. It is the last event\non the stream - there is no `[DONE]` sentinel - and it is everything a\nclient needs, so following a run to completion takes no follow-up\nrequest."}],"discriminator":{"propertyName":"type"},"description":"One event on the run event stream. The `type` field discriminates the\nvariants and mirrors the SSE `event:` name, so an event is identifiable\nfrom its `data:` payload alone - which matters most for\n`run.status.changed` and `run.ended`, whose bodies are otherwise\nidentical. Ignore event types you do not recognize.","title":"AgentRunStreamEvent"},"KnowledgeBaseInjection":{"type":"object","properties":{"auto_tokens":{"type":"integer"},"pin_budget_tokens":{"type":"integer"},"pinned_tokens":{"type":"integer"},"fits_prompt":{"type":"boolean","description":"This knowledge base on its own counts at most `auto_tokens`\ntokens, so an agent whose attached knowledge stays under the\nbudget carries it in the prompt whole and logs no searches\nfor it. Always false while the automatic tier is off.\n"},"corpus_tokens":{"type":"integer","description":"The exact token count when `fits_prompt` is true; absent otherwise."}},"required":["auto_tokens","pin_budget_tokens","pinned_tokens","fits_prompt"],"description":"The prompt budget in force for this knowledge base, present on the\nby-id read. `auto_tokens` is the automatic tier's budget (0 when the\ntier is off): a knowledge base whose documents count at most that\nmany tokens is placed in the prompt whole. `pin_budget_tokens`\nbounds the documents pinned with `injection_mode: always`, and\n`pinned_tokens` is what they count today.\n","title":"KnowledgeBaseInjection"},"KnowledgeBase":{"type":"object","properties":{"id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`).\n"},"name":{"type":"string","description":"Human-readable label for the knowledge base."},"description":{"type":"string","description":"Optional description."},"document_count":{"type":"integer","description":"Number of ingested documents."},"injection":{"$ref":"#/components/schemas/KnowledgeBaseInjection","description":"The prompt budget in force for this knowledge base, present on the\nby-id read. `auto_tokens` is the automatic tier's budget (0 when the\ntier is off): a knowledge base whose documents count at most that\nmany tokens is placed in the prompt whole. `pin_budget_tokens`\nbounds the documents pinned with `injection_mode: always`, and\n`pinned_tokens` is what they count today.\n"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","document_count","created_at","updated_at"],"description":"A bundle of documents that can be attached to one or more voice\nagents. Chunks across every document in the knowledge base are\nembedded and searched together.","title":"KnowledgeBase"},"ListKnowledgeBasesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"knowledge_bases":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBase"}}},"required":["next_cursor","has_more","knowledge_bases"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListKnowledgeBasesResponse"},"CreateKnowledgeBaseRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"name":{"type":"string","description":"Human-readable label."},"description":{"type":"string","description":"Optional description."}},"required":["name"],"title":"CreateKnowledgeBaseRequest"},"SearchKnowledgeBasesRequest":{"type":"object","properties":{"query":{"type":"string","description":"Natural-language search query."},"kb_ids":{"type":"array","items":{"type":"string"},"description":"Knowledge bases to search across. Results scoped to caller-owned entries; unknown IDs are silently ignored."},"limit":{"type":"integer","default":5,"description":"Max hits to return (default 5, capped at 50)."}},"required":["query","kb_ids"],"title":"SearchKnowledgeBasesRequest"},"KnowledgeBaseSearchHit":{"type":"object","properties":{"chunk_id":{"type":"string","pattern":"^chunk_[0-9a-hjkmnp-tv-z]{26}$"},"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"filename":{"type":"string"},"chunk_index":{"type":"integer"},"content":{"type":"string"},"score":{"type":"string","title":"float","description":"Cosine similarity (higher = more relevant)."},"heading_path":{"type":"array","items":{"type":"string"},"description":"The headings the passage sits under, outermost first. Empty for a\ndocument without headings, or one indexed before headings were kept.\n"},"source_url":{"type":"string","description":"The page the document was imported from, when it came from a URL."},"ranking":{"$ref":"#/components/schemas/SearchHitRanking"}},"required":["chunk_id","document_id","kb_id","filename","chunk_index","content","score","heading_path"],"title":"KnowledgeBaseSearchHit"},"SearchKnowledgeBasesResponse":{"type":"object","properties":{"hits":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseSearchHit"}},"coverage":{"$ref":"#/components/schemas/RetrievalCoverage"},"retrieval_version":{"type":"string","description":"The retrieval contract that produced this result, so results can\nbe compared like with like across a rollout.\n"}},"required":["hits","coverage","retrieval_version"],"title":"SearchKnowledgeBasesResponse"},"UpdateKnowledgeBaseRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"Move the resource between workspace projects: a `proj_...` id\nmoves it there, an explicit null moves it back to the implicit\nDefault project, omitted leaves it unchanged.\n"},"name":{"type":"string"},"description":{"type":"string"}},"title":"UpdateKnowledgeBaseRequest"},"KnowledgeBaseCoverageHealth":{"type":"object","properties":{"searches":{"type":"integer","description":"Searches in the window whose scope included this knowledge base."},"conversations":{"type":"integer","description":"Distinct conversations those searches came from."},"sufficient":{"type":"integer"},"partial":{"type":"integer"},"insufficient":{"type":"integer"},"unscored":{"type":"integer","description":"Searches recorded before retrieval gave a verdict."},"median_latency_ms":{"type":["integer","null"],"description":"Median search time in milliseconds; null until a search in the window recorded it."},"documents":{"type":"integer","description":"Ready documents with at least one passage."},"documents_retrieved":{"type":"integer","description":"Those among them a search in the window surfaced."}},"required":["searches","conversations","sufficient","partial","insufficient","unscored","median_latency_ms","documents","documents_retrieved"],"description":"The strip above the report.","title":"KnowledgeBaseCoverageHealth"},"KnowledgeBaseCoverageGap":{"type":"object","properties":{"query":{"type":"string","description":"The phrasing callers used most often."},"variants":{"type":"array","items":{"type":"string"},"description":"Other phrasings in the cluster, newest first (at most 5)."},"searches":{"type":"integer"},"partial":{"type":"integer"},"insufficient":{"type":"integer"},"last_seen_at":{"type":"string","format":"date-time"},"conversation_ids":{"type":"array","items":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The most recent conversations that hit the gap (at most 5)."}},"required":["query","variants","searches","partial","insufficient","last_seen_at","conversation_ids"],"description":"One cluster of questions the knowledge base did not cover.","title":"KnowledgeBaseCoverageGap"},"KnowledgeBaseDocumentSourceKind":{"type":"string","enum":["file","url","text"],"description":"How the document entered the KB. `file` is the upload path,\n`text` is inline pasted content, `url` is fetched via\nFirecrawl. Sitemap and crawl imports also produce `url` rows.\n","title":"KnowledgeBaseDocumentSourceKind"},"KnowledgeBaseCoverageDocument":{"type":"object","properties":{"id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"filename":{"type":"string"},"source_kind":{"$ref":"#/components/schemas/KnowledgeBaseDocumentSourceKind"},"source_url":{"type":"string"},"chunk_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}},"required":["id","filename","source_kind","chunk_count","created_at"],"description":"A document as the coverage report lists it.","title":"KnowledgeBaseCoverageDocument"},"KnowledgeBaseCoverage":{"type":"object","properties":{"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"window_days":{"type":"integer"},"since":{"type":"string","format":"date-time","description":"Start of the window; searches at or after this instant count."},"health":{"$ref":"#/components/schemas/KnowledgeBaseCoverageHealth"},"gaps":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseCoverageGap"},"description":"The partial and insufficient searches, clustered by the content\nwords they share, most frequent first. At most 50 clusters, built\nfrom the newest 2,000 such searches.\n"},"unused_documents":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseCoverageDocument"},"description":"Ready documents no search in the window retrieved a passage\nfrom, oldest first. The oldest 100; `unused_document_count` is\nthe exact total.\n"},"unused_document_count":{"type":"integer"}},"required":["kb_id","window_days","since","health","gaps","unused_documents","unused_document_count"],"description":"What one knowledge base was asked over a trailing window, what it\ncould not answer, and what in it never gets used. Built from the\nsearches agents ran against it.\n","title":"KnowledgeBaseCoverage"},"KnowledgeBaseEvalCase":{"type":"object","properties":{"id":{"type":"string","pattern":"^kbcase_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"query":{"type":"string"},"expected_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The documents whose passages answer the question. Empty for an unanswerable case."},"unanswerable":{"type":"boolean","description":"Nothing in the knowledge base should answer this; retrieval is expected to say so."},"note":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","kb_id","query","expected_document_ids","unanswerable","note","created_at","updated_at"],"description":"A labelled question for one knowledge base: the documents that\nanswer it, or `unanswerable` when nothing should.\n","title":"KnowledgeBaseEvalCase"},"ListKnowledgeBaseEvalCasesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"cases":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseEvalCase"}}},"required":["next_cursor","has_more","cases"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/eval-cases`.","title":"ListKnowledgeBaseEvalCasesResponse"},"CreateKnowledgeBaseEvalCaseRequest":{"type":"object","properties":{"query":{"type":"string","maxLength":500},"expected_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Required unless `unanswerable`; every id must belong to this knowledge base."},"unanswerable":{"type":"boolean","default":false},"note":{"type":"string","maxLength":1000}},"required":["query"],"title":"CreateKnowledgeBaseEvalCaseRequest"},"UpdateKnowledgeBaseEvalCaseRequest":{"type":"object","properties":{"query":{"type":"string","maxLength":500},"expected_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"}},"unanswerable":{"type":"boolean"},"note":{"type":"string","maxLength":1000}},"description":"Omitted fields keep their value.","title":"UpdateKnowledgeBaseEvalCaseRequest"},"JobStatus":{"type":"string","enum":["pending","running","completed","failed","cancelled"],"description":"The one lifecycle vocabulary shared by every async job (batch\ncalls, knowledge-base imports, agent-test runs, suite runs):\n`pending` \u2192 `running` \u2192 a terminal state.\n\n- `pending` - accepted but not yet executing (queued for a worker,\n or deferred to a future scheduled time).\n- `running` - actively executing.\n- `completed` - ran to conclusion. The single terminal-success\n verb. For a job that produces a pass/fail judgment (an agent-test\n run), this means it produced a verdict - read the separate\n `verdict` field for the judgment, not this status.\n- `failed` - could not complete (an infrastructure or input\n failure), distinct from a `completed` job whose `verdict` is\n `failed`.\n- `cancelled` - cancelled before reaching a natural terminal state.\n","title":"JobStatus"},"KnowledgeBaseEvaluationResults":{"type":"object","properties":{"passed":{"type":"integer"},"failed":{"type":"integer"}},"required":["passed","failed"],"description":"The verdict breakdown over the cases evaluated so far, apart from the processing counts.","title":"KnowledgeBaseEvaluationResults"},"KnowledgeBaseEvaluationMetrics":{"type":"object","properties":{"cases":{"type":"integer"},"answerable":{"type":"integer"},"unanswerable":{"type":"integer"},"recall_at_k":{"type":"number","format":"double"},"hit_rate_at_k":{"type":"number","format":"double"},"mrr":{"type":"number","format":"double"},"answerable_gated":{"type":"number","format":"double"},"unanswerable_gated":{"type":"number","format":"double"},"unanswerable_zero_hits":{"type":"number","format":"double"},"duplicate_hits":{"type":"integer"}},"required":["cases","answerable","unanswerable","recall_at_k","hit_rate_at_k","mrr","answerable_gated","unanswerable_gated","unanswerable_zero_hits","duplicate_hits"],"description":"The summary over the cases that were searched. Recall, hit rate and\nMRR cover the answerable cases; the gated shares say how often\nretrieval refused an answerable question (a false refusal) and how\noften it refused an unanswerable one (abstention recall).\n","title":"KnowledgeBaseEvaluationMetrics"},"KnowledgeBaseEvaluation":{"type":"object","properties":{"id":{"type":"string","pattern":"^kbeval_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"status":{"$ref":"#/components/schemas/JobStatus"},"retrieval_version":{"type":"string","description":"The retrieval pipeline the run was started under, so two runs can be compared like with like."},"top_k":{"type":"integer"},"total":{"type":"integer","description":"Cases to evaluate (the progress denominator)."},"completed":{"type":"integer","description":"Cases evaluated."},"failed":{"type":"integer","description":"Cases whose search failed."},"results":{"$ref":"#/components/schemas/KnowledgeBaseEvaluationResults"},"metrics":{"oneOf":[{"$ref":"#/components/schemas/KnowledgeBaseEvaluationMetrics"},{"type":"null"}],"description":"The summary, null until the evaluation completes."},"error":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}},"required":["id","kb_id","status","retrieval_version","top_k","total","completed","failed","results","metrics","created_at","updated_at","started_at","ended_at"],"description":"One run of every case against the live retrieval pipeline. Poll it\nwhile `status` is `pending` or `running`; `metrics` fills in when it\ncompletes.\n","title":"KnowledgeBaseEvaluation"},"ListKnowledgeBaseEvaluationsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseEvaluation"}}},"required":["next_cursor","has_more","runs"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/evaluations`.","title":"ListKnowledgeBaseEvaluationsResponse"},"StartKnowledgeBaseEvaluationRequest":{"type":"object","properties":{"top_k":{"type":"integer","minimum":1,"maximum":20,"default":5,"description":"How many passages each search returns; the metrics are computed at this depth."}},"title":"StartKnowledgeBaseEvaluationRequest"},"KnowledgeBaseEvaluationHit":{"type":"object","properties":{"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"filename":{"type":"string"},"chunk_index":{"type":"integer"},"score":{"type":"number","format":"double"},"heading_path":{"type":"array","items":{"type":"string"}},"excerpt":{"type":"string","description":"The first 300 characters of the passage."},"content_hash":{"type":"string","description":"A fingerprint of the whole passage, so two hits can be told apart past the excerpt."},"ranking":{"$ref":"#/components/schemas/SearchHitRanking","description":"The component scores behind the position; absent when only the cosine ranked the hit."}},"required":["document_id","filename","chunk_index","score","heading_path","excerpt","content_hash"],"title":"KnowledgeBaseEvaluationHit"},"KnowledgeBaseEvaluationResult":{"type":"object","properties":{"case_id":{"type":"string","pattern":"^kbcase_[0-9a-hjkmnp-tv-z]{26}$"},"query":{"type":"string"},"expected_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"}},"unanswerable":{"type":"boolean"},"passed":{"type":"boolean"},"found_document_ids":{"type":"array","items":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The expected documents that appeared in the result set."},"first_rank":{"type":"integer","description":"1-based rank of the first expected document, 0 when none appeared."},"top_score":{"type":["number","null"],"format":"double","description":"The best hit's score, null when nothing was retrieved."},"coverage":{"$ref":"#/components/schemas/RetrievalCoverage","description":"The verdict retrieval gave; absent when the search failed."},"latency_ms":{"type":["integer","null"]},"error":{"type":"string"},"hits":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseEvaluationHit"}},"created_at":{"type":"string","format":"date-time"}},"required":["case_id","query","expected_document_ids","unanswerable","passed","found_document_ids","first_rank","top_score","latency_ms","hits","created_at"],"description":"One case's outcome inside one evaluation. An answerable case passes\nwhen an expected document is in the result set and retrieval did not\nrefuse; an unanswerable one passes when retrieval refused or found\nnothing.\n","title":"KnowledgeBaseEvaluationResult"},"ListKnowledgeBaseEvaluationResultsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"results":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseEvaluationResult"}}},"required":["next_cursor","has_more","results"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/evaluations/{evaluation_id}/results`.","title":"ListKnowledgeBaseEvaluationResultsResponse"},"KnowledgeBaseDocumentInjectionMode":{"type":"string","enum":["on_demand","always"],"description":"`always` places the document in every prompt of the agents its\nknowledge base is attached to; `on_demand` (the default) leaves it\non retrieval. A knowledge base small enough to fit the automatic\nbudget is placed in the prompt whole regardless of this setting.\n","title":"KnowledgeBaseDocumentInjectionMode"},"KnowledgeBaseDocumentStatus":{"type":"string","enum":["fetching","embedding","ready","failed"],"description":"Document lifecycle. `fetching` is the pre-scrape state used\nonly by url-sourced rows; file and text docs skip straight\nto `embedding` because their content is available\nsynchronously. Terminal states are `ready` and `failed`.\n","title":"KnowledgeBaseDocumentStatus"},"KnowledgeBaseDocument":{"type":"object","properties":{"id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`) of\nthe knowledge base the document belongs to.\n"},"source_kind":{"$ref":"#/components/schemas/KnowledgeBaseDocumentSourceKind"},"source_url":{"type":"string","description":"Source URL for url-sourced documents (and the sitemap /\ncrawl imports that produce them). Empty string for file\nand text rows.\n"},"folder_id":{"type":["string","null"],"pattern":"^kfolder_[0-9a-hjkmnp-tv-z]{26}$","description":"Folder this document lives in. Null for root-level\n(unfiled) documents. Mutated via the move endpoint.\n"},"filename":{"type":"string"},"content_type":{"type":"string"},"byte_size":{"type":"integer","format":"int64"},"char_count":{"type":"integer"},"chunk_count":{"type":"integer"},"injection_mode":{"$ref":"#/components/schemas/KnowledgeBaseDocumentInjectionMode","description":"`always` places the document in every prompt of the agents its\nknowledge base is attached to; `on_demand` (the default) leaves it\non retrieval. A knowledge base small enough to fit the automatic\nbudget is placed in the prompt whole regardless of this setting.\n"},"status":{"$ref":"#/components/schemas/KnowledgeBaseDocumentStatus"},"error":{"type":"string","description":"Populated when status is failed."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","kb_id","source_kind","folder_id","filename","content_type","byte_size","char_count","chunk_count","injection_mode","status","created_at","updated_at"],"title":"KnowledgeBaseDocument"},"ListKnowledgeBaseDocumentsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"documents":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}},"required":["next_cursor","has_more","documents"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListKnowledgeBaseDocumentsResponse"},"KnowledgeBaseDocumentDetailInjectionMode":{"type":"string","enum":["on_demand","always"],"description":"`always` places the document in every prompt of the agents its\nknowledge base is attached to; `on_demand` (the default) leaves it\non retrieval. A knowledge base small enough to fit the automatic\nbudget is placed in the prompt whole regardless of this setting.\n","title":"KnowledgeBaseDocumentDetailInjectionMode"},"DependentAgent":{"type":"object","properties":{"id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"}},"required":["id","name"],"description":"Minimal agent pointer (id + name) used by the document\ndetail view to render a clickable link to each agent that\nhas the document's KB attached.\n","title":"DependentAgent"},"RefreshConfig":{"type":"object","properties":{"enabled":{"type":"boolean"},"interval_days":{"type":"integer"},"auto_remove_enabled":{"type":"boolean","description":"Delete the document once the source itself has answered \"page gone\"\n(HTTP 404 or 410) on 5 consecutive refreshes. A transient failure\n(outage, timeout, block) never removes a document: the last\nsuccessfully fetched version keeps serving and the failure streak\nis reported in `consecutive_fetch_failures`.\n"},"last_refreshed_at":{"type":["string","null"],"format":"date-time"},"consecutive_fetch_failures":{"type":"integer","description":"Refreshes in a row that failed to fetch the source. Reset to zero\nby the next successful fetch. While non-zero, the document still\nserves its last successfully fetched version.\n"}},"required":["enabled","interval_days","auto_remove_enabled","last_refreshed_at","consecutive_fetch_failures"],"description":"Per-document auto-refresh state. Only populated\nfor url-sourced documents; file and text rows omit this and\nclients hide auto-refresh affordances accordingly.\n","title":"RefreshConfig"},"KnowledgeBaseDocumentDetail":{"type":"object","properties":{"id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`) of\nthe knowledge base the document belongs to.\n"},"source_kind":{"$ref":"#/components/schemas/KnowledgeBaseDocumentSourceKind"},"source_url":{"type":"string","description":"Source URL for url-sourced documents (and the sitemap /\ncrawl imports that produce them). Empty string for file\nand text rows.\n"},"folder_id":{"type":["string","null"],"pattern":"^kfolder_[0-9a-hjkmnp-tv-z]{26}$","description":"Folder this document lives in. Null for root-level\n(unfiled) documents. Mutated via the move endpoint.\n"},"filename":{"type":"string"},"content_type":{"type":"string"},"byte_size":{"type":"integer","format":"int64"},"char_count":{"type":"integer"},"chunk_count":{"type":"integer"},"injection_mode":{"$ref":"#/components/schemas/KnowledgeBaseDocumentDetailInjectionMode","description":"`always` places the document in every prompt of the agents its\nknowledge base is attached to; `on_demand` (the default) leaves it\non retrieval. A knowledge base small enough to fit the automatic\nbudget is placed in the prompt whole regardless of this setting.\n"},"status":{"$ref":"#/components/schemas/KnowledgeBaseDocumentStatus"},"error":{"type":"string","description":"Populated when status is failed."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"content_preview":{"type":"string"},"token_count":{"type":"integer","description":"The document's size in prompt tokens, the number the prompt budget counts."},"preview_truncated":{"type":"boolean"},"dependent_agents":{"type":"array","items":{"$ref":"#/components/schemas/DependentAgent"}},"refresh":{"$ref":"#/components/schemas/RefreshConfig"}},"required":["id","kb_id","source_kind","folder_id","filename","content_type","byte_size","char_count","chunk_count","injection_mode","status","created_at","updated_at","content_preview","token_count","preview_truncated","dependent_agents"],"description":"Payload of GET /v1/agents/knowledge-bases/documents/{document_id}. Extends\nthe list-view document with a bounded content preview, the\nlist of dependent agents, and (for url-sourced docs) the\nauto-refresh state.\n","title":"KnowledgeBaseDocumentDetail"},"V1AgentsKnowledgeBasesKbIdDocumentsDocumentIdPatchRequestBodyContentApplicationJsonSchemaInjectionMode":{"type":"string","enum":["on_demand","always"],"description":"`always` keeps the document in every prompt; `on_demand`\nreturns it to retrieval.\n","title":"V1AgentsKnowledgeBasesKbIdDocumentsDocumentIdPatchRequestBodyContentApplicationJsonSchemaInjectionMode"},"KnowledgeBaseChunk":{"type":"object","properties":{"id":{"type":"string","pattern":"^chunk_[0-9a-hjkmnp-tv-z]{26}$"},"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`) of\nthe knowledge base the chunk belongs to.\n"},"chunk_index":{"type":"integer"},"content":{"type":"string"},"context":{"type":"string","description":"A sentence or two that situates the passage within its document,\nwritten at ingest when contextual chunks are on. Absent otherwise.\n"},"heading_path":{"type":"array","items":{"type":"string"},"description":"The headings the chunk sits under, outermost first; empty when the document has none."}},"required":["id","document_id","kb_id","chunk_index","content","heading_path"],"title":"KnowledgeBaseChunk"},"ListKnowledgeBaseChunksResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"chunks":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseChunk"}}},"required":["next_cursor","has_more","chunks"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListKnowledgeBaseChunksResponse"},"CreateCrawlImportRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"max_pages":{"type":"integer"},"max_depth":{"type":"integer"},"folder_id":{"type":["string","null"],"description":"Folder to import the documents into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["url"],"title":"CreateCrawlImportRequest"},"ImportJobKind":{"type":"string","enum":["sitemap","crawl","refresh","urls"],"title":"ImportJobKind"},"ImportJob":{"type":"object","properties":{"id":{"type":"string","pattern":"^import_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"kind":{"$ref":"#/components/schemas/ImportJobKind"},"status":{"$ref":"#/components/schemas/JobStatus"},"total":{"type":"integer","description":"Total URLs to process (the progress denominator)."},"completed":{"type":"integer","description":"URLs imported successfully."},"failed":{"type":"integer","description":"URLs that failed to import."},"params":{"type":"object","additionalProperties":{"description":"Any type"},"description":"JSON blob whose shape depends on `kind` \u2014 typically `url`,\n`max_pages`, `max_depth`. Read it for display\nonly.\n"},"error":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}},"required":["id","kb_id","kind","status","total","completed","failed","params","created_at","updated_at","started_at","ended_at"],"description":"Async URL import job (sitemap, crawl, plus\nthe auto-refresh path). Poll\n`GET /v1/agents/knowledge-bases/{kb_id}/imports` while the job is\nnon-terminal.\n","title":"ImportJob"},"CreateSitemapImportRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"folder_id":{"type":["string","null"],"description":"Folder to import the documents into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["url"],"title":"CreateSitemapImportRequest"},"CreateTextDocumentRequest":{"type":"object","properties":{"name":{"type":"string"},"content":{"type":"string"},"folder_id":{"type":["string","null"],"description":"Folder to drop the document into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["name","content"],"description":"Body for POST /v1/agents/knowledge-bases/{kb_id}/documents/text.","title":"CreateTextDocumentRequest"},"CreateURLDocumentRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"folder_id":{"type":["string","null"],"description":"Folder to drop the document into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["url"],"description":"Body for POST /v1/agents/knowledge-bases/{kb_id}/documents/url.","title":"CreateURLDocumentRequest"},"CreateURLBatchImportRequest":{"type":"object","properties":{"urls":{"type":"array","items":{"type":"string","format":"uri"}},"folder_id":{"type":["string","null"],"description":"Folder to import the documents into. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted = root.\n"}},"required":["urls"],"description":"Body for POST /v1/agents/knowledge-bases/{kb_id}/documents/urls. Submit\n1..N URLs in a single async import. The server dedupes and\nvalidates each entry before queueing. The per-import cap is\noperator-tunable (default 250) via kbUrlBatchMaxUrls; the\nserver returns 400 when the resolved list exceeds the cap, so\nno maxItems is encoded in the schema to avoid SDK-side false\nrejections when an operator raises the limit.\n","title":"CreateURLBatchImportRequest"},"BatchDeleteDocumentsRequest":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}}},"required":["ids"],"description":"Body for DELETE /v1/agents/knowledge-bases/{kb_id}/documents/batch. All\nids must belong to the supplied KB; capped at 200 ids per\ncall.\n","title":"BatchDeleteDocumentsRequest"},"BatchMoveDocumentsRequest":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"}},"folder_id":{"type":["string","null"],"description":"Destination folder. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null moves every\ndocument to the knowledge base root.\n"}},"required":["ids","folder_id"],"description":"Body for PATCH /v1/agents/knowledge-bases/{kb_id}/documents/batch.\nCapped at 200 ids per call. Pass `folder_id: null` to move to\nroot.\n","title":"BatchMoveDocumentsRequest"},"BatchMoveDocumentsResponse":{"type":"object","properties":{"documents":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseDocumentDetail"}}},"required":["documents"],"description":"The documents moved by a batch-move request, each in the same detail\nshape the single-document move\n(`PATCH /v1/agents/knowledge-bases/{kb_id}/documents/{document_id}`)\nreturns.\n","title":"BatchMoveDocumentsResponse"},"UpdateRefreshConfigRequest":{"type":"object","properties":{"enabled":{"type":"boolean"},"interval_days":{"type":"integer"},"auto_remove_enabled":{"type":"boolean","description":"Delete the document once the source itself has answered \"page gone\"\n(HTTP 404 or 410) on 5 consecutive refreshes. Transient fetch\nfailures never remove a document.\n"}},"description":"PATCH body \u2014 every field optional.","title":"UpdateRefreshConfigRequest"},"RefreshHistoryEntryStatus":{"type":"string","enum":["running","changed","unchanged","failed","removed"],"title":"RefreshHistoryEntryStatus"},"RefreshHistoryEntry":{"type":"object","properties":{"id":{"type":"string","pattern":"^refresh_[0-9a-hjkmnp-tv-z]{26}$"},"document_id":{"type":"string","pattern":"^doc_[0-9a-hjkmnp-tv-z]{26}$"},"started_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"},"status":{"$ref":"#/components/schemas/RefreshHistoryEntryStatus"},"error":{"type":"string"},"previous_hash":{"type":"string"},"new_hash":{"type":"string"}},"required":["id","document_id","started_at","ended_at","status"],"description":"One auto-refresh attempt. `running` only appears mid-tick;\nterminal values are the ones the drawer renders.\n","title":"RefreshHistoryEntry"},"ListRefreshHistoryResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"entries":{"type":"array","items":{"$ref":"#/components/schemas/RefreshHistoryEntry"}}},"required":["next_cursor","has_more","entries"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/documents/{document_id}/refresh-history`.","title":"ListRefreshHistoryResponse"},"KnowledgeBaseFolder":{"type":"object","properties":{"id":{"type":"string","pattern":"^kfolder_[0-9a-hjkmnp-tv-z]{26}$"},"kb_id":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`kb_<26 char Crockford base32>`) of\nthe owning knowledge base.\n"},"parent_folder_id":{"type":["string","null"],"pattern":"^kfolder_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"document_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","kb_id","parent_folder_id","name","document_count","created_at","updated_at"],"description":"Folder inside a knowledge base. Root-level folders have\n`parent_folder_id: null`. `document_count` is populated only\non the list endpoint.\n","title":"KnowledgeBaseFolder"},"ListKnowledgeBaseFoldersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"folders":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseFolder"}}},"required":["next_cursor","has_more","folders"],"description":"Flat list of folders for a knowledge base. Build the folder\ntree from `parent_folder_id` references, so callers should\nwalk every page before rendering.\n","title":"ListKnowledgeBaseFoldersResponse"},"CreateFolderRequest":{"type":"object","properties":{"name":{"type":"string"},"parent_folder_id":{"type":["string","null"],"description":"Parent folder. Prefixed wire identifier\n(`kfolder_<26 char Crockford base32>`); null/omitted creates a\nroot-level folder.\n"}},"required":["name"],"title":"CreateFolderRequest"},"UpdateFolderRequest":{"type":"object","properties":{"name":{"type":"string"},"parent_folder_id":{"type":["string","null"],"description":"Folder to reparent under (prefixed wire identifier\n`kfolder_<26 char Crockford base32>`), or `null` to move the\nfolder to the knowledge base root. Omit to leave unchanged.\n"}},"description":"PATCH body (JSON merge-patch). All fields optional; omit a field\nto leave it unchanged. Set `parent_folder_id` to reparent into\nthat folder, or send `parent_folder_id: null` to move the folder\nto the knowledge base root.\n","title":"UpdateFolderRequest"},"ListImportJobsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"jobs":{"type":"array","items":{"$ref":"#/components/schemas/ImportJob"}}},"required":["next_cursor","has_more","jobs"],"description":"Payload for `GET /v1/agents/knowledge-bases/{kb_id}/imports`.","title":"ListImportJobsResponse"},"AttachedKnowledgeBasesResponseInjection":{"type":"object","properties":{"whole":{"type":"boolean"},"corpus_tokens":{"type":"integer"},"auto_tokens":{"type":"integer"},"pinned_documents":{"type":"integer"}},"required":["whole","auto_tokens","pinned_documents"],"description":"How the agent's attached knowledge reaches the model, on the\nfirst page only. `whole` means the entire attached corpus rides\nin the prompt and the agent never searches (`corpus_tokens` is\nits size); otherwise retrieval is on and `pinned_documents`\ncounts the documents pinned into every prompt beside it.\n`auto_tokens` is the automatic tier's budget, 0 when off.\n","title":"AttachedKnowledgeBasesResponseInjection"},"AttachedKnowledgeBasesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"knowledge_bases":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBase"}},"injection":{"$ref":"#/components/schemas/AttachedKnowledgeBasesResponseInjection","description":"How the agent's attached knowledge reaches the model, on the\nfirst page only. `whole` means the entire attached corpus rides\nin the prompt and the agent never searches (`corpus_tokens` is\nits size); otherwise retrieval is on and `pinned_documents`\ncounts the documents pinned into every prompt beside it.\n`auto_tokens` is the automatic tier's budget, 0 when off.\n"}},"required":["next_cursor","has_more","knowledge_bases"],"description":"Cursor-paginated list of the knowledge bases attached to an agent.\n","title":"AttachedKnowledgeBasesResponse"},"ToolKind":{"type":"string","enum":["builtin","webhook","client","mcp"],"description":"What kind of tool this is, and where it executes.\n- `builtin`: a worker-resident platform capability (e.g. end_call, play_audio), configured per-agent\n- `webhook`: worker signs a payload and POSTs it to your URL\n- `client`: worker dispatches to the caller's browser/SDK via data channel\n- `mcp`: worker connects to a customer-hosted MCP server and proxies tool calls\n","title":"ToolKind"},"SystemBuiltin":{"type":"string","description":"Identifier of a built-in system tool. New builtins are added by\nSpeechify across releases. Read the catalogue from\n`GET /v1/agents/tool-capabilities` rather than depending on this\nstring set staying stable across releases.\n","title":"SystemBuiltin"},"ToolParamType":{"type":"string","enum":["string","number","integer","boolean"],"description":"Permitted JSON-Schema primitive types for tool params.","title":"ToolParamType"},"ToolParam":{"type":"object","properties":{"name":{"type":"string"},"type":{"$ref":"#/components/schemas/ToolParamType"},"description":{"type":"string"},"required":{"type":"boolean"},"enum":{"type":"array","items":{"type":"string"}}},"required":["name","type","description","required"],"description":"One argument the LLM can pass when calling the tool. Mirrors the JSON-Schema subset standard function-calling schemas support.","title":"ToolParam"},"BuiltinToolConfig":{"type":"object","properties":{"builtin":{"$ref":"#/components/schemas/SystemBuiltin"},"params":{"type":"array","items":{"$ref":"#/components/schemas/ToolParam"}},"builtin_config":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-builtin extras, keyed by the `builtin` above.\n\n`transfer_to_number` takes `mode` (`cold`, the default, hands the\ncaller's own line to the carrier via SIP REFER; `warm` calls the\ndestination first, reads them a summary of the conversation while\nthe caller holds, and bridges the two once they accept),\n`hold_message` (warm only; the line spoken before the caller goes\non hold, `{{variable}}` tokens resolved, platform default when\nempty), and `allowed_numbers` (destinations the LLM may pass, in\neither mode; omit to allow any).\n\nWarm mode takes two more, both optional, and with neither set the\nperson hears the brief the platform writes today.\n`briefing_instructions` steers how that brief is written - what to\nlead with, what to leave out, how long to be - and is applied\nUNDER the platform's own brief-writing rules rather than in place\nof them, so the call transcript stays quoted evidence no\ninstruction inside it can escape (max 2000 characters).\n`briefing_template` replaces the generated brief with a fixed\nline, for a handoff that must state the same facts every time:\n`{{variable}}` tokens resolve as they do in `hold_message`, and\n`{{system__summary}}` resolves to the generated brief, so a\ntemplate can pin the facts first and still hand over the rest of\nthe call. That token sits in the reserved `system__` namespace, so\nit can never shadow a variable of your own. A template that never\nnames it skips generating one (max\n600 characters, short enough that the line and the \"press 1 to\ntake the call\" after it fit one playout).\n\nWarm mode also takes `ringing_timeout_ms`, how long the\ndestination may ring before the transfer is given up on. Omit it\nto ring for 30 seconds. The accepted range is the one an outbound call\nalready publishes, 1000-80000 ms; a whole number of milliseconds\noutside it is rejected here, and on the flow node, rather than\nquietly shortened.\nTransferring into a queue usually needs longer than 30 seconds,\nbecause the queue's own rollover never gets the chance to fire.\n\n`play_audio` takes `audio_asset_id`.\n"}},"required":["builtin"],"description":"Config shape for `kind=builtin`. The `builtin` value names the\nworker-resident capability; the catalogue served by\n`GET /v1/agents/tool-capabilities` is the runtime source of truth\nfor valid names plus their human-readable labels. `builtin_config`\ncarries per-instance extras (e.g. mode + hold_message +\nallowed_numbers for transfer_to_number, audio_asset_id for\nplay_audio).\n","title":"BuiltinToolConfig"},"WebhookToolConfigMethod":{"type":"string","enum":["POST","GET"],"title":"WebhookToolConfigMethod"},"LongRunningToolConfigWaitAudioPreset":{"type":"string","enum":["office","city","forest","crowded_room","keyboard_typing","hold_music"],"description":"Which bundled clip to loop. One roster and one mix level\nshared with the agent-level background noise, so a preset\nsounds the same whichever surface picks it.\n","title":"LongRunningToolConfigWaitAudioPreset"},"LongRunningToolConfigWaitAudio":{"type":"object","properties":{"preset":{"$ref":"#/components/schemas/LongRunningToolConfigWaitAudioPreset","description":"Which bundled clip to loop. One roster and one mix level\nshared with the agent-level background noise, so a preset\nsounds the same whichever surface picks it.\n"}},"required":["preset"],"description":"A sound looped under the wait, instead of - or beneath - the\nspoken phrase, for the caller who expects hold music rather\nthan an agent repeating itself. It starts on the same dwell as\n`filler_message` (`filler_delay_ms` with nobody speaking) and\nstops the moment the tool settles, so the real answer never\nqueues behind it. Omit to leave the wait silent; the phrase and\nthe sound are independent, so set either, both, or neither.\n","title":"LongRunningToolConfigWaitAudio"},"LongRunningToolConfigOnDuplicate":{"type":"string","enum":["reject","allow"],"description":"What happens when the model calls this tool while a previous\ncall of the SAME tool is still running. `reject` (the default\nwhenever `long_running` is set) refuses the second call and\ntells the model the first is still in flight, so a slow\nside-effecting webhook cannot be fired twice in one turn.\n`allow` restores the unguarded behaviour for a read-only tool\nthe model legitimately fans out over several arguments \u2014\nduplicates are matched by tool name alone, never by\narguments.\n","title":"LongRunningToolConfigOnDuplicate"},"LongRunningToolConfig":{"type":"object","properties":{"filler_message":{"type":"string","minLength":1,"maxLength":200,"description":"Phrase spoken verbatim once the tool has been running for\n`filler_delay_ms`, e.g. \"one sec, pulling that up\". Write it\nin your agent's own language and voice \u2014 Speechify ships no\ndefault phrase, so nothing we invented can reach your caller\nin the wrong language. Omit to arm the duplicate policy\nwithout any spoken filler.\n\nOn a multilingual agent the phrase is spoken only while the\ncall is still in the agent's configured `language`; after a\nmid-call language switch the filler is suppressed rather than\nspoken in the language you wrote it in.\n"},"filler_delay_ms":{"type":"integer","minimum":500,"maximum":30000,"description":"How long the tool must have been running, with nobody\nspeaking, before the filler is spoken. Defaults to 2000. The\nwait restarts whenever the agent or the caller speaks, so the\nfiller never lands on top of live speech.\n"},"additional_filler_messages":{"type":"array","items":{"type":"string","minLength":1,"maxLength":200},"description":"Phrases for the second and later times the agent fills, in\norder, so a tool that outlives one phrase does not repeat it\nword for word - which is what makes an agent sound broken\nrather than busy. The last entry repeats once the list runs\nout. Requires `filler_message` and `filler_interval_ms`.\n"},"filler_interval_ms":{"type":"integer","minimum":2000,"maximum":30000,"description":"Cooldown before the filler may be spoken again while the tool\nis still running. Omit to speak it at most once, which covers\nthe common few-second tool; set it for a tool whose timeout\nruns to tens of seconds. Capped at 10 utterances per call\nregardless. Requires `filler_message`.\n"},"wait_audio":{"$ref":"#/components/schemas/LongRunningToolConfigWaitAudio","description":"A sound looped under the wait, instead of - or beneath - the\nspoken phrase, for the caller who expects hold music rather\nthan an agent repeating itself. It starts on the same dwell as\n`filler_message` (`filler_delay_ms` with nobody speaking) and\nstops the moment the tool settles, so the real answer never\nqueues behind it. Omit to leave the wait silent; the phrase and\nthe sound are independent, so set either, both, or neither.\n"},"on_duplicate":{"$ref":"#/components/schemas/LongRunningToolConfigOnDuplicate","description":"What happens when the model calls this tool while a previous\ncall of the SAME tool is still running. `reject` (the default\nwhenever `long_running` is set) refuses the second call and\ntells the model the first is still in flight, so a slow\nside-effecting webhook cannot be fired twice in one turn.\n`allow` restores the unguarded behaviour for a read-only tool\nthe model legitimately fans out over several arguments \u2014\nduplicates are matched by tool name alone, never by\narguments.\n"}},"description":"How the agent keeps a live call sane while a slow tool runs.\nPresent means the tool is declared slow; absent keeps the tool's\nprevious behaviour exactly.\n\nThe tool still blocks the turn either way: the agent never\nreceives a synthetic result while your endpoint is still working,\nso it can only ever speak about a result it actually got.\n","title":"LongRunningToolConfig"},"WebhookToolConfig":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"method":{"$ref":"#/components/schemas/WebhookToolConfigMethod"},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Static headers sent with every call. `Authorization` and `Speechify-Signature` are reserved."},"timeout_ms":{"type":"integer","description":"Per-call timeout in milliseconds. Defaults to 10000 server-side when omitted."},"params":{"type":"array","items":{"$ref":"#/components/schemas/ToolParam"}},"fire_and_forget":{"type":"boolean","description":"When true the worker dispatches the HTTP request and returns\nimmediately to the LLM with a synthetic \"queued\" result\ninstead of waiting for the response body. The customer's\nendpoint is expected to enqueue the work and return any\nnon-error status quickly; errors raised after dispatch are\nlogged but never surfaced to the LLM. Use for long-running\ncustomer-side work (job triggers, async ticket creation,\netc.) where blocking the call on the response would hurt\nthe conversation. Defaults to false.\n"},"long_running":{"$ref":"#/components/schemas/LongRunningToolConfig","description":"Declares the endpoint slow, so the agent speaks a filler\nphrase instead of leaving the caller in silence and refuses\na duplicate call while one is in flight.\n\nCannot be combined with `fire_and_forget`. That path returns\nto the agent before your endpoint answers, so there is no\nwait to fill; saving both is refused rather than storing a\nholding phrase that can never be spoken.\n"},"requires_approval":{"type":"boolean","description":"Legacy spelling of `approval: require_approval` on the tool\ndefinition. When true a durable run obtains human approval before\nthis tool runs. Prefer the definition-level `approval` field.\n"}},"required":["url"],"description":"Config shape for `kind=webhook`.\n\nOn a durable run (`POST /v1/agents/{agent_id}/runs`) your endpoint is\ntold `user_identity`: the person the agent is acting for, exactly as\nyour application supplied it when it started the run. The field is\nabsent when the run is acting for nobody in particular, so treat an\nabsent value as \"no person\", never as a default one. Voice\nconversations and sessions do not carry it yet.\n\nOn `method: POST` it rides inside the **signed** JSON body rather than\na header, so an endpoint deciding whose data to touch can verify the\nanswer with the same HMAC it already checks. On `method: GET` there is\nno body to sign, so it arrives as a **`user_identity` query\nparameter**, unverifiable exactly as the arguments beside it are: a\nGET's signature covers an envelope that is not on the wire. Use POST\nfor any endpoint that authorizes on who the call is for. A tool\nargument of the same name never overrides it.\n","title":"WebhookToolConfig"},"ClientToolConfig":{"type":"object","properties":{"params":{"type":"array","items":{"$ref":"#/components/schemas/ToolParam"}},"timeout_ms":{"type":"integer","description":"Per-call timeout in milliseconds. Defaults to 10000 server-side when omitted."}},"description":"Config shape for `kind=client`. Execution happens in the caller's browser / SDK.","title":"ClientToolConfig"},"MCPTransport":{"type":"string","enum":["http_streamable","sse"],"description":"MCP transport. `http_streamable` is the default; `sse` is the\nlegacy fallback for servers that haven't migrated yet.\n","title":"MCPTransport"},"MCPAuth":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["none"],"description":"Discriminator value: none"}},"required":["type"],"description":"none variant"},{"type":"object","properties":{"type":{"type":"string","enum":["bearer"],"description":"Discriminator value: bearer"},"credential_id":{"type":"string","description":"`cred_` id of a `bearer` credential in the workspace\nvault. Create the credential first via `POST /v1/credentials`,\nthen reference it here.\n"}},"required":["type","credential_id"],"description":"Bearer auth for an MCP server. References a workspace credential of\nkind `bearer` by id; the secret lives in the credentials vault and is\nresolved server-side at dispatch, never inlined on the tool.\n"},{"type":"object","properties":{"type":{"type":"string","enum":["oauth2_client_credentials"],"description":"Discriminator value: oauth2_client_credentials"},"credential_id":{"type":"string","description":"`cred_` id of an `oauth2_client_credentials` credential\nin the workspace vault.\n"}},"required":["type","credential_id"],"description":"OAuth2 client-credentials auth for an MCP server. References a\nworkspace credential of kind `oauth2_client_credentials` by id; the\ntoken_url / client_id / client_secret / scopes all live in the vault\ncredential and are resolved server-side at dispatch.\n"}],"discriminator":{"propertyName":"type"},"description":"Discriminated union over `type`.","title":"MCPAuth"},"ToolActionClass":{"type":"string","enum":["read","communicate_internal","communicate_external","data_export","financial","irreversible_other"],"description":"The impact class a tool declares: what kind of consequence a call has,\nindependent of which transport executes it. It decides the approval an\nautonomous run applies by default.\n\n- `read`: a lookup with no side effect.\n- `communicate_internal`: writes inside your own estate (a CRM note, a\n post to your own Slack, a row in a store).\n- `communicate_external`: reaches a third party (an email, an SMS).\n- `data_export`: moves a data set out.\n- `financial`: moves money or commits you to a charge.\n- `irreversible_other`: any other action that cannot be undone.\n","title":"ToolActionClass"},"MCPToolConfig":{"type":"object","properties":{"endpoint":{"type":"string","format":"uri"},"transport":{"$ref":"#/components/schemas/MCPTransport"},"auth":{"$ref":"#/components/schemas/MCPAuth"},"long_running":{"$ref":"#/components/schemas/LongRunningToolConfig","description":"Declares the SERVER slow. Applies to every remote tool the\n`list_tools` handshake discovers on it \u2014 MCP publishes no\nper-tool latency signal at config time, so this describes the\nserver rather than one of its verbs.\n"},"action_classes":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/ToolActionClass"},"description":"What each of the server's own tools does, keyed by the remote tool\nname. One MCP tool is a whole server of mixed verbs, so without\nthis the server carries a single impact and an autonomous run\neither treats its writes as safe or stops for a human on its\nlookups. Classify the lookups `read` and they run unattended.\n\nA tool you do not name here keeps the server-level class, which is\ndeliberately the cautious one. An explicit server-level `approval`\nstill wins over everything here, so gating a whole server stays a\nsingle switch you can trust.\n\nYour server can also classify its own tools: a `tools/list` entry\ncarrying `_meta: {\"speechify/action_class\": \"communicate_external\"}`\ndeclares that tool's impact. The declaration is honoured under the\nceiling set by the tool definition's `action_class`: a declared\nclass at least as strong as the definition's is used, a weaker one\nis held to the definition's, and an unclassified definition\n(`irreversible_other`) makes every declaration moot. So one server\nwith `action_class: read` runs its reads unattended and parks a run\non the tool it declared `communicate_external`, with no second\nserver and no entry here. What you declare here beats what the\nserver declares.\n"}},"required":["endpoint","auth"],"description":"Config shape for `kind=mcp`. The worker opens the\nconfigured transport at session start, runs `initialize` +\n`list_tools`, and registers each discovered remote tool as a\nlivekit-agents function_tool proxying through the long-lived\nClientSession.\n\nOn a durable run (`POST /v1/agents/{agent_id}/runs`) every request to\nyour server carries a `Speechify-User-Identity` header holding the\nperson the agent is acting for, exactly as your application supplied\nit when it started the run. This is what lets a server you wrote hold\nyour users' third-party credentials and act for the right one; the\nheader is absent when the run is acting for nobody. It is a header\nrather than a body field because MCP owns its own JSON-RPC envelope,\nso trust it exactly as far as you trust the connection your server\nalready authenticated. Voice conversations and sessions do not carry\nit yet.\n\nEvery request also carries `Speechify-Idempotency-Key`, stable for one\nstep of one run and identical to the `Idempotency-Key` a webhook tool\nreceives. A durable run is retried if the platform redelivers it, so a\ntool that sends mail or takes payment can be called more than once for\nthe same decision: key on this header and refuse to act twice. One\nconnector serving both transports dedups on one identifier. The key\nis `:`, and `GET /v1/agents/runs/{run_id}` resolves the\nrun, its agent and the person it acts for from the id alone.\n","title":"MCPToolConfig"},"AgentToolConfig":{"oneOf":[{"$ref":"#/components/schemas/BuiltinToolConfig"},{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"description":"Resolved config - `BuiltinToolConfig`, `WebhookToolConfig`, `ClientToolConfig`, or `MCPToolConfig` depending on `kind`.","title":"AgentToolConfig"},"ToolApprovalClass":{"type":"string","enum":["auto","require_approval","disabled"],"description":"What governs a tool on the autonomous path (durable runs), where no\nhuman is on the line: `auto` runs it, `require_approval` parks the run\nin `requires_action` until a human decides, `disabled` refuses it. The\nrealtime voice and text runtimes have no suspend primitive and ignore\nit; use the per-agent tool's `enabled` flag to switch a tool off there.\n","title":"ToolApprovalClass"},"AgentTool":{"type":"object","properties":{"id":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`tool_<26 char Crockford base32>`), stable across kinds."},"kind":{"$ref":"#/components/schemas/ToolKind"},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean","description":"When false the tool is configured but skipped at dispatch."},"definition_id":{"type":["string","null"],"description":"Provenance: the backing workspace tool definition id for\nshared kinds (webhook/client/mcp), or `null` for a built-in.\nNon-null \u27fa the tool's config is shared and edited at\n`/v1/agents/tool-definitions/{tool_definition_id}`.\n"},"config":{"$ref":"#/components/schemas/AgentToolConfig","description":"Resolved config - `BuiltinToolConfig`, `WebhookToolConfig`, `ClientToolConfig`, or `MCPToolConfig` depending on `kind`."},"webhook_secret":{"type":"string","description":"HMAC signing secret for a `webhook` tool. Returned in full\n**only** on the create response; subsequent reads return a\nmasked placeholder.\n"},"action_class":{"oneOf":[{"$ref":"#/components/schemas/ToolActionClass"},{"type":"null"}],"description":"The impact class you declared on the shared definition; the platform\nfixes it for a built-in.\n"},"effective_action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact the tool actually carries, derived from its shape when none was declared."},"approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"The explicit override on the shared definition. Absent for a built-in or when derived."},"effective_approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"What governs the tool on the autonomous path. Present for the\nshared kinds a durable run can execute; absent for a built-in,\nwhich only ever runs in-process on a live session.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","kind","name","description","enabled","definition_id","config","action_class","effective_action_class","created_at","updated_at"],"description":"One thing an agent can do, in the unified per-agent view. Kind\ndiscriminates a per-agent built-in instance (`builtin`) from an\nattached workspace tool definition (`webhook`/`client`/`mcp`).\nThe config is resolved + embedded so consumers never have to fetch\nthe backing definition. `id` is a `tool_<...>` for every kind.\n","title":"AgentTool"},"ListAgentToolsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"tools":{"type":"array","items":{"$ref":"#/components/schemas/AgentTool"}}},"required":["next_cursor","has_more","tools"],"description":"The agent's full, mixed-kind toolbelt, cursor-paginated.\n","title":"ListAgentToolsResponse"},"CreateAgentToolRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/BuiltinToolConfig"},{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"title":"CreateAgentToolRequestConfig"},"CreateAgentToolRequest":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/ToolKind"},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean","description":"Defaults to true on the server when omitted."},"config":{"$ref":"#/components/schemas/CreateAgentToolRequestConfig"},"action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact class for a shared kind (`webhook`/`client`/`mcp`),\nwhose definition is created here. Omitted, the server stamps the\nverb default. Ignored for a built-in, whose class the platform fixes.\n"},"approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"Overrides the approval derived from the class, for a shared kind."}},"required":["kind","name","config"],"description":"Add a tool to an agent. With `kind=builtin`, `config` carries a\n`BuiltinToolConfig` and a per-agent built-in instance is created.\nWith `kind=webhook`/`client`/`mcp`, `config` carries that kind's\nconfig and a workspace definition is created AND attached in one\ncall.\n","title":"CreateAgentToolRequest"},"UpdateAgentToolRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/BuiltinToolConfig"}],"description":"For a built-in, the full `BuiltinToolConfig`. Rejected for shared kinds.","title":"UpdateAgentToolRequestConfig"},"UpdateAgentToolRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"config":{"$ref":"#/components/schemas/UpdateAgentToolRequestConfig","description":"For a built-in, the full `BuiltinToolConfig`. Rejected for shared kinds."},"params":{"type":"array","items":{"$ref":"#/components/schemas/ToolParam"}},"enabled":{"type":"boolean"}},"description":"PATCH body. For a built-in, all fields apply. For an attached\nexternal tool only `enabled` is honoured - editing the shared\nconfig/name from here returns `tool_config_shared`.\n","title":"UpdateAgentToolRequest"},"ToolConfig":{"oneOf":[{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"description":"One of `WebhookToolConfig`, `ClientToolConfig`, or `MCPToolConfig` depending on `kind`.","title":"ToolConfig"},"Tool":{"type":"object","properties":{"id":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`tool_<26 char Crockford base32>`).\n"},"name":{"type":"string"},"description":{"type":"string"},"kind":{"$ref":"#/components/schemas/ToolKind"},"config":{"$ref":"#/components/schemas/ToolConfig","description":"One of `WebhookToolConfig`, `ClientToolConfig`, or `MCPToolConfig` depending on `kind`."},"webhook_secret":{"type":"string","description":"HMAC signing secret for `kind=webhook`. Returned in full **only** on the create\nresponse; all subsequent reads return a masked placeholder. Store it on first\ncreate \u2014 there is no way to retrieve it later.\n"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"action_class":{"oneOf":[{"$ref":"#/components/schemas/ToolActionClass"},{"type":"null"}],"description":"The impact class you declared. Null means you declared none and the\ntool's impact is read off its shape instead - see\n`effective_action_class`.\n"},"effective_action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact the tool actually carries: `action_class` when you set\none, otherwise the class its shape implies (a `GET` webhook reads, a\n`POST` webhook reaches outside your team, an MCP or client tool can\ndo anything its author wired). Derived on every read, so changing a\nwebhook's method changes this with it. Read-only.\n"},"approval":{"oneOf":[{"$ref":"#/components/schemas/ToolApprovalClass"},{"type":"null"}],"description":"Your explicit override of the approval derived from the class.\nNull means derived.\n"},"effective_approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"What governs the tool on the autonomous path: `approval` when set,\nelse a webhook config's legacy `requires_approval`, else the class\ndefault (`read` and `communicate_internal` run; every other class,\nand an unclassified tool, requires approval). Read-only.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","kind","config","action_class","effective_action_class","approval","effective_approval","created_at","updated_at"],"description":"A workspace tool definition: a reusable webhook / client / MCP\nintegration created once and attached to many agents. Built-ins\nare NOT definitions - they are per-agent (see `AgentTool`).\n\n**A tool row belongs to exactly one project and is reused only inside\nit.** An attach whose agent and tool sit in different projects is\nrefused with `409 cross_project_reference`, and there is no\nworkspace-shared tier for a tool the way there is for a vault\ncredential: a tool with no `project_id` lives in the implicit Default\nproject and attaches only to agents that also live there. So an\napplication running one project per business customer duplicates its\nconnector tool per customer, and that is deliberate rather than a\nlimitation to route around. A tool row carries the endpoint, the\nheaders and the credential reference the agent will call with, and\nthose are exactly what an application wants to be able to vary, revoke\nor rotate for ONE of its customers without touching the others. Its\n`project_id` is also what makes a tool follow its agents through a\nmove, a promote and a project teardown; a shared row would have to be\nleft behind by all three.\n\nWhat does NOT need duplicating is the person: the customer's end-user\ntravels per call as `user_identity` in the signed webhook body or the\n`Speechify-User-Identity` header, so one connector row per project\nserves every person under that customer.\n","title":"Tool"},"ListToolsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"tools":{"type":"array","items":{"$ref":"#/components/schemas/Tool"}}},"required":["next_cursor","has_more","tools"],"description":"Payload for `GET /v1/agents/tool-definitions` \u2014 the workspace-level\ntool-definition catalog. Cursor-paginated; the per-agent toolbelt\nendpoint uses a different (bare, mixed-kind) shape \u2014 see\nListAgentToolsResponse.\n","title":"ListToolsResponse"},"CreateToolRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"title":"CreateToolRequestConfig"},"CreateToolRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"name":{"type":"string"},"description":{"type":"string"},"kind":{"$ref":"#/components/schemas/ToolKind"},"config":{"$ref":"#/components/schemas/CreateToolRequestConfig"},"action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact class. Omit it and the tool's impact is read off its\nshape instead, and keeps tracking it: a `GET` webhook reads, a\n`POST` webhook reaches outside your team, an MCP or client tool can\ndo anything its author wired.\n\nOn an MCP tool it is also the ceiling for what the server may claim\nabout its own tools: a class a `tools/list` entry declares under\n`_meta[\"speechify/action_class\"]` is honoured when it is at least\nthis strong and held to this class when weaker (see\n`MCPToolConfig.action_classes`).\n"},"approval":{"$ref":"#/components/schemas/ToolApprovalClass","description":"Overrides the approval derived from the class. Omitted means derived."}},"required":["name","description","kind","config"],"description":"Create a workspace tool definition (webhook / client / mcp only).","title":"CreateToolRequest"},"UpdateToolRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/WebhookToolConfig"},{"$ref":"#/components/schemas/ClientToolConfig"},{"$ref":"#/components/schemas/MCPToolConfig"}],"title":"UpdateToolRequestConfig"},"UpdateToolRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"Move the resource between workspace projects: a `proj_...` id\nmoves it there, an explicit null moves it back to the implicit\nDefault project, omitted leaves it unchanged.\n"},"name":{"type":"string"},"description":{"type":"string"},"config":{"$ref":"#/components/schemas/UpdateToolRequestConfig"},"action_class":{"oneOf":[{"$ref":"#/components/schemas/ToolActionClass"},{"type":"null"}],"description":"A class sets it, an explicit null clears it to unclassified, omitted leaves it unchanged."},"approval":{"oneOf":[{"$ref":"#/components/schemas/ToolApprovalClass"},{"type":"null"}],"description":"A class sets the override, an explicit null returns to derived, omitted leaves it unchanged."}},"description":"All fields optional. `kind` is immutable \u2014 create a new definition to change it.","title":"UpdateToolRequest"},"ToolAttachedAgent":{"type":"object","properties":{"id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Opaque agent ID."},"name":{"type":"string","description":"Human-readable agent name."}},"required":["id","name"],"description":"Minimal agent identity returned alongside a tool so a client\ncan render \"this tool is attached to: X, Y\" before a\ndestructive action runs.\n","title":"ToolAttachedAgent"},"ListToolAttachedAgentsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"agents":{"type":"array","items":{"$ref":"#/components/schemas/ToolAttachedAgent"}}},"required":["next_cursor","has_more","agents"],"description":"Response shape for GET /v1/agents/tool-definitions/{tool_definition_id}/attached-agents.\nAgents are tenant-scoped and ordered by name ASC.\n","title":"ListToolAttachedAgentsResponse"},"TestMCPConnectionRequest":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/MCPToolConfig"}},"required":["config"],"description":"Body for `POST /v1/agents/tool-definitions/test-mcp-connection`. `config` is the\nsame MCPToolConfig shape `POST /v1/agents/tool-definitions` would persist; nothing\nis persisted by the probe itself. For a non-none auth, the server\nresolves `auth.credential_id` to the vault secret before probing \u2014\nthe credential is created up front via `POST /v1/credentials`, so it\nresolves independently of any tool.\n","title":"TestMCPConnectionRequest"},"MCPProbeTool":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"read_only":{"type":"boolean","description":"The server's own account of this tool, read from the MCP\n`annotations.readOnlyHint` it published. Offered as a starting\npoint when you classify the server's tools in `action_classes`;\nit is never a policy input on its own, because a server declaring\nitself harmless is not the same as you deciding it is.\n"}},"required":["name"],"description":"One discovered tool in a probe result.","title":"MCPProbeTool"},"McpProbeErrorDetailsStage":{"type":"string","enum":["validation","oauth2_token","mcp_connect","mcp_initialize","mcp_notify","mcp_list_tools"],"title":"McpProbeErrorDetailsStage"},"MCPProbeErrorDetails":{"type":"object","properties":{"stage":{"$ref":"#/components/schemas/McpProbeErrorDetailsStage"},"http_status":{"type":"integer"},"oauth2_error":{"type":"string"},"oauth2_error_description":{"type":"string"},"upstream_body":{"type":"string"},"field_hint":{"type":"string"}},"description":"Structured upstream signal for an MCP probe failure. All fields\nare optional; a client renders what's present. `stage` names\nthe phase the probe was in (`validation`, `oauth2_token`,\n`mcp_connect`, `mcp_initialize`, `mcp_notify`, `mcp_list_tools`).\n`oauth2_error` / `oauth2_error_description` mirror RFC 6749 \u00a75.2\nwhen the customer's auth server returned the standard error\nshape. `http_status` is the upstream status code for transport\nfailures. `upstream_body` is a truncated prefix (max ~1 KiB) of\nthe upstream response body when the failure isn't structured.\n`field_hint` names a form field (`endpoint`, `transport`,\n`token`, `token_url`, `client_id`, `client_secret`, `scope`)\na client should highlight so the customer knows what to fix.\n","title":"MCPProbeErrorDetails"},"MCPProbeResult":{"type":"object","properties":{"tools":{"type":["array","null"],"items":{"$ref":"#/components/schemas/MCPProbeTool"}},"error":{"type":"string"},"details":{"$ref":"#/components/schemas/MCPProbeErrorDetails"}},"required":["tools"],"description":"Result of an MCP probe. On success, `tools` is the discovered\ncatalogue and `error` is absent. On failure, `tools` is `null`\nand `error` carries a human-readable reason a client can render\ninline next to the form. `details` is optional structured\nsignal from the upstream (OAuth2 RFC 6749 fields, HTTP status,\ntruncated upstream body, form field hint) a client can use to\nexpand the inline banner and highlight the offending input.\nOlder clients ignore `details` and fall back to `error`. Both\nvalidation and network failures land in `error` rather than\nnon-2xx responses, so consumers must check `error` before\nreading `tools`.\n","title":"MCPProbeResult"},"TestWebhookConnectionRequest":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/WebhookToolConfig"},"tool_id":{"type":"string","description":"Optional `tool_` id of the existing tool to sign\nthe probe with. Raw UUIDs and other-resource prefixes are\nrejected.\n"}},"required":["config"],"description":"Body for `POST /v1/agents/tool-definitions/test-webhook-connection`.\n`config` is the same WebhookToolConfig shape `POST /v1/agents/tool-definitions`\nwould persist; nothing is persisted by the probe. `tool_id` is\nonly meaningful in the edit-form flow \u2014 when set, the server\nsigns the probe request with the tool's stored HMAC secret so\nthe test exercises the real signature path.\n","title":"TestWebhookConnectionRequest"},"WebhookProbeResult":{"type":"object","properties":{"ok":{"type":"boolean"},"status_code":{"type":"integer","description":"HTTP status the endpoint returned. Absent on a transport failure."},"latency_ms":{"type":"integer","format":"int64","description":"Wall-clock round-trip time in milliseconds."},"response_body":{"type":"string","description":"Truncated prefix (max ~2 KiB) of the endpoint's response body."},"signed":{"type":"boolean","description":"Whether the probe request carried an HMAC signature header."},"error":{"type":"string","description":"Human-readable transport-level failure reason. Absent when any response was received."}},"required":["ok","signed"],"description":"Result of a webhook probe. `ok` is true only when the endpoint\nreturned a 2xx. A non-2xx response still populates `status_code`\nand `response_body` with `ok=false` \u2014 the request reached the\nendpoint, the endpoint just declined it. `error` is set only for\ntransport-level failures (DNS, connect, TLS, timeout, blocked\naddress range) where no response was received; `status_code` is\nabsent in that case. `signed` reports whether the probe carried\na `Speechify-Signature` header \u2014 false on the create-form\nflow, which has no stored secret yet. Both success and failure\nuse the 200 envelope so a client can render them inline.\n","title":"WebhookProbeResult"},"SystemBuiltinInfo":{"type":"object","properties":{"name":{"$ref":"#/components/schemas/SystemBuiltin"},"label":{"type":"string","description":"Human-readable display label for the builtin."},"description":{"type":"string","description":"One-line summary of what the builtin does."},"action_class":{"$ref":"#/components/schemas/ToolActionClass","description":"The impact class the platform fixes for this builtin."}},"required":["name","label","description","action_class"],"description":"One entry in the system-builtin catalogue.","title":"SystemBuiltinInfo"},"ListSystemBuiltinsResponse":{"type":"object","properties":{"builtins":{"type":"array","items":{"$ref":"#/components/schemas/SystemBuiltinInfo"}}},"required":["builtins"],"title":"ListSystemBuiltinsResponse"},"TestType":{"type":"string","enum":["reply","tool","simulation"],"description":"Discriminates the shape of `AgentTest.config`.\n- `reply` - send one message to the agent and judge the response with an LLM.\n- `tool` - assert that the agent calls a specific tool given a context.\n- `simulation` - run a multi-turn conversation between the agent and an AI caller.\n","title":"TestType"},"SimulationMessageRole":{"type":"string","enum":["user","assistant"],"title":"SimulationMessageRole"},"SimulationMessage":{"type":"object","properties":{"role":{"$ref":"#/components/schemas/SimulationMessageRole"},"content":{"type":"string"}},"required":["role","content"],"description":"One turn in a simulation conversation. `role` is `user` (the AI caller) or `assistant` (the agent).","title":"SimulationMessage"},"ReplyConfig":{"type":"object","properties":{"context":{"type":"string","description":"User message sent to the agent to trigger the behaviour under test. Optional when `initial_chat_history` already ends with a user message."},"success_criteria":{"type":"string","description":"Natural-language description of what a passing agent response looks like."},"success_examples":{"type":"array","items":{"type":"string"},"description":"Concrete examples of passing responses (few-shot for the judge)."},"failure_examples":{"type":"array","items":{"type":"string"},"description":"Concrete examples of failing responses (few-shot for the judge)."},"initial_chat_history":{"type":"array","items":{"$ref":"#/components/schemas/SimulationMessage"},"description":"Optional seed conversation prepended before `context`. Lets you test the agent's reply mid-conversation rather than on a cold single-turn prompt."},"system_prompt_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`, which applies a proposed\nprompt to every test in the run without editing each one.\nStill honoured; the run-level override wins when both are set.\nReplaces the agent's system prompt for this run only.","deprecated":true},"first_message_override":{"type":"string","description":"Replaces the agent's first message for this run only."},"model_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Overrides the LLM\nmodel used by the agent for this run only. Must be a model the\nworkspace's plan includes; an over-tier or unknown model fails\nthe run.","deprecated":true}},"required":["success_criteria"],"description":"Configuration for a `reply` test. The runner sends `context` as\na user message and asks an LLM judge to evaluate the agent response\nagainst `success_criteria`. Optional few-shot examples sharpen the\njudge's calibration. Use `initial_chat_history` to prepend prior\nturns before `context`; when the history already ends with a user\nmessage, `context` may be omitted and the agent is evaluated on\nits reply to that last history turn.","title":"ReplyConfig"},"ParameterCheckMode":{"type":"string","enum":["exact","regex","llm"],"description":"How a `ParameterCheck` validates a tool argument.\n- `exact` - JSON equality.\n- `regex` - the argument stringified is matched against the pattern.\n- `llm` - an LLM judge decides whether the value semantically satisfies\n the criteria (e.g. \"is a plausible email address\").\n","title":"ParameterCheckMode"},"ParameterCheck":{"type":"object","properties":{"path":{"type":"string","description":"Dotted JSON path to the argument being checked. Empty means the whole args object."},"mode":{"$ref":"#/components/schemas/ParameterCheckMode"},"expected":{"type":"string","description":"Expected value string for `exact` and `regex` modes."},"criteria":{"type":"string","description":"Natural-language criteria for `llm` mode (e.g. \"is a valid email address\")."}},"required":["path","mode"],"description":"Validates one argument of an expected tool call. `path` is a\ndotted JSON path (e.g. `customer.email`); use zero-indexed\nnotation for arrays (`items.0.sku`). An empty path checks the\nwhole args object.","title":"ParameterCheck"},"ToolCallConfig":{"type":"object","properties":{"context":{"type":"string","description":"User message that should cause the agent to invoke the expected tool. Optional when `initial_chat_history` already ends with a user message."},"expected_tool":{"type":"string","description":"Name of the tool the agent is expected to call. Leave empty to\ninvert the assertion: the test passes only when the agent calls\nno tool at all."},"parameter_checks":{"type":"array","items":{"$ref":"#/components/schemas/ParameterCheck"},"description":"Assertions on specific arguments of the tool call."},"initial_chat_history":{"type":"array","items":{"$ref":"#/components/schemas/SimulationMessage"},"description":"Optional seed conversation prepended before `context`."},"system_prompt_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Replaces the\nagent's system prompt for this run only.","deprecated":true},"model_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Overrides the LLM\nmodel used by the agent for this run only. Must be a model the\nworkspace's plan includes; an over-tier or unknown model fails\nthe run.","deprecated":true}},"required":["expected_tool"],"description":"Configuration for a `tool` test. The runner sends `context` as a\nuser message and asserts that the agent calls `expected_tool` with\narguments matching all `parameter_checks`. Use\n`initial_chat_history` to test tool invocations that only make\nsense mid-conversation.","title":"ToolCallConfig"},"DataAssertionMode":{"type":"string","enum":["exact","regex","llm"],"description":"How the assertion validates the extracted value.","title":"DataAssertionMode"},"DataAssertion":{"type":"object","properties":{"key":{"type":"string","description":"Name of the data-collection field on the agent's evaluation config. The assertion fails when this key is missing from the extracted data."},"mode":{"$ref":"#/components/schemas/DataAssertionMode","description":"How the assertion validates the extracted value."},"expected":{"type":"string","description":"Expected value string for `exact` and `regex` modes."},"criteria":{"type":"string","description":"Natural-language criteria for `llm` mode."}},"required":["key","mode"],"description":"Asserts on one entry in the LLM-extracted data-collection map\nproduced by the unified evaluator. `key` matches a\ndata-collection field configured on the agent; the assertion\nruns against the value the judge wrote under that key. Same\nexact / regex / llm modes as `ParameterCheck` so the tool-call\nand data-collection assertion surfaces are uniform.","title":"DataAssertion"},"SimulationConfig":{"type":"object","properties":{"scenario":{"type":"string","description":"Instructions for the AI caller describing who they are and what they want."},"max_turns":{"type":"integer","minimum":1,"maximum":50,"default":5,"description":"Maximum agent turns before the simulation is cut off and judged."},"initial_chat_history":{"type":"array","items":{"$ref":"#/components/schemas/SimulationMessage"},"description":"Optional seed conversation that precedes the AI caller's first generated message."},"data_assertions":{"type":"array","items":{"$ref":"#/components/schemas/DataAssertion"},"description":"Optional assertions on the LLM-extracted data-collection\nmap. Each entry references a key from the agent's\ndata_collection config and validates the extracted value.\nThe test fails if any assertion fails."},"system_prompt_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Replaces the\nagent's system prompt for this run only.","deprecated":true},"model_override":{"type":"string","description":"Deprecated. Prefer the run-level `config_override`\non `POST /v1/agents/{agent_id}/tests/runs`. Still honoured; the\nrun-level override wins when both are set. Overrides the LLM\nmodel used by the agent for this run only. Must be a model the\nworkspace's plan includes; an over-tier or unknown model fails\nthe run.","deprecated":true}},"required":["scenario"],"description":"Configuration for a `simulation` test. An AI caller drives a\nmulti-turn conversation with the agent according to `scenario`.\nAfter `max_turns` exchanges (or when the agent ends the call),\nthe unified post-call evaluator scores the synthetic transcript\nagainst the agent's configured evaluation criteria + data\ncollection fields. A test passes when no configured criterion\nfails and every `data_assertions` entry passes.","title":"SimulationConfig"},"CreateAgentTestRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/ReplyConfig"},{"$ref":"#/components/schemas/ToolCallConfig"},{"$ref":"#/components/schemas/SimulationConfig"}],"description":"Type-specific configuration. Must match the shape for the given `type`.","title":"CreateAgentTestRequestConfig"},"MockingStrategy":{"type":"string","enum":["none","all","selected"],"description":"Controls which tool calls the runner intercepts during a run.\nSystem tools (`end_call`, `transfer_to_number`, etc.) are never\nmocked regardless of strategy.\n- `none` - no interception; all tools are called normally.\n- `all` - every non-system tool call is intercepted and matched\n against the `mocks` list.\n- `selected` - only tools explicitly listed in `mocks` are\n intercepted; others are called normally.\n","title":"MockingStrategy"},"ToolMock":{"type":"object","properties":{"tool_name":{"type":"string","description":"Name of the tool to intercept."},"args_match":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional structured argument matcher. When set, the mock fires\nonly if the tool call's arguments deep-contain every key/value\nin this object: nested objects match recursively as subsets,\narrays and scalar leaves match by deep equality. An empty\nobject matches unconditionally. When absent the mock matches\nunconditionally for this tool."},"response":{"description":"JSON value returned to the agent as the tool result."}},"required":["tool_name","response"],"description":"A canned response returned when the agent calls `tool_name`. If\n`args_match` is set the mock only triggers when the call arguments\ndeep-contain it (a structured subset match). A mock without\n`args_match` always matches for its tool.","title":"ToolMock"},"NoMatchBehavior":{"type":"string","enum":["call_real_tool","finish_with_error","skip"],"description":"Fallback when a mockable tool is called but no configured mock\nmatches the call arguments.\n- `call_real_tool` - pass-through: actually invoke the underlying\n tool (a webhook tool POSTs to the customer endpoint). Use only\n when the real call is safe to make from a test.\n- `finish_with_error` - fail: the run finishes as a `failed`\n verdict. Useful when a test wants to assert that a specific\n mocked response path is taken - any unmocked tool call fails the\n test.\n- `skip` - return an empty stub (`{\"skipped\":true}`) to the agent so\n the simulation proceeds without treating the call as a failure.\n Useful when a tool's output is irrelevant to the behaviour under\n test but the model may still decide to call it. This is the\n default for a test with no mock configuration.\n","title":"NoMatchBehavior"},"ToolMockConfig":{"type":"object","properties":{"strategy":{"$ref":"#/components/schemas/MockingStrategy"},"mocks":{"type":"array","items":{"$ref":"#/components/schemas/ToolMock"},"description":"Canned responses for specific tools (order matters - first match wins)."},"no_match_behavior":{"$ref":"#/components/schemas/NoMatchBehavior"}},"required":["strategy","no_match_behavior"],"description":"Controls tool-call interception during a test run.","title":"ToolMockConfig"},"CreateAgentTestRequest":{"type":"object","properties":{"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Optional authoring agent (`agent_<26 char Crockford base32>`).\nWhen supplied it must be an agent in the caller's workspace; it\nseeds the test's tool schemas and variables and is recorded as\nauthored-from provenance. Omit it to create a bare workspace\ntest with no authoring agent. It is never a scope, and the run\ntarget is bound at run time independently of it.\n"},"name":{"type":"string","maxLength":200,"description":"Short human-readable label for the test."},"description":{"type":"string","maxLength":2000,"description":"Optional longer description of what this test verifies."},"type":{"$ref":"#/components/schemas/TestType"},"config":{"$ref":"#/components/schemas/CreateAgentTestRequestConfig","description":"Type-specific configuration. Must match the shape for the given `type`."},"tool_mock_config":{"$ref":"#/components/schemas/ToolMockConfig","description":"Optional tool-mocking config applied during every run of this test."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-test variable values substituted into string fields of the\nconfig at run-start. Keys use the same rules as agent-level\n`DynamicVariable` keys.\n"},"folder_id":{"type":["string","null"],"description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`)\nof the folder to place the test in. Omit / null for root.\n"}},"required":["name","type","config"],"description":"Payload for `POST /v1/agents/tests`.","title":"CreateAgentTestRequest"},"AgentTestConfig":{"oneOf":[{"$ref":"#/components/schemas/ReplyConfig"},{"$ref":"#/components/schemas/ToolCallConfig"},{"$ref":"#/components/schemas/SimulationConfig"}],"description":"Type-specific configuration document.","title":"AgentTestConfig"},"AgentTest":{"type":"object","properties":{"id":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent this test was authored against, or `null` for a\nbare workspace test with no authoring agent. This is\nauthored-from provenance - it records which agent's tool schemas\nand variables the test was built from, plus an audit of where\nthe test came from - not an ownership or access handle: every\nread, list, and run is scoped by workspace, and the run target\nis bound at run time independently of this field. It also stays\nput (a dangling provenance pointer) if the authoring agent is\nlater deleted.\n"},"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"type":{"$ref":"#/components/schemas/TestType"},"config":{"$ref":"#/components/schemas/AgentTestConfig","description":"Type-specific configuration document."},"tool_mock_config":{"$ref":"#/components/schemas/ToolMockConfig","description":"Optional tool-mocking config applied during runs of this test."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-test dynamic-variable overrides. Keys substitute `{{key}}`\nplaceholders inside the test config at run-start. Unknown keys\nrender as empty string, matching session dispatch behaviour.\n"},"folder_id":{"type":["string","null"],"pattern":"^folder_[0-9a-hjkmnp-tv-z]{26}$","description":"When set, prefixed wire identifier\n(`folder_<26 char Crockford base32>`) of the containing folder.\nNull means root (unfiled).\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","agent_id","name","description","type","config","created_at","updated_at"],"description":"A configured test against a voice agent. `config` is a\ntype-specific document - see `ReplyConfig`, `ToolCallConfig`,\nand `SimulationConfig` for the per-type shapes (discriminated by `type`).","title":"AgentTest"},"TestVerdict":{"type":"string","enum":["passed","failed"],"description":"The domain pass/fail judgment of an agent-test run or suite run,\nseparate from the lifecycle `status`. Present only once the run\nreached a verdict (`status` is `completed`); absent for a run that\ncould not execute (`status` `failed`), was `cancelled`, or is still\nin flight.\n","title":"TestVerdict"},"ReplyResult":{"type":"object","properties":{"agent_response":{"type":"string","description":"The raw text response the agent produced."},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"LLM judge's explanation of the verdict."},"score":{"type":"string","title":"float","description":"0-1 judge confidence score."},"duration_ms":{"type":"integer","format":"int64","description":"Wall-clock time for the run in milliseconds."}},"required":["agent_response","passed","rationale","score","duration_ms"],"description":"Result details for a `reply` test run.","title":"ReplyResult"},"ParameterCheckResult":{"type":"object","properties":{"path":{"type":"string"},"mode":{"$ref":"#/components/schemas/ParameterCheckMode"},"actual_json":{"type":"string","description":"JSON-serialised actual value at `path`."},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"LLM rationale (populated for `llm` mode checks)."}},"required":["path","mode","actual_json","passed"],"description":"Result of one `ParameterCheck` within a tool-call test run.","title":"ParameterCheckResult"},"ToolCallResult":{"type":"object","properties":{"tool_called":{"type":"string","description":"Name of the tool the agent actually called (may differ from `expected_tool`)."},"tool_args":{"description":"Arguments the agent passed to the tool, as a JSON object."},"expected_tool":{"type":"string","description":"Name of the tool the test expected the agent to call."},"tool_matched":{"type":"boolean","description":"True when `tool_called` equals `expected_tool`."},"parameter_results":{"type":"array","items":{"$ref":"#/components/schemas/ParameterCheckResult"}},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"Explanation of the overall verdict."},"duration_ms":{"type":"integer","format":"int64"}},"required":["tool_called","expected_tool","tool_matched","parameter_results","passed","rationale","duration_ms"],"description":"Result details for a `tool` test run.","title":"ToolCallResult"},"SimulationToolCall":{"type":"object","properties":{"turn_index":{"type":"integer","description":"Zero-based index of the conversation turn in which this call occurred."},"tool_name":{"type":"string"},"args":{"description":"Arguments passed to the tool, as a JSON object."},"response":{"description":"Response returned to the agent (absent for system tools that end the call)."},"mocked":{"type":"boolean"}},"required":["turn_index","tool_name","args","mocked"],"description":"One tool invocation that occurred during a simulation run.\n`mocked` is true when the call was intercepted by the run's\nmock config; false when the real tool was called or when the\ntool is a system tool.","title":"SimulationToolCall"},"SimulationResultSentiment":{"type":"string","enum":["positive","neutral","negative"],"description":"Overall sentiment classification.","title":"SimulationResultSentiment"},"SimulationCriterionResultStatus":{"type":"string","enum":["success","failure","unknown"],"description":"Three-state outcome. `unknown` means the criterion did not\napply on this run (the topic never came up); `failure`\nmeans it did apply and the agent did not satisfy it.","title":"SimulationCriterionResultStatus"},"SimulationCriterionResult":{"type":"object","properties":{"criterion_id":{"type":"string"},"name":{"type":"string"},"status":{"$ref":"#/components/schemas/SimulationCriterionResultStatus","description":"Three-state outcome. `unknown` means the criterion did not\napply on this run (the topic never came up); `failure`\nmeans it did apply and the agent did not satisfy it."},"score":{"type":"string","title":"float","description":"0.0..1.0 continuous estimate of how well the criterion was met."},"rationale":{"type":"string"}},"required":["criterion_id","name","status","rationale"],"description":"One scored entry of an agent's configured evaluation criterion\nagainst a simulation transcript. Mirrors the per-criterion row\nthe post-call evaluator persists, so test runs and live\nconversations carry identical per-criterion shapes.","title":"SimulationCriterionResult"},"DataAssertionResultMode":{"type":"string","enum":["exact","regex","llm"],"title":"DataAssertionResultMode"},"DataAssertionResult":{"type":"object","properties":{"key":{"type":"string"},"mode":{"$ref":"#/components/schemas/DataAssertionResultMode"},"actual_json":{"type":"string","description":"The extracted value rendered as JSON (`null` when the key was missing from the data map)."},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"Empty on pass; reason for failure otherwise."}},"required":["key","mode","actual_json","passed"],"description":"Outcome of one `data_assertions` entry: did the value the\nevaluator extracted under `key` pass the configured exact /\nregex / llm check.","title":"DataAssertionResult"},"SimulationResult":{"type":"object","properties":{"transcript":{"type":"array","items":{"$ref":"#/components/schemas/SimulationMessage"},"description":"Full synthetic conversation in order."},"tool_calls":{"type":"array","items":{"$ref":"#/components/schemas/SimulationToolCall"},"description":"Every tool invocation across all turns."},"turns_used":{"type":"integer","description":"Number of agent turns that ran before the simulation ended."},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"Top-level verdict explanation (run summary on pass; first failing criterion or assertion on fail)."},"duration_ms":{"type":"integer","format":"int64"},"summary":{"type":"string","description":"One-sentence narrative summary of what happened in the conversation."},"sentiment":{"$ref":"#/components/schemas/SimulationResultSentiment","description":"Overall sentiment classification."},"criteria":{"type":"array","items":{"$ref":"#/components/schemas/SimulationCriterionResult"},"description":"One result row per configured EvaluationCriterion on the\nagent. Same shape as the per-criterion rows persisted on\nthe post-call evaluations table."},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"LLM-extracted values for the agent's configured\ndata-collection fields. Keys mirror the agent's\ndata_collection field keys; values are typed per the\ndeclared field type."},"data_assertions":{"type":"array","items":{"$ref":"#/components/schemas/DataAssertionResult"},"description":"One result row per `data_assertions` entry on the simulation config."}},"required":["transcript","turns_used","passed","rationale","duration_ms"],"description":"Result details for a `simulation` test run. Scoring is unified\nwith the post-call evaluator: the synthetic transcript\nis scored against the agent's configured evaluation criteria\nand data-collection fields, then per-test `data_assertions`\ncheck the extracted values. The top-level `passed` is derived\n\u2014 every criterion must resolve to `success` or `unknown` and\nevery assertion must pass.","title":"SimulationResult"},"TestRunResult":{"type":"object","properties":{"test_type":{"$ref":"#/components/schemas/TestType"},"passed":{"type":"boolean"},"rationale":{"type":"string","description":"Top-level verdict explanation duplicated from the inner result for quick rendering."},"duration_ms":{"type":"integer","format":"int64"},"reply":{"oneOf":[{"$ref":"#/components/schemas/ReplyResult"},{"type":"null"}]},"tool_call":{"oneOf":[{"$ref":"#/components/schemas/ToolCallResult"},{"type":"null"}]},"simulation":{"oneOf":[{"$ref":"#/components/schemas/SimulationResult"},{"type":"null"}]}},"required":["test_type","passed","rationale","duration_ms"],"description":"Union-like result of a completed test run. Exactly one of\n`reply`, `tool_call`, or `simulation` is populated, matching\nthe `test_type`.","title":"TestRunResult"},"AgentTestRun":{"type":"object","properties":{"id":{"type":"string","pattern":"^run_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`run_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"test_id":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`)\nof the parent test.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent this run executed against.\n"},"status":{"$ref":"#/components/schemas/JobStatus"},"verdict":{"oneOf":[{"$ref":"#/components/schemas/TestVerdict"},{"type":"null"}],"description":"The pass/fail judgment, present only once the run reached a\nverdict (`status` is `completed`).\n"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"},"result":{"oneOf":[{"$ref":"#/components/schemas/TestRunResult"},{"type":"null"}],"description":"Populated once the run reached a verdict."},"error":{"type":"string","description":"Human-readable error message when `status` is `failed`."},"created_at":{"type":"string","format":"date-time"}},"required":["id","test_id","agent_id","status","created_at"],"description":"One execution of a test. The lifecycle `status` is the unified\nasync-job vocabulary; the pass/fail judgment lives in the separate\n`verdict` field. `result` is populated when the run reached a\nverdict (`status` is `completed`); see `TestRunResult` for the\nshape. A run that could not execute is `status` `failed` with no\n`verdict`, and `error` carries the reason.","title":"AgentTestRun"},"AgentTestWithLastRun":{"type":"object","properties":{"id":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent this test was authored against, or `null` for a\nbare workspace test with no authoring agent. This is\nauthored-from provenance - it records which agent's tool schemas\nand variables the test was built from, plus an audit of where\nthe test came from - not an ownership or access handle: every\nread, list, and run is scoped by workspace, and the run target\nis bound at run time independently of this field. It also stays\nput (a dangling provenance pointer) if the authoring agent is\nlater deleted.\n"},"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"type":{"$ref":"#/components/schemas/TestType"},"config":{"$ref":"#/components/schemas/AgentTestConfig","description":"Type-specific configuration document."},"tool_mock_config":{"$ref":"#/components/schemas/ToolMockConfig","description":"Optional tool-mocking config applied during runs of this test."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-test dynamic-variable overrides. Keys substitute `{{key}}`\nplaceholders inside the test config at run-start. Unknown keys\nrender as empty string, matching session dispatch behaviour.\n"},"folder_id":{"type":["string","null"],"pattern":"^folder_[0-9a-hjkmnp-tv-z]{26}$","description":"When set, prefixed wire identifier\n(`folder_<26 char Crockford base32>`) of the containing folder.\nNull means root (unfiled).\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"last_run":{"oneOf":[{"$ref":"#/components/schemas/AgentTestRun"},{"type":"null"}],"description":"The most recent run, or null if the test has never been run."}},"required":["id","agent_id","name","description","type","config","created_at","updated_at"],"description":"List-view projection of a test that includes the most recent run\nso a client can display pass/fail badges without an extra\nround-trip.","title":"AgentTestWithLastRun"},"ListTestsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"tests":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestWithLastRun"}}},"required":["next_cursor","has_more","tests"],"description":"Workspace-wide paginated list of tests. Walk pages while\n`has_more` is true; pass `next_cursor` back as the request\n`cursor` parameter.","title":"ListTestsResponse"},"UpdateAgentTestRequestConfig":{"oneOf":[{"$ref":"#/components/schemas/ReplyConfig"},{"$ref":"#/components/schemas/ToolCallConfig"},{"$ref":"#/components/schemas/SimulationConfig"}],"description":"Replaces the test config when present.","title":"UpdateAgentTestRequestConfig"},"UpdateAgentTestRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"config":{"$ref":"#/components/schemas/UpdateAgentTestRequestConfig","description":"Replaces the test config when present."},"tool_mock_config":{"$ref":"#/components/schemas/ToolMockConfig","description":"Replaces the tool-mock config when present."},"folder_id":{"type":["string","null"],"description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`)\nof the folder to move the test into, or `null` to move the\ntest back to root. Omit to leave unchanged.\n"}},"description":"Payload for `PATCH /v1/agents/tests/{test_id}` (JSON merge-patch).\nAll fields are optional; omitting a field leaves it unchanged. Set\n`folder_id` to a target folder id to move the test into that\nfolder, or send `folder_id: null` to move the test back to root.","title":"UpdateAgentTestRequest"},"AgentTestFolder":{"type":"object","properties":{"id":{"type":"string","pattern":"^folder_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"parent_folder_id":{"type":["string","null"],"pattern":"^folder_[0-9a-hjkmnp-tv-z]{26}$","description":"When set, prefixed wire identifier\n(`folder_<26 char Crockford base32>`) of the parent folder.\nNull means root.\n"},"name":{"type":"string"},"test_count":{"type":"integer","description":"Number of tests in this folder the caller's current project\nscope admits: the credential's project pin and the member's\nproject grants, narrowed further by the `project_id` filter\nwhen given. It is the row count\n`GET /v1/agents/tests?folder_id=` returns under the same\nscope.\n"},"total_test_count":{"type":"integer","description":"Number of tests in this folder the caller could see with no\n`project_id` filter: every project for an unscoped caller,\nonly the granted projects for a scoped member. It never counts\na test the caller cannot list, so under a `project_id` filter\nthe difference to `test_count` is how many of the folder's\nvisible tests that filter hides, never how many sit outside\nthe caller's grants.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","test_count","total_test_count","created_at","updated_at"],"description":"One organisational node in the per-owner tests tree. A folder is\nworkspace-wide: it groups tests across agents and so across\nprojects, and no project scope ever hides a folder. Every folder\nresponse carries `test_count` and `total_test_count`; neither ever\ncounts a test the caller could not list. A single-folder response\ntakes no `project_id` filter, so there the two are equal.\n","title":"AgentTestFolder"},"ListAgentTestFoldersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"folders":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestFolder"}}},"required":["next_cursor","has_more","folders"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentTestFoldersResponse"},"CreateAgentTestFolderRequest":{"type":"object","properties":{"name":{"type":"string"},"parent_folder_id":{"type":["string","null"],"description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`)\nof the parent folder. Omit / null for a root-level folder.\n"}},"required":["name"],"title":"CreateAgentTestFolderRequest"},"UpdateAgentTestFolderRequest":{"type":"object","properties":{"name":{"type":"string"},"parent_folder_id":{"type":["string","null"],"description":"Prefixed wire identifier (`folder_<26 char Crockford base32>`)\nof the folder to reparent this folder under, or `null` to\nreparent it to root. Omit to leave unchanged.\n"}},"description":"PATCH body (JSON merge-patch). All fields optional; omit a field\nto leave it unchanged. Set `parent_folder_id` to a target folder\nid to reparent into that folder, or send `parent_folder_id: null`\nto reparent this folder to root.","title":"UpdateAgentTestFolderRequest"},"ListAgentTestRunsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestRun"}}},"required":["next_cursor","has_more","runs"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentTestRunsResponse"},"RunTarget":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent to run every test against: the run target, bound\nat run time, independent of any test's authored-from agent.\n"},"version":{"type":"string","description":"Reserved for version-pinned targeting once agent versioning\nships. Not supported yet; supplying it returns 400."},"tag":{"type":"string","description":"Reserved for tag targeting once agent versioning ships. Not\nsupported yet; supplying it returns 400."}},"required":["agent_id"],"description":"The agent a targeted run binds to at run time. `agent_id` is\nrequired. `version` and `tag` pin a specific agent version once\nagent versioning ships; supplying either today returns 400, so\nagent-id targeting works now and version/tag light up later without\na wire change.","title":"RunTarget"},"TestRunConfigOverride":{"type":"object","properties":{"prompt":{"type":"string","description":"Replaces the agent's system prompt for every test in the run."},"model":{"type":"string","description":"Overrides the LLM model for every test in the run. The model\nid rides on the agent's configured provider \u2014 a\ncross-provider switch is not supported."},"tool_ids":{"type":"array","items":{"type":"string"},"description":"Replaces the agent's attached external tools for the run with\nexactly this set. Each entry is a prefixed `tool_`\nid; `builtin_` ids are rejected. An empty array runs with no\ntools; omit the field to keep the agent's attachments."}},"description":"A run-level config override applied to every test in a Run All.\nLayered on top of the agent's stored config for the duration of\nthe suite run, so the whole suite can be validated against a\nproposed prompt / model / toolbelt without editing any test. An\nabsent field leaves the agent's value untouched; a run-level\noverride wins over a deprecated per-test `system_prompt_override`\n/ `model_override`.","title":"TestRunConfigOverride"},"RunTargetedTestsRequest":{"type":"object","properties":{"test_ids":{"type":"array","items":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Prefixed `test_` ids to run. De-duplicated, so a\nrepeated id runs once.\n"},"target":{"$ref":"#/components/schemas/RunTarget"},"config_override":{"$ref":"#/components/schemas/TestRunConfigOverride"}},"required":["test_ids","target"],"description":"Run a chosen set of tests against a target agent, bound at run\ntime. The tests are not attached to the agent, so the same set can\nrun against another agent in a second call (A/B), and against a\npinned agent version once versioning ships (version-pinned\nregression). Total runs are capped at 100 per call.","title":"RunTargetedTestsRequest"},"SuiteRunTrigger":{"type":"string","enum":["run_all","batch","resubmit","target"],"description":"Which entry point created a suite run.\n- `run_all` - POST /v1/agents/{agent_id}/tests/runs.\n- `batch` - POST /v1/agents/tests/runs/batch.\n- `resubmit` - POST /v1/agents/tests/suite-runs/{suite_run_id}/resubmit.\n- `target` - POST /v1/agents/tests/runs (a chosen set of tests\n bound to a target agent at run time).\n","title":"SuiteRunTrigger"},"AgentTestSuiteRunResults":{"type":"object","properties":{"passed":{"type":"integer"},"failed":{"type":"integer"}},"required":["passed","failed"],"description":"Per-verdict breakdown among the `completed` child runs.","title":"AgentTestSuiteRunResults"},"AgentTestSuiteRun":{"type":"object","properties":{"id":{"type":"string","pattern":"^srun_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`srun_<26 char Crockford base32>`)."},"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed `agent_` id of the agent whose suite\nwas run. Set for the `run_all` trigger; null for `batch`,\nwhich can span many agents.\n"},"agent_name":{"type":["string","null"],"description":"Display name of `agent_id`'s agent, resolved at read time.\nNull whenever `agent_id` is null, and on the suite run\nembedded in run/resubmit creation responses.\n"},"trigger":{"$ref":"#/components/schemas/SuiteRunTrigger"},"parent_suite_run_id":{"type":["string","null"],"description":"Set on a `resubmit`: the prefixed `srun_` id of\nthe suite run whose failed/errored tests this one re-ran.\nNull for `run_all` and `batch`.\n"},"status":{"$ref":"#/components/schemas/JobStatus"},"verdict":{"oneOf":[{"$ref":"#/components/schemas/TestVerdict"},{"type":"null"}],"description":"The suite's pass/fail judgment, present only when `status` is\n`completed`.\n"},"total":{"type":"integer","description":"Number of child runs in the suite."},"completed":{"type":"integer","description":"Child runs that produced a verdict."},"failed":{"type":"integer","description":"Child runs that could not execute (an infrastructure failure)."},"running":{"type":"integer","description":"Child runs actively executing."},"pending":{"type":"integer","description":"Child runs queued, not yet executing."},"cancelled":{"type":"integer","description":"Child runs cancelled."},"results":{"$ref":"#/components/schemas/AgentTestSuiteRunResults","description":"Per-verdict breakdown among the `completed` child runs."},"created_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time","description":"Newest child-run completion; null until every child run is terminal."},"config_override":{"oneOf":[{"$ref":"#/components/schemas/TestRunConfigOverride"},{"type":"null"}],"description":"The run-level config override this suite was run\nwith, or null for an ordinary Run All / batch."},"flow_version_id":{"type":["string","null"],"pattern":"^fver_[0-9a-hjkmnp-tv-z]{26}$","description":"The flow version (`agent_versions` row) this suite targeted\n(prefixed external id, `fver_...`), or null for the agent's\nactive / synthesized flow."},"flow_version_number":{"type":["integer","null"],"description":"Human-facing version number of `flow_version_id`; null when no version was targeted."}},"required":["id","trigger","status","total","completed","failed","running","pending","cancelled","results","created_at"],"description":"A suite run (test invocation): the grouping object over every\ntest run dispatched by one Run All, batch, or resubmit call.\n`status`, `verdict`, and the count fields are derived from the\nchild runs. `status` is the unified async-job lifecycle: `running`\nwhile any child is pending/running, then `cancelled` (any child\ncancelled), `failed` (any child could not execute), or `completed`\n(every child produced a verdict). When `completed`, `verdict` is\n`passed` if every child passed, else `failed`. `results` carries\nthe per-verdict breakdown.","title":"AgentTestSuiteRun"},"RunAgentTestsResponse":{"type":"object","properties":{"runs":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestRun"}},"suite_run":{"oneOf":[{"$ref":"#/components/schemas/AgentTestSuiteRun"},{"type":"null"}],"description":"The suite run grouping the queued runs."}},"required":["runs"],"description":"Response from `POST /v1/agents/{agent_id}/tests/runs` and the suite-run\nresubmit endpoint. Contains every newly-queued run so the client\ncan poll each for completion, plus the `suite_run` that groups\nthem. `suite_run` is null only when a Run All found no tests.","title":"RunAgentTestsResponse"},"BatchRunEntry":{"type":"object","properties":{"test_id":{"type":"string","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`)\nof the test to run.\n"},"agent_id":{"type":"string","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent to run the test against (any agent in the\nworkspace; the run target is bound here at run time). Omit to\nrun against the agent the test was authored against.\n"}},"required":["test_id"],"description":"One entry in a batch-run request. Omit `agent_id` to run the test\nagainst the agent it was authored against; a test with no authoring\nagent requires an explicit `agent_id`.","title":"BatchRunEntry"},"RunBatchRequest":{"type":"object","properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/BatchRunEntry"}}},"required":["entries"],"description":"Batch-run payload. Total expanded runs across all entries are\ncapped at 100 per call.","title":"RunBatchRequest"},"ListSuiteRunsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"suite_runs":{"type":"array","items":{"$ref":"#/components/schemas/AgentTestSuiteRun"}}},"required":["next_cursor","has_more","suite_runs"],"description":"One page of suite runs, newest first. Walk pages while\n`has_more` is true; pass `next_cursor` back as the request\n`cursor` parameter.","title":"ListSuiteRunsResponse"},"AgentTestSuiteRunWithRunsResults":{"type":"object","properties":{"passed":{"type":"integer"},"failed":{"type":"integer"}},"required":["passed","failed"],"description":"Per-verdict breakdown among the `completed` child runs.","title":"AgentTestSuiteRunWithRunsResults"},"SuiteChildRun":{"type":"object","properties":{"id":{"type":"string","pattern":"^run_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`run_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"test_id":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`test_<26 char Crockford base32>`)\nof the parent test.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent this run executed against.\n"},"status":{"$ref":"#/components/schemas/JobStatus"},"verdict":{"oneOf":[{"$ref":"#/components/schemas/TestVerdict"},{"type":"null"}],"description":"The pass/fail judgment, present only once the run reached a\nverdict (`status` is `completed`).\n"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"},"result":{"oneOf":[{"$ref":"#/components/schemas/TestRunResult"},{"type":"null"}],"description":"Populated once the run reached a verdict."},"error":{"type":"string","description":"Human-readable error message when `status` is `failed`."},"created_at":{"type":"string","format":"date-time"},"test_name":{"type":"string","description":"Name of the test this run executed."},"agent_name":{"type":"string","description":"Display name of the agent this run executed against."}},"required":["id","test_id","agent_id","status","created_at","test_name","agent_name"],"description":"One child run inside a suite run, carrying the parent test's\nname and the target agent's name so the grouped result view can\nlabel each row. The agent name disambiguates results when the\nsuite spans multiple agents.","title":"SuiteChildRun"},"AgentTestSuiteRunWithRuns":{"type":"object","properties":{"id":{"type":"string","pattern":"^srun_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`srun_<26 char Crockford base32>`)."},"agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed `agent_` id of the agent whose suite\nwas run. Set for the `run_all` trigger; null for `batch`,\nwhich can span many agents.\n"},"agent_name":{"type":["string","null"],"description":"Display name of `agent_id`'s agent, resolved at read time.\nNull whenever `agent_id` is null, and on the suite run\nembedded in run/resubmit creation responses.\n"},"trigger":{"$ref":"#/components/schemas/SuiteRunTrigger"},"parent_suite_run_id":{"type":["string","null"],"description":"Set on a `resubmit`: the prefixed `srun_` id of\nthe suite run whose failed/errored tests this one re-ran.\nNull for `run_all` and `batch`.\n"},"status":{"$ref":"#/components/schemas/JobStatus"},"verdict":{"oneOf":[{"$ref":"#/components/schemas/TestVerdict"},{"type":"null"}],"description":"The suite's pass/fail judgment, present only when `status` is\n`completed`.\n"},"total":{"type":"integer","description":"Number of child runs in the suite."},"completed":{"type":"integer","description":"Child runs that produced a verdict."},"failed":{"type":"integer","description":"Child runs that could not execute (an infrastructure failure)."},"running":{"type":"integer","description":"Child runs actively executing."},"pending":{"type":"integer","description":"Child runs queued, not yet executing."},"cancelled":{"type":"integer","description":"Child runs cancelled."},"results":{"$ref":"#/components/schemas/AgentTestSuiteRunWithRunsResults","description":"Per-verdict breakdown among the `completed` child runs."},"created_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time","description":"Newest child-run completion; null until every child run is terminal."},"config_override":{"oneOf":[{"$ref":"#/components/schemas/TestRunConfigOverride"},{"type":"null"}],"description":"The run-level config override this suite was run\nwith, or null for an ordinary Run All / batch."},"flow_version_id":{"type":["string","null"],"pattern":"^fver_[0-9a-hjkmnp-tv-z]{26}$","description":"The flow version (`agent_versions` row) this suite targeted\n(prefixed external id, `fver_...`), or null for the agent's\nactive / synthesized flow."},"flow_version_number":{"type":["integer","null"],"description":"Human-facing version number of `flow_version_id`; null when no version was targeted."},"runs":{"type":"array","items":{"$ref":"#/components/schemas/SuiteChildRun"}}},"required":["id","trigger","status","total","completed","failed","running","pending","cancelled","results","created_at","runs"],"description":"A suite run plus every child run, for the grouped detail view.","title":"AgentTestSuiteRunWithRuns"},"RunAllTestsRequest":{"type":"object","properties":{"config_override":{"$ref":"#/components/schemas/TestRunConfigOverride"},"flow_version_id":{"type":"string","description":"Targets a specific flow version (an `agent_versions` row)\ninstead of the agent's active flow \u2014 version-targeted\nregression. Must be a flow version of the agent under test.\nPrefixed external id (`fver_...`)."}},"description":"Optional body of `POST /v1/agents/{agent_id}/tests/runs`. Omit it\nentirely to run every test against the agent's live config and\nactive flow.","title":"RunAllTestsRequest"},"TestStatsBucket":{"type":"object","properties":{"day":{"type":"string","description":"ISO date (YYYY-MM-DD)."},"passed":{"type":"integer"},"failed":{"type":"integer"},"errored":{"type":"integer"}},"required":["day","passed","failed","errored"],"description":"One daily point on the aggregate pass-rate chart.","title":"TestStatsBucket"},"TestStats":{"type":"object","properties":{"window_days":{"type":"integer"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/TestStatsBucket"}},"total_runs":{"type":"integer"},"passed_runs":{"type":"integer"},"failed_runs":{"type":"integer"},"errored_runs":{"type":"integer"},"avg_duration_ms":{"type":"integer"},"by_type":{"type":"object","additionalProperties":{"type":"integer"}}},"required":["window_days","buckets","total_runs","passed_runs","failed_runs","errored_runs","avg_duration_ms"],"description":"Aggregate run metrics over the requested window. `buckets` is\ndense - one entry per day in the window, zero-filled, so a chart\nnever has gaps. `by_type` counts runs per test type across the\nwhole window.","title":"TestStats"},"ShareLinkStatus":{"type":"string","enum":["active","revoked","expired","exhausted"],"description":"The link's resolved lifecycle state, computed server-side so\nevery client agrees on what \"expired\" means.","title":"ShareLinkStatus"},"ShareLink":{"type":"object","properties":{"id":{"type":"string","pattern":"^share_[0-9a-hjkmnp-tv-z]{26}$"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"label":{"type":"string","description":"The owner's own note on the link, for telling several apart."},"token_prefix":{"type":"string","description":"The token's leading characters, kept so a list can show a\nrecognisable masked label. Useless as a guess on its own."},"status":{"$ref":"#/components/schemas/ShareLinkStatus","description":"The link's resolved lifecycle state, computed server-side so\nevery client agrees on what \"expired\" means."},"expires_at":{"type":"string","format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"},"budget_seconds":{"type":"integer","description":"Total conversation time this link may ever authorise."},"seconds_used":{"type":"integer","description":"Conversation time settled against the link."},"seconds_reserved":{"type":"integer","description":"Conversation time held by calls currently in flight."},"seconds_remaining":{"type":"integer","description":"What a new call can still draw on: the budget minus settled and\nin-flight time. Reservations count against it, so two\nsimultaneous starts cannot both spend the tail."},"max_call_duration_seconds":{"type":"integer","description":"Ceiling on any single call opened through this link. Clamped at\ndispatch against the agent's own limit and the workspace plan,\nso a link can shorten a call but never lengthen one."},"max_concurrent":{"type":"integer","description":"How many calls this link may run at once. Bounded separately\nfrom the workspace's own concurrency cap, so a widely-shared\nlink cannot consume the capacity the owner's real callers need."},"allowed_tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Tools the owner opted this link into, by id \u2014 the same\n`tool_\u2026` ids `listAgentTools` returns. Empty means the\ndefault posture: the agent's built-in call controls and\nknowledge search only, with no webhook, MCP or transfer tool \u2014\na link recipient is a stranger, not the owner's customer."},"last_used_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"required":["id","agent_id","label","token_prefix","status","expires_at","revoked_at","budget_seconds","seconds_used","seconds_reserved","seconds_remaining","max_call_duration_seconds","max_concurrent","allowed_tool_ids","last_used_at","created_at"],"description":"A share link: a revocable, capped URL that lets anyone hold a voice\nconversation with an agent without signing in.\n\nThe bearer token is not part of this shape. A list read is fired on\nevery page mount and the token is a credential that spends money, so\nit is fetched one link at a time from `revealShareLinkToken` instead.\n`token_prefix` is what a list can show. Every link's URL can be read\nback, so there is no state in which a copy action is unavailable.","title":"ShareLink"},"ListShareLinksResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"share_links":{"type":"array","items":{"$ref":"#/components/schemas/ShareLink"}}},"required":["next_cursor","has_more","share_links"],"description":"Payload for `GET /v1/agents/{agent_id}/share-links`. Cursor-paginated:\nwalk pages while `has_more` is true.","title":"ListShareLinksResponse"},"CreateShareLinkRequest":{"type":"object","properties":{"label":{"type":"string","maxLength":120,"description":"An optional note so several links can be told apart."},"expires_in_seconds":{"type":"integer","description":"How long the link stays usable. A duration rather than a\ntimestamp, so a client with a skewed clock cannot mint a link\nthat is already expired. Defaults to 7 days, maximum 30."},"budget_seconds":{"type":"integer","description":"Total conversation time the link may authorise, across all its\ncalls. This is the control that bounds what a leaked link can\ncost. Defaults to 1 hour, maximum 4."},"max_call_duration_seconds":{"type":"integer","description":"Ceiling on a single call. Defaults to 5 minutes, maximum 30, and\nis clamped again to the agent's own limit."},"max_concurrent":{"type":"integer","description":"Simultaneous calls allowed on this link. Defaults to 3, maximum 10."},"allowed_tool_ids":{"type":"array","items":{"type":"string"},"description":"Tools to opt this link into, by id \u2014 the same `tool_\u2026` ids\n`listAgentTools` returns. Omit for the default posture (call\ncontrols and knowledge search only). Opting a webhook or MCP\ntool in lets a stranger drive it, so it is an explicit act\nrather than something inherited from the agent. Every id must\nname a tool attached to this agent; anything else is a 400."}},"description":"Body for `POST /v1/agents/{agent_id}/share-links`. Every field is\noptional and falls back to a bounded default.","title":"CreateShareLinkRequest"},"CreateShareLinkResponseStatus":{"type":"string","enum":["active","revoked","expired","exhausted"],"description":"The link's resolved lifecycle state, computed server-side so\nevery client agrees on what \"expired\" means.","title":"CreateShareLinkResponseStatus"},"CreateShareLinkResponse":{"type":"object","properties":{"id":{"type":"string","pattern":"^share_[0-9a-hjkmnp-tv-z]{26}$"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"label":{"type":"string","description":"The owner's own note on the link, for telling several apart."},"token_prefix":{"type":"string","description":"The token's leading characters, kept so a list can show a\nrecognisable masked label. Useless as a guess on its own."},"status":{"$ref":"#/components/schemas/CreateShareLinkResponseStatus","description":"The link's resolved lifecycle state, computed server-side so\nevery client agrees on what \"expired\" means."},"expires_at":{"type":"string","format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"},"budget_seconds":{"type":"integer","description":"Total conversation time this link may ever authorise."},"seconds_used":{"type":"integer","description":"Conversation time settled against the link."},"seconds_reserved":{"type":"integer","description":"Conversation time held by calls currently in flight."},"seconds_remaining":{"type":"integer","description":"What a new call can still draw on: the budget minus settled and\nin-flight time. Reservations count against it, so two\nsimultaneous starts cannot both spend the tail."},"max_call_duration_seconds":{"type":"integer","description":"Ceiling on any single call opened through this link. Clamped at\ndispatch against the agent's own limit and the workspace plan,\nso a link can shorten a call but never lengthen one."},"max_concurrent":{"type":"integer","description":"How many calls this link may run at once. Bounded separately\nfrom the workspace's own concurrency cap, so a widely-shared\nlink cannot consume the capacity the owner's real callers need."},"allowed_tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Tools the owner opted this link into, by id \u2014 the same\n`tool_\u2026` ids `listAgentTools` returns. Empty means the\ndefault posture: the agent's built-in call controls and\nknowledge search only, with no webhook, MCP or transfer tool \u2014\na link recipient is a stranger, not the owner's customer."},"last_used_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"token":{"type":"string","description":"The link's opaque bearer token."}},"required":["id","agent_id","label","token_prefix","status","expires_at","revoked_at","budget_seconds","seconds_used","seconds_reserved","seconds_remaining","max_call_duration_seconds","max_concurrent","allowed_tool_ids","last_used_at","created_at","token"],"description":"The created link, plus its bearer token. The token is also stored, so\nit can be read back later with `revealShareLinkToken` rather than by\nrevoking a link the recipient may already hold.\n\nThe response returns the raw token rather than a URL, so the surface\nthat shows it owns the origin.","title":"CreateShareLinkResponse"},"RevealShareLinkTokenResponse":{"type":"object","properties":{"token":{"type":"string","description":"The link's opaque bearer token."}},"required":["token"],"description":"A share link's bearer token, so the owner can copy the URL again.","title":"RevealShareLinkTokenResponse"},"ShareLinkPreview":{"type":"object","properties":{"agent_name":{"type":"string","description":"The agent's display name. Present because a visitor needs to\nknow who they are about to talk to, and it is not part of the\nembed's visual configuration."},"widget_config":{"$ref":"#/components/schemas/WidgetConfig","description":"The embed's resolved visual configuration \u2014 avatar, colours,\ntheme, button copy \u2014 so the page renders exactly like the real\nembed without ever naming the agent."},"terms_enabled":{"type":"boolean","description":"Whether the owner configured a pre-call consent gate. Surfaced\nhere so the PAGE can render it: the embed's own gate reads the\nagent's configuration, which a share-link page deliberately\nnever loads."},"terms_content":{"type":"string","description":"The consent text to display, as markdown."},"max_call_duration_seconds":{"type":"integer"},"seconds_remaining":{"type":"integer","description":"Call time left on the link, so the page can say \"nearly used\nup\" rather than failing at the click."}},"required":["agent_name","widget_config","terms_enabled","max_call_duration_seconds","seconds_remaining"],"description":"What a share-link page may know before anyone speaks: enough to\nrender \"who is this, and can I talk to them\".\n\nDeliberately thin. No agent id (which would let a caller bypass the\nlink's limits by calling the public session endpoint directly), no\nworkspace id, no prompt, no model, no tool or knowledge-base names.","title":"ShareLinkPreview"},"CreateShareLinkSessionRequest":{"type":"object","properties":{"token":{"type":"string","description":"The share link's opaque bearer token."}},"required":["token"],"description":"Body for `POST /v1/agents/share-links/sessions`. The token travels in\nthe body rather than the URL so it does not reach request logs.","title":"CreateShareLinkSessionRequest"},"ShareLinkSession":{"type":"object","properties":{"token":{"type":"string","description":"The realtime access token for the session."},"url":{"type":"string","description":"The realtime server URL to connect to."}},"required":["token","url"],"description":"The realtime handles for a share-link call \u2014 exactly what the\n`` element's `sessionProvider` returns.","title":"ShareLinkSession"},"PhoneNumberProvider":{"type":"string","enum":["livekit","twilio","telnyx","byoc","twilio_purchased","telnyx_purchased","verified_caller_id"],"description":"Which provider the number came from. Determines the provisioning\nand portability path.\n\n- `livekit` - LiveKit owns the carrier relationship; US inbound only.\n- `twilio` - Customer's own Twilio number bridged via Elastic SIP Trunk.\n- `telnyx` - Customer's own Telnyx number bridged via a Telnyx FQDN connection.\n- `byoc` - Any SIP provider using a customer-supplied trunk.\n- `twilio_purchased` - Bought through `POST /v1/agents/phone-numbers/purchase` on Speechify's master Twilio account; billed to Speechify.\n- `telnyx_purchased` - Bought through `POST /v1/agents/phone-numbers/purchase` (with `provider=telnyx`) on Speechify's master Telnyx account; billed to Speechify.\n- `verified_caller_id` - Customer-verified outbound caller ID on\n their own Twilio account (Twilio's OutgoingCallerIds resource).\n Server-determined at import time: when an `e164` submitted with\n `provider=twilio` is not a full DID on the customer's account but\n IS a verified caller ID, the resulting row gets this provider.\n Outbound-only, never agent-bindable, rides the customer's\n existing shared Twilio trunk for outbound routing. Requires a\n prior `twilio` full-DID import from the same account; without\n it the import returns 400.\n","title":"PhoneNumberProvider"},"PhoneNumberRelayHealth":{"type":"string","enum":["healthy","unhealthy"],"description":"Read-only endpoint health. `unhealthy` after several consecutive\ndelivery failures: inbound calls answer with the goodbye line and\nSMS events are parked instead of delivered. Resets to `healthy`\non the next successful delivery, on rebinding the relay, or on\nrotating the secret.","title":"PhoneNumberRelayHealth"},"PhoneNumberRelay":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The HTTPS endpoint receiving this number's events."},"voice_id":{"type":"string","description":"Voice that speaks the relayed replies. When absent, the platform\ndefault voice is used."},"reply_timeout_ms":{"type":"integer","minimum":1000,"maximum":30000,"description":"Per-turn deadline in milliseconds for the endpoint to answer a\nreply-bearing event. Default 5000."},"fallback_line":{"type":"string","description":"Line spoken to cover a slow or failing endpoint while the\ndelivery is retried (e.g. \"One moment please\")."},"goodbye_line":{"type":"string","description":"Line spoken before hanging up when the endpoint stays\nunreachable after the retry."},"api_version":{"type":"string","description":"Dated version (`YYYY-MM-DD`) the relay's event payloads are\nrendered at. When absent, deliveries follow the workspace's\ncurrent version."},"greeting":{"type":"string","description":"Line spoken when the call is picked up, ahead of the webhook's own\n`call.started` reply (and after any AI-disclosure line). Omitted\nwhen unset."},"transfer_number":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","description":"Default cold-transfer destination in E.164, used when a reply asks\nto transfer without an explicit target. Omitted when unset."},"hangup_after_silence_seconds":{"type":"integer","description":"Auto-hangup silence timeout in seconds. Omitted when unset (no\nsilence hangup)."},"ai_disclosure":{"$ref":"#/components/schemas/AIDisclosure","description":"Opt-in AI-disclosure opening line spoken first on `call.started`,\nbefore the webhook greeting. Omitted when off (the default)."},"signing_secret":{"type":"string","description":"HMAC signing secret (`whsec_...`) for verifying the\n`Speechify-Signature` header on deliveries. Present ONLY in the\nresponse that FIRST bound the relay and in the rotate-secret\nresponse; store it, it cannot be retrieved later. Editing a relay\nsetting on an already-bound number never mints a new one, so the\ncopy you stored keeps verifying - rotate-secret is the only way\nto replace it."},"health":{"$ref":"#/components/schemas/PhoneNumberRelayHealth","description":"Read-only endpoint health. `unhealthy` after several consecutive\ndelivery failures: inbound calls answer with the goodbye line and\nSMS events are parked instead of delivered. Resets to `healthy`\non the next successful delivery, on rebinding the relay, or on\nrotating the secret."}},"required":["url","reply_timeout_ms"],"description":"**Deprecated. Read the number's `brain_id` and fetch the brain at\n`/v1/agents/brains/{brain_id}` instead.**\n\nA read-only projection of the brain a number is bound to, kept for one\ndeprecation cycle. It is returned ONLY by the deprecated\n`POST /v1/agents/phone-numbers/{phone_number_id}/rotate-secret`, which\nis the one place its `signing_secret` can appear; every other\nphone-number response omits it.\n","title":"PhoneNumberRelay","deprecated":true},"PhoneNumberCapability":{"type":"string","enum":["inbound","outbound"],"description":"What the number can do. LiveKit-native numbers are `inbound` only;\nverified caller IDs are `outbound` only; Twilio and BYOC full-DID\nnumbers (and Speechify-purchased numbers) support both directions.\n","title":"PhoneNumberCapability"},"PhoneNumberReputationRisk":{"type":"string","enum":["unknown","low","medium","high"],"description":"Overall spam-risk classification. `unknown` means the number has\nbeen checked but the carrier networks have no score yet (a fresh\nnumber has no calling history); `low` / `medium` / `high` are the\nreported risk levels.","title":"PhoneNumberReputationRisk"},"PhoneNumberReputation":{"type":"object","properties":{"risk":{"$ref":"#/components/schemas/PhoneNumberReputationRisk","description":"Overall spam-risk classification. `unknown` means the number has\nbeen checked but the carrier networks have no score yet (a fresh\nnumber has no calling history); `low` / `medium` / `high` are the\nreported risk levels."},"checked_at":{"type":"string","format":"date-time","description":"When the reputation was last read."}},"required":["risk"],"description":"A number's carrier spam-reputation status, refreshed on a schedule\nfrom the analytics networks carriers use. Read-only; surfaced\nalongside relay health so a number going bad is visible before it\ndrags down deliverability.\n","title":"PhoneNumberReputation"},"PhoneNumber":{"type":"object","properties":{"id":{"type":"string","pattern":"^phone_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`phone_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"e164":{"type":"string","description":"The phone number in E.164 format (e.g. `+12025551234`)."},"type":{"$ref":"#/components/schemas/PhoneNumberProvider"},"label":{"type":"string","description":"Optional human-readable label set by the customer."},"trunk_id":{"type":"string","description":"ID of the SIP trunk backing this number, if applicable."},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"ID of the agent this number's calls run as. Null only while the\nnumber is unbound. Pointing a number at a brain provisions an agent\ntoo - it is what gives an externally-brained call a conversation,\ntranscript and lifecycle - so this is also the `agent_id` to pass to\n`POST /v1/agents/outbound-calls` to dial out from such a number."},"brain_id":{"type":"string","pattern":"^brain_[0-9a-hjkmnp-tv-z]{26}$","description":"The external brain answering this number's traffic. Absent when\nthe number is unbound or answered by a hosted agent. Read the\nendpoint, health and settings at `/v1/agents/brains/{brain_id}`."},"relay":{"$ref":"#/components/schemas/PhoneNumberRelay","description":"**Deprecated. Use `brain_id`.** Returned only by the deprecated\nrotate-secret operation on this resource, to carry the brain's\none-time `signing_secret`; omitted everywhere else.","deprecated":true},"iso_country":{"type":"string","description":"ISO-3166 alpha-2 country of the number. Present on purchased\nnumbers; may be absent on imported numbers."},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumberCapability"},"description":"What this number can do."},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"reputation":{"$ref":"#/components/schemas/PhoneNumberReputation","description":"Carrier spam-reputation status for the number, refreshed on a\nschedule. Present once the number has been checked at least\nonce; omitted otherwise."},"created_at":{"type":"string","format":"date-time","description":"When the number was imported."},"updated_at":{"type":"string","format":"date-time","description":"When the number was last modified."}},"required":["id","e164","type","capabilities","created_at","updated_at"],"description":"A phone number in the workspace inventory. Its brain is a union: a\nhosted agent, or one of your own external brains via `brain_id` - never\nboth at once. Unbound numbers are valid but non-functional until\nassigned. A number bound to an external brain also carries an\n`agent_id` (the bind provisions one, which is what gives the call a\nconversation and a transcript), so `brain_id` is the discriminator.\n","title":"PhoneNumber"},"ListPhoneNumbersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"numbers":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumber"},"description":"Phone numbers in the workspace."}},"required":["next_cursor","has_more","numbers"],"description":"Response for `GET /v1/agents/phone-numbers`.","title":"ListPhoneNumbersResponse"},"TwilioImportSpec":{"type":"object","properties":{"account_sid":{"type":"string","description":"Twilio Account SID (starts with `AC`)."},"auth_token":{"type":"string","description":"Twilio Auth Token. Write-only - never echoed back."}},"required":["account_sid","auth_token"],"description":"Twilio credentials for the one-click import flow. Used only when\n`provider=twilio`. The Account SID and Auth Token are used to\nprovision an Elastic SIP Trunk on the customer's Twilio account\npointing at LiveKit's SIP endpoint, then stored for future trunk\nmanagement operations.\n","title":"TwilioImportSpec"},"TelnyxImportSpec":{"type":"object","properties":{"connection_id":{"type":"string","description":"The Telnyx FQDN connection id (numeric string) the number is\nrouted through. Required.\n"},"sip_username":{"type":"string","description":"Optional credential-auth username for the Telnyx connection.\nWhen both `sip_username` and `sip_password` are provided, the\nimport also provisions outbound calling for the number; omit them\nfor an inbound-only import. Write-only - never echoed back.\n"},"sip_password":{"type":"string","description":"Optional credential-auth password for the Telnyx connection,\npaired with `sip_username`. Write-only - never echoed back.\n"}},"required":["connection_id"],"description":"Telnyx connection details for the bring-your-own-connection import\nflow. Used only when `provider=telnyx`. You provision an FQDN\nconnection on your Telnyx account whose inbound calls forward to\nSpeechify's LiveKit SIP ingress, assign the number to it, then import\nby passing the connection's id. A second number on the same connection\nreuses the one shared trunk + dispatch rule.\n","title":"TelnyxImportSpec"},"ImportPhoneNumberRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"e164":{"type":"string","description":"The phone number in E.164 format. For `provider=livekit` this\nis the number you want LiveKit to purchase. For `provider=twilio`,\n`provider=telnyx`, and `provider=byoc` it is the number you\nalready own.\n"},"provider":{"$ref":"#/components/schemas/PhoneNumberProvider"},"label":{"type":"string","description":"Optional human-readable label."},"trunk_id":{"type":"string","description":"For `provider=byoc`: the SIP trunk to bind this number to.\nPrefixed wire identifier (`trunk_<26 char Crockford base32>`).\nNot required for `provider=livekit`, `provider=twilio`, or\n`provider=telnyx`.\n"},"agent_id":{"type":"string","description":"Optional agent to bind on import. Prefixed wire identifier\n(`agent_<26 char Crockford base32>`).\n"},"twilio":{"$ref":"#/components/schemas/TwilioImportSpec"},"telnyx":{"$ref":"#/components/schemas/TelnyxImportSpec"}},"required":["e164","provider"],"description":"Body for `POST /v1/agents/phone-numbers`. The required fields vary by\n`provider` - see the individual provider descriptions.\n","title":"ImportPhoneNumberRequest"},"PurchasedPhoneNumberProvider":{"type":"string","enum":["twilio_purchased","telnyx_purchased"],"description":"The carrier a Speechify-managed number is bought on, used by\n`POST /v1/agents/phone-numbers/purchase`. The resulting\n`phone_numbers` row carries the matching `PhoneNumberProvider`\nvalue.\n\nOnly `telnyx_purchased` can be bought. `twilio_purchased` is\n**retired for new purchases** and returns `400 validation_failed`;\nit remains in this enum because numbers already bought on it are\nstill listed, released and dialled normally, and because removing a\nrequest enum value would be a breaking change. Omit `provider` and\nthe buy lands on the current carrier automatically - the\nrecommended call for every client.\n","title":"PurchasedPhoneNumberProvider"},"AvailablePhoneNumber":{"type":"object","properties":{"e164":{"type":"string","description":"The phone number in E.164 format."},"friendly_name":{"type":"string","description":"Carrier-formatted display variant, e.g. \"(415) 555-2671\"."},"locality":{"type":"string","description":"City the number is associated with, when known."},"region":{"type":"string","description":"Two-letter state code for US numbers."},"iso_country":{"type":"string","description":"ISO-3166 alpha-2 country code."},"provider":{"$ref":"#/components/schemas/PurchasedPhoneNumberProvider","description":"The purchasable carrier this number comes from. Pass it back\nverbatim as `provider` on the purchase request so the buy is\nplaced on the matching Speechify-managed account.\n"}},"required":["e164","iso_country","provider"],"description":"One hit from `GET /v1/agents/phone-numbers/available`. The number is\nnot held: a concurrent buy by another customer may take it\nbetween this response and a subsequent purchase request.\n","title":"AvailablePhoneNumber"},"SearchAvailablePhoneNumbersResponse":{"type":"object","properties":{"numbers":{"type":"array","items":{"$ref":"#/components/schemas/AvailablePhoneNumber"},"description":"Available numbers (may be empty if no inventory matches)."}},"required":["numbers"],"description":"Response for `GET /v1/agents/phone-numbers/available`.","title":"SearchAvailablePhoneNumbersResponse"},"IntendedUseMarketingOrInformational":{"type":"string","enum":["marketing","informational","mixed"],"description":"The nature of the traffic.","title":"IntendedUseMarketingOrInformational"},"IntendedUse":{"type":"object","properties":{"use_case":{"type":"string","maxLength":500,"description":"What the numbers are used for."},"geographies":{"type":"array","items":{"type":"string"},"description":"Regions or countries the traffic targets."},"expected_monthly_volume":{"type":"integer","minimum":0,"description":"Rough expected calls + messages per month."},"marketing_or_informational":{"$ref":"#/components/schemas/IntendedUseMarketingOrInformational","description":"The nature of the traffic."}},"required":["use_case"],"description":"A workspace's declared intended use for its phone numbers, captured\nat first number purchase (or relay bind): the use case, target\ngeographies, expected monthly volume, and whether the traffic is\nmarketing or informational.\n","title":"IntendedUse"},"PurchasePhoneNumberRequest":{"type":"object","properties":{"project_id":{"type":"string","description":"Optional workspace project to place this resource in (prefixed\n`proj_...` id). Omit for the implicit Default project. An\nunknown id returns 404 project_not_found.\n"},"e164":{"type":"string","description":"The E.164 number to buy. Must currently be in carrier inventory."},"label":{"type":"string","description":"Optional human-readable label."},"provider":{"$ref":"#/components/schemas/PurchasedPhoneNumberProvider","description":"Which carrier's Speechify-managed account to buy on. Optional\nand best omitted: the platform picks the current carrier\n(`telnyx_purchased`). Passing the retired `twilio_purchased`\nreturns `400 validation_failed`.\n"},"agent_id":{"type":"string","description":"Optional agent to bind the number to at purchase time.\nPrefixed wire identifier (`agent_<26 char Crockford base32>`).\n"},"intended_use":{"$ref":"#/components/schemas/IntendedUse","description":"Optional workspace-level intended-use declaration recorded with\nthe purchase (replaces any earlier declaration). May be required\nby the workspace trust policy.\n"},"tos_accepted":{"type":"boolean","description":"Set true to record the ToS/consent attestation for this\nworkspace. Recorded once - a repeat purchase never moves the\noriginal attestation's actor or timestamp.\n"}},"required":["e164"],"description":"Body for `POST /v1/agents/phone-numbers/purchase`. The `e164` must come\nfrom a recent `SearchAvailablePhoneNumbers` response.\n","title":"PurchasePhoneNumberRequest"},"UpdatePhoneNumberRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"Move the resource between workspace projects: a `proj_...` id\nmoves it there, an explicit null moves it back to the implicit\nDefault project, omitted leaves it unchanged.\n"},"label":{"type":"string","description":"New label. Pass an empty string to clear."},"brain_id":{"type":["string","null"],"description":"Point this number at one of your external brains: a `brain_...`\nid binds it, an explicit null takes it off the number, omitted\nleaves the binding unchanged. Only a reference ever rides this body\n- the endpoint and its signing secret belong to the brain and are\nedited at `/v1/agents/brains/{brain_id}`, so rotating the secret\nthere is enough and no number has to be re-saved.\n"},"replace_agent_binding":{"type":"boolean","description":"Acknowledges that this bind takes the number away from a hosted\nagent that is still answering on it. Without it, pointing an\nagent-bound number at an external brain is refused with\n`409 relay_displaces_agent` naming the agent, so the displacement\nis never a surprise. Re-pointing one brain at another does not need\nit.\n"}},"description":"PATCH body for `PATCH /v1/agents/phone-numbers/{phone_number_id}`. Edits\nthe number's own attributes - `label` - and, where the Phone product is\nenabled, points the number at one of your external brains via\n`brain_id`. The\nnumber's provider and `e164` are immutable after import. The agent\nbinding is managed separately as a relationship, via\n`POST`/`DELETE /v1/agents/{agent_id}/phone-numbers/{phone_number_id}`;\na number's brain is a hosted agent or a brain of your own, never both.\n","title":"UpdatePhoneNumberRequest"},"CreateOutboundCallRequest":{"type":"object","properties":{"agent_id":{"type":"string","description":"ID of the agent that handles the answered call."},"to":{"type":"string","description":"Destination phone number in E.164 format (e.g. `+12025559876`)."},"caller_id_number":{"type":"string","description":"The number shown to the callee as caller ID, in E.164 format.\nDefaults to the first outbound-capable number in the workspace.\nUseful for multi-number campaigns where you want to rotate\ncaller IDs.\n"},"language":{"type":"string","description":"Starts the call in one of the agent's configured languages (the\ndefault `language` or an `additional_languages` entry, matched\nby primary subtag) - e.g. a batch campaign dialing a per-row\nlocale. Omit for the agent's default language; an unconfigured\nlanguage is rejected with 400.\n"},"dtmf_prefix":{"type":"string","description":"DTMF digits dialed automatically after the call is answered,\nbefore the agent begins speaking. Use this for IVR navigation\n(e.g. `1ww2` presses 1, waits two seconds, presses 2). `w`\nis a half-second pause; `W` is a one-second pause.\n"},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-call variable overrides merged on top of the agent's stored\ndefaults. Keys must not use the reserved `system__` prefix.\nUseful for injecting per-call context (customer name, order ID)\ninto the agent prompt.\n"},"ringing_timeout_ms":{"type":"integer","description":"How long to wait for the callee to answer before abandoning,\nin milliseconds. Defaults to 30000 (30s). Capped at 80000 (80s).\n"},"amd":{"$ref":"#/components/schemas/AMDConfig","description":"Optional per-call override for the AMD routing config. When\nset, wholesale-replaces the agent's stored AMD shape for\nthis single call (PATCH-replace, not merge). Unlocks the\nbatch-campaign pattern: one agent dialling many recipients\nwith per-row tailored voicemail messages via the existing\ndynamic_variables substitution. Validation rules match\nthe agent-update boundary.\n"}},"required":["agent_id","to"],"description":"Body for `POST /v1/agents/outbound-calls`. Requires a Twilio or BYOC\ntrunk; LiveKit-native numbers do not support outbound today.\n","title":"CreateOutboundCallRequest"},"CreateOutboundCallResponse":{"type":"object","properties":{"conversation_id":{"type":"string","pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"ID of the conversation created for this call. Use to poll status."}},"required":["conversation_id"],"description":"Returned synchronously when LiveKit accepts the SIP INVITE. Poll\n`GET /v1/agents/conversations/{conversation_id}` for status transitions:\n`pending` (ringing) \u2192 `active` (answered) \u2192 `completed`.\n","title":"CreateOutboundCallResponse"},"BatchCall":{"type":"object","properties":{"id":{"type":"string","pattern":"^batch_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`batch_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent that will run the batch.\n"},"phone_number_id":{"type":["string","null"],"pattern":"^phone_[0-9a-hjkmnp-tv-z]{26}$","description":"Caller-ID override. When set, prefixed wire identifier\n(`phone_<26 char Crockford base32>`) of the phone number to\nuse; falls back to the agent's bound number when null.\n"},"name":{"type":"string","description":"Human-readable batch name."},"status":{"$ref":"#/components/schemas/JobStatus","description":"Lifecycle status. A batch deferred to a future time is\n`pending` with a non-null `scheduled_at`; it moves to `running`\nonce the scheduled time arrives and the dispatcher starts\ndialing.\n"},"total":{"type":"integer","description":"Total number of recipients (the progress denominator)."},"completed":{"type":"integer","description":"Recipients successfully dialed."},"failed":{"type":"integer","description":"Recipients that failed."},"error":{"type":"string","description":"Populated when the batch itself fails."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time","description":"When the dispatcher started dialing."},"ended_at":{"type":["string","null"],"format":"date-time","description":"When the last recipient was resolved."},"scheduled_at":{"type":["string","null"],"format":"date-time","description":"If set, the batch waits until this time before dialing."},"ringing_timeout_ms":{"type":["integer","null"],"description":"Per-call ringing timeout in milliseconds applied to every\nrecipient in the batch. Null when the batch uses the 30s\ndefault.\n"}},"required":["id","agent_id","name","status","total","completed","failed","created_at","updated_at"],"description":"A batch of outbound calls dispatched to a list of recipients.","title":"BatchCall"},"ListBatchCallsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"batches":{"type":"array","items":{"$ref":"#/components/schemas/BatchCall"}}},"required":["next_cursor","has_more","batches"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListBatchCallsResponse"},"BatchRecipientRequest":{"type":"object","properties":{"phone":{"type":"string","description":"Recipient phone number in E.164 format."},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-recipient variable overrides injected into the agent prompt.\nValues may be any JSON type, matching the outbound-call and\nweb-session `dynamic_variables` surfaces. CSV uploads carry\nstring values.\n"},"custom_id":{"type":"string","maxLength":256,"description":"Optional client-supplied correlation key, echoed back on the\nrecipient read shape so results can be reconciled to the caller's\nown identifier (an order id, a CRM lead id). A CSV upload sets it\nfrom a `custom_id` column. Max 256 characters.\n"}},"required":["phone"],"description":"One entry in a batch-call request.","title":"BatchRecipientRequest"},"CreateBatchCallRequest":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable batch name."},"agent_id":{"type":"string","description":"Agent that handles each call."},"phone_number_id":{"type":"string","description":"Caller-ID override. Falls back to the agent's bound number."},"scheduled_at":{"type":"string","format":"date-time","description":"Schedule the batch for a future time (RFC 3339). Omit to start immediately."},"ringing_timeout_ms":{"type":"integer","minimum":1000,"maximum":80000,"description":"Ringing timeout in milliseconds applied to every call in the\nbatch (how long each recipient rings before the dial gives\nup). Range 1000-80000 (1-80s). Omit to use the 30s default.\nExpress this in milliseconds even if a UI collects seconds.\n"},"recipients":{"type":"array","items":{"$ref":"#/components/schemas/BatchRecipientRequest"}}},"required":["name","agent_id","recipients"],"description":"Body for `POST /v1/agents/batch-calls`. Also accepts `multipart/form-data`\nwith a CSV file upload (`csv_file` field) where the `phone` column is\nrequired, an optional `custom_id` column sets the recipient correlation\nkey, and remaining columns become per-recipient `dynamic_variables`.\n","title":"CreateBatchCallRequest"},"BatchRecipientStatus":{"type":"string","enum":["pending","dialing","completed","failed","cancelled","interrupted"],"description":"`cancelled` marks a recipient the batch cancel spared before it\nwas dialed - distinct from `failed` (a dial that was attempted\nand failed). `interrupted` marks a recipient a crashed dispatcher\nleft mid-attempt on resume: the dial was attempted but its outcome\nwas never recorded, so it is not re-dialed - check the linked\nconversation, if any, for the real result.\n","title":"BatchRecipientStatus"},"BatchRecipient":{"type":"object","properties":{"id":{"type":"string","pattern":"^rcpt_[0-9a-hjkmnp-tv-z]{26}$"},"batch_id":{"type":"string","pattern":"^batch_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`batch_<26 char Crockford base32>`)\nof the parent batch.\n"},"phone":{"type":"string","description":"Recipient phone number in E.164 format."},"dynamic_variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-recipient variable overrides injected into the agent prompt."},"status":{"$ref":"#/components/schemas/BatchRecipientStatus","description":"`cancelled` marks a recipient the batch cancel spared before it\nwas dialed - distinct from `failed` (a dial that was attempted\nand failed). `interrupted` marks a recipient a crashed dispatcher\nleft mid-attempt on resume: the dial was attempted but its outcome\nwas never recorded, so it is not re-dialed - check the linked\nconversation, if any, for the real result.\n"},"conversation_id":{"type":["string","null"],"pattern":"^conv_[0-9a-hjkmnp-tv-z]{26}$","description":"Set once the call is placed. Prefixed wire identifier\n(`conv_<26 char Crockford base32>`).\n"},"custom_id":{"type":"string","description":"Client-supplied correlation key echoed back from the create\nrequest, so results can be reconciled to the caller's own\nidentifier. Absent when the recipient carried none.\n"},"error":{"type":"string","description":"Populated when this recipient fails."},"attempted_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"}},"required":["id","batch_id","phone","status"],"description":"One recipient row in a batch call.","title":"BatchRecipient"},"ListBatchRecipientsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"recipients":{"type":"array","items":{"$ref":"#/components/schemas/BatchRecipient"}}},"required":["next_cursor","has_more","recipients"],"description":"Response for `GET /v1/agents/batch-calls/{batch_call_id}/recipients`.","title":"ListBatchRecipientsResponse"},"IvrMenuListEntryMenuTree":{"type":"object","properties":{},"description":"Validated menu_tree per contracts/agents/ivr_menu.schema.json. Opaque to consumers other than the worker.","title":"IvrMenuListEntryMenuTree"},"IVRMenuListEntry":{"type":"object","properties":{"id":{"type":"string","pattern":"^menu_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`menu_<26 char Crockford base32>`)."},"fingerprint_id":{"type":"string","pattern":"^fp_[0-9a-hjkmnp-tv-z]{26}$"},"transcript_sample":{"type":"string"},"schema_version":{"type":"integer"},"menu_tree":{"$ref":"#/components/schemas/IvrMenuListEntryMenuTree","description":"Validated menu_tree per contracts/agents/ivr_menu.schema.json. Opaque to consumers other than the worker."},"confidence_score":{"type":"string","title":"float"},"succeeded_traversals":{"type":"integer"},"total_traversals":{"type":"integer"},"last_validated_at":{"type":"string","format":"date-time"},"last_observed_at":{"type":"string","format":"date-time"},"occurrence_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}},"required":["id","fingerprint_id","transcript_sample","schema_version","menu_tree","confidence_score","succeeded_traversals","total_traversals","last_validated_at","last_observed_at","occurrence_count","created_at"],"description":"One row in the list-IVR-menus response. Carries the sample\ntranscript so a client can render the IVR identity (keyed on\nthe prefixed `fingerprint_id`) without a second round-trip.\n`last_observed_at` and `occurrence_count` are projected from\n`ivr_fingerprints` for the \"when did we last see this IVR\" signal.\n","title":"IVRMenuListEntry"},"ListIVRMenusResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"menus":{"type":"array","items":{"$ref":"#/components/schemas/IVRMenuListEntry"}}},"required":["next_cursor","has_more","menus"],"description":"Payload for `GET /v1/agents/ivr-menus`.","title":"ListIVRMenusResponse"},"IvrMenuMenuTree":{"type":"object","properties":{},"description":"Validated menu_tree per contracts/agents/ivr_menu.schema.json. Opaque to consumers other than the worker.","title":"IvrMenuMenuTree"},"IVRMenu":{"type":"object","properties":{"id":{"type":"string","pattern":"^menu_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`menu_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"fingerprint_id":{"type":"string","pattern":"^fp_[0-9a-hjkmnp-tv-z]{26}$"},"schema_version":{"type":"integer"},"menu_tree":{"$ref":"#/components/schemas/IvrMenuMenuTree","description":"Validated menu_tree per contracts/agents/ivr_menu.schema.json. Opaque to consumers other than the worker."},"confidence_score":{"type":"string","title":"float"},"succeeded_traversals":{"type":"integer"},"total_traversals":{"type":"integer"},"last_validated_at":{"type":"string","format":"date-time"},"invalidated_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","fingerprint_id","schema_version","menu_tree","confidence_score","succeeded_traversals","total_traversals","last_validated_at","created_at","updated_at"],"description":"One memorized IVR menu level. Identified by the\nSHA-256 fingerprint of the normalized greeting transcript;\nscoped to the caller's workspace (foreign-tenant menus are\nnever returned).\n\n`menu_tree` is the validated JSONB blob the worker consumes:\nprompt text plus the options offered (label + DTMF). Sub-menus\nreached by pressing an option are their own rows, looked up at\ndescent time by a fresh fingerprint - the tree structure is the\nimplicit graph of fingerprint -> fingerprint transitions.\n\n`confidence_score` is `succeeded_traversals / total_traversals`.\nThe worker's plan-then-execute fast path only activates at or\nabove 0.5.\n\n`invalidated_at` is non-null on a soft-deleted row; the API\nfilters these out of list / lookup / get responses so this field\nis informational only.\n","title":"IVRMenu"},"UpdateIVRMenuLabelRequest":{"type":"object","properties":{"dtmf":{"type":"string","description":"DTMF value of the option to relabel (e.g. \"1\", \"*\", \"#\")."},"label":{"type":"string","maxLength":256,"description":"New label. Capped at 256 chars server-side."}},"required":["dtmf","label"],"description":"Re-label one option in the stored menu_tree. The option is\nmatched by its DTMF value; the label is the human-readable text\nshown for review and surfaced to the agent at navigate time.\n","title":"UpdateIVRMenuLabelRequest"},"InvalidateIVRMenuRequest":{"type":"object","properties":{"reason":{"type":"string","maxLength":256,"description":"Operator-debug cause string. Bounded to 256 chars."}},"description":"Optional reason captured in structured logs. The column today\nis the timestamp, not the cause; a future audit table may\npersist the reason if customer demand justifies it.\n","title":"InvalidateIVRMenuRequest"},"SIPTrunkProvider":{"type":"string","enum":["livekit","twilio","telnyx","byoc"],"description":"Which provider backs the trunk. Informs the provisioning path and\nportability story.\n\n- `livekit` - Provisioned by LiveKit's native phone-number API.\n- `twilio` - Backed by a Twilio Elastic SIP Trunk on the customer's account.\n- `telnyx` - Backed by a Telnyx FQDN connection on the customer's account.\n- `byoc` - Any SIP provider with a customer-managed trunk.\n","title":"SIPTrunkProvider"},"SIPTrunkDirection":{"type":"string","enum":["inbound","outbound","both"],"description":"Whether the trunk handles inbound calls, outbound calls, or both.\nA `both` trunk has distinct provider inbound and outbound trunk IDs.\n","title":"SIPTrunkDirection"},"SIPTransport":{"type":"string","enum":["auto","udp","tcp","tls"],"description":"SIP transport protocol. `auto` lets LiveKit negotiate. Use `tls`\nfor production where available - note that TLS is incompatible\nwith SIP REFER (cold transfer). Trunks that need `transfer_to_number`\nshould use `udp` or `tcp`.\n","title":"SIPTransport"},"SIPMediaEncryption":{"type":"string","enum":["disable","allow","require"],"description":"SRTP media encryption policy.\n\n- `disable` - Unencrypted media only.\n- `allow` - Negotiate SRTP; fall back to unencrypted. Recommended default.\n- `require` - Reject calls that do not support SRTP.\n","title":"SIPMediaEncryption"},"SIPTrunk":{"type":"object","properties":{"id":{"type":"string","pattern":"^trunk_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`trunk_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"name":{"type":"string","description":"Human-readable name."},"provider":{"$ref":"#/components/schemas/SIPTrunkProvider"},"direction":{"$ref":"#/components/schemas/SIPTrunkDirection"},"outbound_trunk_set":{"type":"boolean","description":"Whether an outbound trunk is provisioned for this trunk (i.e. it can place outbound calls)."},"sip_address":{"type":"string","description":"SIP endpoint hostname (e.g. `sip.telnyx.com`). Required for `provider=byoc`."},"auth_username":{"type":"string","description":"SIP digest auth username."},"auth_password_set":{"type":"boolean","description":"Whether a SIP digest auth password is configured. The value is never returned."},"allowed_addresses":{"type":"array","items":{"type":"string"},"description":"IP address / CIDR allowlist for inbound SIP connections."},"destination_country":{"type":"string","description":"ISO 3166-1 alpha-2 country code for the outbound dial plan\n(e.g. `US`, `DE`). Required for international outbound on\nsome carriers.\n"},"transport":{"$ref":"#/components/schemas/SIPTransport"},"media_encryption":{"$ref":"#/components/schemas/SIPMediaEncryption"},"headers_to_attributes":{"type":"object","additionalProperties":{"type":"string"},"description":"Inbound SIP headers mapped onto agent variables, as configured\nwhen the trunk was created. Absent when the trunk maps none.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","provider","direction","allowed_addresses","transport","media_encryption","created_at","updated_at"],"description":"A SIP trunk in the workspace. Trunks back one or more phone numbers\nand hold the carrier credentials LiveKit uses to route calls.\n`auth_password` is never echoed - `auth_password_set` indicates\nwhether one is configured.\n","title":"SIPTrunk"},"ListSIPTrunksResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"trunks":{"type":"array","items":{"$ref":"#/components/schemas/SIPTrunk"},"description":"SIP trunks in the workspace (up to 20)."}},"required":["next_cursor","has_more","trunks"],"description":"Response for `GET /v1/agents/sip-trunks`.","title":"ListSIPTrunksResponse"},"CreateSipTrunkRequestCredentials":{"type":"object","properties":{},"description":"Provider-specific credential blob (for future extensibility).","title":"CreateSipTrunkRequestCredentials"},"CreateSIPTrunkRequest":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for the trunk."},"provider":{"$ref":"#/components/schemas/SIPTrunkProvider"},"direction":{"$ref":"#/components/schemas/SIPTrunkDirection"},"sip_address":{"type":"string","description":"SIP endpoint hostname. Required for `provider=byoc`."},"auth_username":{"type":"string","description":"SIP digest auth username."},"auth_password":{"type":"string","description":"SIP digest auth password. Write-only."},"allowed_addresses":{"type":"array","items":{"type":"string"},"description":"IP / CIDR allowlist for inbound connections. Empty means any source is accepted."},"destination_country":{"type":"string","description":"ISO 3166-1 alpha-2 country for the outbound dial plan."},"transport":{"$ref":"#/components/schemas/SIPTransport"},"media_encryption":{"$ref":"#/components/schemas/SIPMediaEncryption"},"credentials":{"$ref":"#/components/schemas/CreateSipTrunkRequestCredentials","description":"Provider-specific credential blob (for future extensibility)."},"headers_to_attributes":{"type":"object","additionalProperties":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,39}$"},"description":"Map inbound SIP headers onto agent variables, so an upstream that\nhas already identified the caller can pass that through instead of\nthe agent re-verifying it in conversation.\n\nEach key is an extension header on the INVITE (`X-\u2026`, a valid SIP\ntoken); each value names the variable its content is exposed under.\nA header mapped to `customer_id` renders in the agent's prompt, and\nin flow tool arguments, as `{{system__sip_customer_id}}`. Two\nheaders may not share a variable name, which is the one rule the\nschema cannot state and the server rejects with\n`validation_failed`.\n\nOnly applies to inbound calls on this trunk. For `provider=byoc`\nthe mapping takes effect when the trunk's first phone number is\nimported, which is when its inbound route is provisioned.\n\n**A mapped header is an identity assertion from your own phone\nsystem, and is only as trustworthy as that system.** PSTN caller ID\nis spoofable and is a hint, not a credential; do not treat either as\nproof of identity for a high-value action. Values are passed to the\nagent as data, never as instructions.\n"}},"required":["name","provider","direction"],"description":"Body for `POST /v1/agents/sip-trunks`.","title":"CreateSIPTrunkRequest"},"Caller":{"type":"object","properties":{"id":{"type":"string","pattern":"^caller_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`caller_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"tenant_id":{"type":"string","pattern":"^ws_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`ws_<26 char Crockford base32>`) of\nthe owning workspace.\n"},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`agent_<26 char Crockford base32>`)\nof the agent the caller is scoped under.\n"},"contact_id":{"type":"string","pattern":"^contact_[0-9a-hjkmnp-tv-z]{26}$","description":"The workspace person this row is one agent's view of. Absent only\nfor a row minted before contacts existed and not seen since.\n"},"caller_identity":{"type":"string","description":"The raw identifier the caller arrived with (E.164 phone for SIP, LiveKit\nparticipant id for web). Stable for the life of the caller row.\n"},"display_name":{"type":["string","null"],"description":"Operator-editable display name, nullable."},"external_ref":{"type":["string","null"],"description":"Optional handle into the customer's own CRM, nullable."},"metadata":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Customer-supplied JSON metadata blob."},"first_seen_at":{"type":"string","format":"date-time","description":"Timestamp of the earliest observed conversation / memory for this caller."},"last_seen_at":{"type":"string","format":"date-time","description":"Timestamp of the most recent observation. Drives the default list ordering."},"conversation_count":{"type":"integer","description":"Number of conversation rows currently pointing at this caller."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","tenant_id","agent_id","caller_identity","metadata","first_seen_at","last_seen_at","conversation_count","created_at","updated_at"],"description":"First-class Caller entity. Identified by\nthe (tenant, agent, identity) triple. Memories and conversations\nFK at it via `caller_id`.\n","title":"Caller"},"ListCallersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"callers":{"type":"array","items":{"$ref":"#/components/schemas/Caller"}}},"required":["next_cursor","has_more","callers"],"description":"Payload for GET /v1/agents/callers.","title":"ListCallersResponse"},"UpdateCallerRequest":{"type":"object","properties":{"display_name":{"type":["string","null"],"description":"Operator-editable display name. Empty string clears the column."},"external_ref":{"type":["string","null"],"description":"Optional handle into the customer's own CRM. Empty string clears the column."},"metadata":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Replacement metadata JSONB. Must not be `null`."}},"description":"PATCH payload. Omitted fields are unchanged; present fields\noverwrite. Empty string clears nullable text columns; `metadata`\nreplaces the JSONB blob in full when supplied.\n","title":"UpdateCallerRequest"},"DeleteCallerResponse":{"type":"object","properties":{"caller_purged":{"type":"integer","description":"1 on the first delete; 0 on idempotent re-delete."},"memories_purged":{"type":"integer","description":"Number of user_memories rows cascade-soft-deleted under this caller."}},"required":["caller_purged","memories_purged"],"description":"Audit envelope returned by DELETE /v1/agents/callers/{caller_id}. Surfaces\nthe cascade row counts so a privacy operator has direct evidence\nof the purge without re-querying.\n","title":"DeleteCallerResponse"},"ListCallerConversationsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}},"required":["next_cursor","has_more","conversations"],"description":"Payload for GET /v1/agents/callers/{caller_id}/conversations.","title":"ListCallerConversationsResponse"},"ListCallerMemoriesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"memories":{"type":"array","items":{"$ref":"#/components/schemas/Memory"}}},"required":["next_cursor","has_more","memories"],"description":"Payload for GET /v1/agents/callers/{caller_id}/memories.","title":"ListCallerMemoriesResponse"},"AudioAsset":{"type":"object","properties":{"id":{"type":"string","pattern":"^audio_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`audio_<26 char Crockford base32>`).\nURL paths accept only this\nprefixed form; legacy UUID path parameters are rejected with\n404.\n"},"original_filename":{"type":"string","description":"The filename supplied at upload time, kept for display."},"content_type":{"type":"string","description":"Always `audio/wav`. Pinned server-side after WAV validation\nrather than trusting the upload's multipart Content-Type\nheader.\n"},"size_bytes":{"type":"integer","description":"Stored byte length. Capped at 4 MiB at upload time."},"duration_ms":{"type":"integer","description":"Clip duration in milliseconds. Capped at 30000 (30s) at upload time."},"sample_rate_hz":{"type":"integer","description":"WAV sample rate. Always 48000 (the rate voice agents play back natively)."},"channels":{"type":"integer","description":"Channel count. Always 1 (mono)."},"bit_depth":{"type":"integer","description":"PCM sample bit depth. Always 16."},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace project this resource lives in (prefixed external\nid). Null means the implicit Default project.\n"},"created_at":{"type":"string","format":"date-time"}},"required":["id","original_filename","content_type","size_bytes","duration_ms","sample_rate_hz","channels","bit_depth","created_at"],"description":"Metadata for a pre-recorded WAV clip stored in the workspace's\naudio-asset bucket. Bytes are immutable once uploaded \u2014 to\nreplace a clip, upload a new asset and update any references.\n","title":"AudioAsset"},"ListAudioAssetsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AudioAsset"}}},"required":["next_cursor","has_more","assets"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAudioAssetsResponse"},"TeamMember":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"The member agent (prefixed external id, `agent_...`)."},"role":{"type":"string","description":"The member's role within the team. Defaults to `member`."},"delegation":{"type":"string","description":"Natural-language note for when the manager should hand work to this\nmember (for example \"refunds over $100\"). Empty when unset.\n"},"position":{"type":"integer","description":"The member's ordering position on the composition canvas."}},"required":["agent_id","role","delegation","position"],"description":"One agent the manager delegates to, with an optional natural-language\nhand-off note and a canvas ordering position.\n","title":"TeamMember"},"Team":{"type":"object","properties":{"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project this team belongs to; null when it belongs to none.\nIts agents live in the same project.\n"},"id":{"type":"string","pattern":"^team_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`team_<26 char Crockford base32>`).\n"},"name":{"type":"string","description":"Human-readable label, unique within the workspace."},"description":{"type":"string","description":"Optional longer description. Empty string when unset."},"manager_agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$","description":"The orchestrator agent, or `null` while a draft has no manager\npicked yet.\n"},"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","manager_agent_id","members","created_at","updated_at"],"description":"A workspace-owned composition of agents: a `manager_agent_id` that\norchestrates plus the `members` it delegates to. Persists the\ncomposition; the delegation runtime that runs a team is a later\naddition.\n","title":"Team"},"ListTeamsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"teams":{"type":"array","items":{"$ref":"#/components/schemas/Team"}}},"required":["next_cursor","has_more","teams"],"description":"Payload for `GET /v1/agents/teams` (the workspace team catalog).\nCursor-paginated.\n","title":"ListTeamsResponse"},"TeamMemberInput":{"type":"object","properties":{"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"role":{"type":"string"},"delegation":{"type":"string"},"position":{"type":"integer"}},"required":["agent_id"],"description":"A member as written on a create/update body. Only `agent_id` is\nrequired: `role` defaults to `member`, `delegation` is optional, and\n`position` defaults to the member's index in the array.\n","title":"TeamMemberInput"},"CreateTeamRequest":{"type":"object","properties":{"project_id":{"type":["string","null"],"description":"The project to create it in. A project-pinned credential creates\ninto its own project regardless, and naming a different one is\nrefused rather than silently redirected. Omit for no project.\n"},"name":{"type":"string"},"description":{"type":"string"},"manager_agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMemberInput"}}},"required":["name"],"description":"Compose a team. The manager and every member must be an agent in the\ncaller's workspace. `description` defaults to an empty string and\n`members` to an empty roster.\n","title":"CreateTeamRequest"},"UpdateTeamRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"manager_agent_id":{"type":["string","null"],"pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMemberInput"}}},"description":"All fields optional (merge-patch): an omitted field is left unchanged,\n`manager_agent_id: null` clears the manager, and a `members` array\nreplaces the whole roster.\n","title":"UpdateTeamRequest"},"CreateTeamRunRequest":{"type":"object","properties":{"instruction":{"type":"string","maxLength":8000,"description":"The task or goal to give the team. The manager agent runs its brain against this, delegating sub-goals to the members, and returns its aggregated result. The server's limit is 8000 **bytes**, so a mostly non-ASCII instruction reaches it before 8000 characters."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Per-run values that seed the manager's flow variables (override its\nstored defaults). As on a single-agent run, the `system__*`\nnamespace and the legacy `memory` alias belong to the platform and\nare rejected with a 400 naming `variables`.\n"},"user_identity":{"type":"string","maxLength":256,"description":"The person this team run acts for, in your own vocabulary - the same\nfield a single-agent run, a conversation and a widget session take,\nso one workspace never has two answers to who a person is. The\nmanager run carries it and every child it delegates inherits it, so\neach member opens knowing what the platform has learned about that\nperson.\n\nOmit it to run the team for nobody in particular. Must not begin\nwith `user_`, `embed_` or `anon_`, which name identities the platform\nderives.\n\nEvery tool called anywhere in the team run - by the manager or by a\ndelegated member - is told this value: a webhook receives it as\n`user_identity` inside the signed body, an MCP server as the\n`Speechify-User-Identity` header, and it renders in a tool's\ntemplated URL or headers as `{{system__caller_id}}`.\n"},"max_turns":{"type":"integer","description":"Upper bound on the manager run's internal turn budget - one turn is\none plan-act-observe cycle. Defaults to 8 when omitted, and bounds\nthe MANAGER only: each delegated child gets its own budget.\n\n**Clamped to the workspace's per-run ceiling** (5 on Free, 10\non Starter, 20 on Pro, 30 on Scale, 50 on Enterprise; per-workspace\noverrides apply): the run's `input.max_turns` echoes the budget it\nactually got, and `GET /v1/workspaces/current/entitlements`\n(`max_run_turns`) reports the ceiling up front, so plan against\nthat rather than the value you sent. An omitted `max_turns` takes\nthe default, clamped to the ceiling.\n"},"output_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional JSON Schema (2020-12) the run's final answer must satisfy.\nWhen set, the agent answers with a JSON object, the platform\nvalidates it, and the conforming object is returned as\n`output.data`.\n\nThe top level must be `type: object` - an array-typed or scalar\nschema is refused at create with `400`. At most 16 KiB.\n\nOn a mismatch the platform re-asks the agent, feeding back up to 8\nof the violations. **At most two repair attempts, and each one\nspends a turn from `max_turns`** - so a schema-constrained run on a\n5-turn ceiling has little room left for tool calls. Each attempt is\njournaled as an `observation` step whose `tool` is the reserved\nname `output_schema`, which a timeline renderer should expect\nalongside real tool names.\n\nA run that never produces a conforming object settles `succeeded`\nwith `incomplete_reason: output_schema_violation` and no\n`output.data` - its prose answer is still on `output.reply`. The\nplatform never returns an object the schema refused.\n"},"metadata":{"type":"object","additionalProperties":{"type":"string"},"description":"Up to 16 arbitrary key/value pairs echoed back on the run. Your own correlation ids belong here - the platform never reads them."}},"required":["instruction"],"description":"Body for `POST /v1/agents/teams/{team_id}/runs`.","title":"CreateTeamRunRequest"},"AgentTriggerType":{"type":"string","enum":["schedule","webhook"],"title":"AgentTriggerType"},"TriggerRunSpec":{"type":"object","properties":{"instruction":{"type":"string","description":"The goal instruction that drives the run (1-8000 characters)."},"variables":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Variable seed for the run. A webhook fire's JSON body is merged in\nunder the webhook's `payload_variable` key.\n"},"max_turns":{"type":"integer","description":"Optional per-run turn budget; 0 uses the workspace default."},"output_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional JSON Schema (2020-12) the run's final answer must satisfy.\nWhen set, the agent answers with a JSON object, the platform\nvalidates it, and the conforming object is returned as\n`output.data`.\n\nThe top level must be `type: object` - an array-typed or scalar\nschema is refused at create with `400`. At most 16 KiB.\n\nOn a mismatch the platform re-asks the agent, feeding back up to 8\nof the violations. **At most two repair attempts, and each one\nspends a turn from `max_turns`** - so a schema-constrained run on a\n5-turn ceiling has little room left for tool calls. Each attempt is\njournaled as an `observation` step whose `tool` is the reserved\nname `output_schema`, which a timeline renderer should expect\nalongside real tool names.\n\nA run that never produces a conforming object settles `succeeded`\nwith `incomplete_reason: output_schema_violation` and no\n`output.data` - its prose answer is still on `output.reply`. The\nplatform never returns an object the schema refused.\n"},"user_identity":{"type":"string","maxLength":256,"description":"The person every run this trigger fires acts for, in your own\nvocabulary - the same field a request-started run takes. A\nscheduled or webhook-fired run then opens with that person's\nmemory, every tool it calls is told who it is for, and\n`GET /v1/agents/runs?user_identity=` finds it, so you keep no\ntrigger-to-person index of your own. Validated when the trigger is\nsaved, by the run surface's rule: must not begin with `user_`,\n`embed_` or `anon_`.\n\nLeave it empty and set `webhook.user_identity_source` to `fire`\nwhen one trigger serves many people and each delivery says which.\n"},"metadata":{"type":"object","additionalProperties":{"type":"string"},"description":"Up to 16 key/value pairs merged onto every run this trigger fires,\nbeside the `trigger_id` and `trigger_fire_key` the platform stamps.\nYour own correlation ids belong here. The platform's keys\n(`trigger`, `trigger_id`, `trigger_fire_key`) are refused with a\n400 when the trigger is saved, since the stamp would win.\n"}},"required":["instruction"],"description":"The durable run a trigger starts when it fires.","title":"TriggerRunSpec"},"TriggerScheduleConfig":{"type":"object","properties":{"cron":{"type":"string","description":"A standard 5-field cron expression (minute hour day-of-month month day-of-week)."},"interval_seconds":{"type":"integer","minimum":60,"maximum":7776000,"description":"Fire every N seconds (minimum 60, maximum 7776000 = 90 days)."},"timezone":{"type":"string","description":"IANA time zone name applied to `cron`, such as `America/New_York`,\n`Europe/Berlin` or `Asia/Tokyo`. Any name in the IANA time zone\ndatabase is accepted; an unknown name is refused with a 400 that\nnames `schedule.timezone`. The cron fires at the same local\nwall-clock time on both sides of a daylight-saving change.\nDefaults to UTC.\n"}},"description":"The cadence of a schedule trigger. Set exactly one of `cron` or\n`interval_seconds`.\n","title":"TriggerScheduleConfig"},"TriggerWebhookConfigUserIdentitySource":{"type":"string","enum":["trigger","fire"],"description":"Who names the person a fired run acts for. Defaults to `trigger`.\n\n`trigger` uses `run.user_identity`, so every fire on\nthis trigger acts for the same person - or for nobody, when it is\nunset.\n\n`fire` takes the person from each delivery, as a\n`Speechify-User-Identity` header on the fire request. One trigger\nthen serves every person your connector watches, instead of one\ntrigger per person. `run.user_identity` must be empty when you\nchoose it: a trigger that both pins a person and reads one per\ndelivery would run silently as the pinned person whenever your\nconnector forgot the header, so the combination is refused with a\n`400` when the trigger is saved.\n\nA fire that names a person the trigger does not accept, and a fire\nthat names nobody on a `fire`-sourced trigger, are both refused\nwith a `400` rather than quietly running for the wrong person.\n","title":"TriggerWebhookConfigUserIdentitySource"},"TriggerWebhookConfig":{"type":"object","properties":{"payload_variable":{"type":"string","description":"The run-variable key the POST body is bound to. Defaults to `payload`."},"user_identity_source":{"$ref":"#/components/schemas/TriggerWebhookConfigUserIdentitySource","description":"Who names the person a fired run acts for. Defaults to `trigger`.\n\n`trigger` uses `run.user_identity`, so every fire on\nthis trigger acts for the same person - or for nobody, when it is\nunset.\n\n`fire` takes the person from each delivery, as a\n`Speechify-User-Identity` header on the fire request. One trigger\nthen serves every person your connector watches, instead of one\ntrigger per person. `run.user_identity` must be empty when you\nchoose it: a trigger that both pins a person and reads one per\ndelivery would run silently as the pinned person whenever your\nconnector forgot the header, so the combination is refused with a\n`400` when the trigger is saved.\n\nA fire that names a person the trigger does not accept, and a fire\nthat names nobody on a `fire`-sourced trigger, are both refused\nwith a `400` rather than quietly running for the wrong person.\n"}},"description":"The inbound-fire config of a webhook trigger.","title":"TriggerWebhookConfig"},"AgentTriggerLastFireStatus":{"type":"string","enum":["pending","dispatched","failed"],"description":"The outcome of the most recent fire: `dispatched` with a run,\n`failed` with the reason in `last_fire_error`, or `pending` while a\ntransient failure waits for its retry. Absent until the trigger has\nfired once. A `failed` trigger stays `failed` until a later fire\nsucceeds; `trigger.fire_failed` is delivered each time.\n","title":"AgentTriggerLastFireStatus"},"AgentTrigger":{"type":"object","properties":{"id":{"type":"string","pattern":"^trigger_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`trigger_<26 char Crockford base32>`)."},"agent_id":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"type":{"$ref":"#/components/schemas/AgentTriggerType"},"name":{"type":"string"},"enabled":{"type":"boolean"},"run":{"$ref":"#/components/schemas/TriggerRunSpec"},"schedule":{"$ref":"#/components/schemas/TriggerScheduleConfig"},"webhook":{"$ref":"#/components/schemas/TriggerWebhookConfig"},"next_fire_at":{"type":"string","format":"date-time","description":"When a schedule trigger next fires, as an absolute UTC instant\ncomputed in the schedule's `timezone`. Absent for a webhook or a\ndisabled schedule.\n"},"last_fired_at":{"type":"string","format":"date-time"},"last_fire_status":{"$ref":"#/components/schemas/AgentTriggerLastFireStatus","description":"The outcome of the most recent fire: `dispatched` with a run,\n`failed` with the reason in `last_fire_error`, or `pending` while a\ntransient failure waits for its retry. Absent until the trigger has\nfired once. A `failed` trigger stays `failed` until a later fire\nsucceeds; `trigger.fire_failed` is delivered each time.\n"},"last_fire_error":{"type":"string","description":"Why the most recent fire did not start a run: the error code a request\nwould have been answered with and what to do about it, for example\n`agent_publish_gate_required: the agent's current configuration has not\npassed the publish gate; publish it and the next fire runs`. Empty when\nthe last fire dispatched.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"fire_path":{"type":"string","description":"The inbound fire URL path for a webhook trigger; prepend the API\nhost. Absent for a schedule trigger.\n"},"secret":{"type":"string","description":"The plaintext fire token for a webhook trigger. Present in the\ncreate response ONLY and never again - store it securely.\n"},"secret_hint":{"type":"string","description":"A masked fragment of the webhook fire token, for display."}},"required":["id","agent_id","type","name","enabled","run","created_at","updated_at"],"description":"An automated start condition bound to a task agent - a `schedule`\n(cron/interval) or a `webhook` (an inbound fire URL) that starts a\ndurable run.\n","title":"AgentTrigger"},"ListAgentTriggersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"triggers":{"type":"array","items":{"$ref":"#/components/schemas/AgentTrigger"}}},"required":["next_cursor","has_more","triggers"],"description":"Payload for `GET /v1/agents/{agent_id}/triggers`. Cursor-paginated.\n","title":"ListAgentTriggersResponse"},"CreateAgentTriggerRequestType":{"type":"string","enum":["schedule","webhook"],"title":"CreateAgentTriggerRequestType"},"CreateAgentTriggerRequest":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/CreateAgentTriggerRequestType"},"name":{"type":"string"},"enabled":{"type":"boolean"},"run":{"$ref":"#/components/schemas/TriggerRunSpec"},"schedule":{"$ref":"#/components/schemas/TriggerScheduleConfig"},"webhook":{"$ref":"#/components/schemas/TriggerWebhookConfig"}},"required":["type","name","run"],"description":"Create a trigger. For a `schedule` trigger set `schedule`; for a\n`webhook` trigger set `webhook` (optional). `enabled` defaults to true.\n","title":"CreateAgentTriggerRequest"},"UpdateAgentTriggerRequest":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"run":{"$ref":"#/components/schemas/TriggerRunSpec"},"schedule":{"$ref":"#/components/schemas/TriggerScheduleConfig"},"webhook":{"$ref":"#/components/schemas/TriggerWebhookConfig"}},"description":"All fields optional (merge-patch): an omitted field is left unchanged.\n`type` is immutable.\n","title":"UpdateAgentTriggerRequest"},"AgentTriggerFireResponseStatus":{"type":"string","enum":["pending","dispatched","failed"],"description":"The fire's dispatch status.","title":"AgentTriggerFireResponseStatus"},"AgentTriggerFireResponse":{"type":"object","properties":{"run_id":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$","description":"The started durable run, present when dispatch succeeded."},"status":{"$ref":"#/components/schemas/AgentTriggerFireResponseStatus","description":"The fire's dispatch status."},"replayed":{"type":"boolean","description":"True when a duplicate delivery replayed the original fire."}},"required":["status"],"description":"Acknowledgement of an inbound webhook fire.","title":"AgentTriggerFireResponse"},"Skill":{"type":"object","properties":{"id":{"type":"string","pattern":"^skill_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"description":{"type":"string"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project this skill belongs to; null when it belongs to none."},"version":{"type":"integer","description":"The version this body came from."},"latest_version":{"type":"integer","description":"The highest version minted. Differs from `version` only when you asked for an older one."},"instructions":{"type":"string","description":"The procedure, rendered into the system prompt of every agent attached at this version."},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Tool definitions this skill contributes to an attached agent's toolbelt."},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"},"description":"Knowledge bases this procedure needs. Unlike tools these are not\ncontributed - the agent must already have them attached, and an\nattach naming one it lacks is refused.\n"},"variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Defaults for the `{{tokens}}` the instructions reference. They only\nfill keys nothing else set - the agent's own values, the flow's and\nthe session's all outrank them.\n"},"attached_agent_count":{"type":"integer","description":"How many agents hold this skill. Zero on list responses."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","project_id","version","latest_version","instructions","tool_ids","knowledge_base_ids","variables","attached_agent_count","created_at","updated_at"],"description":"A versioned procedure: an instruction block plus the tools and\nknowledge it needs, written once and attached to many agents. The body\nreturned is one version's - the latest, or the one `?version=` named.\n","title":"Skill"},"ListSkillsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"skills":{"type":"array","items":{"$ref":"#/components/schemas/Skill"}}},"required":["next_cursor","has_more","skills"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListSkillsResponse"},"CreateSkillRequest":{"type":"object","properties":{"name":{"type":"string","description":"1-128 characters of letters, digits, spaces, or `. _ -`; unique per workspace."},"description":{"type":"string","maxLength":1000},"instructions":{"type":"string","minLength":1,"maxLength":4000,"description":"The procedure. Bounded because it rides on every dispatched turn of\nevery agent that attaches it, so the limit is a per-turn token bill\nrather than a storage bound.\n"},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"variables":{"type":"object","additionalProperties":{"type":"string"},"description":"Default token values. Keys in the reserved `system__` namespace are refused."},"project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project to create the skill in; omit for the caller's default."}},"required":["name","instructions"],"title":"CreateSkillRequest"},"UpdateSkillRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"instructions":{"type":"string","minLength":1,"maxLength":4000},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"variables":{"type":"object","additionalProperties":{"type":"string"}}},"description":"Merge-patch. `name` and `description` edit in place; any of\n`instructions`, `tool_ids`, `knowledge_base_ids` or `variables` mints a\nnew version instead. A present list replaces rather than appends.\n","title":"UpdateSkillRequest"},"SkillVersion":{"type":"object","properties":{"version":{"type":"integer"},"summary":{"type":"string","description":"The first line of the body, bounded. Read the full body with `GET /v1/skills/{skill_id}?version=`."},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"created_at":{"type":"string","format":"date-time"}},"required":["version","summary","tool_ids","knowledge_base_ids","created_at"],"title":"SkillVersion"},"ListSkillVersionsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"versions":{"type":"array","items":{"$ref":"#/components/schemas/SkillVersion"}}},"required":["next_cursor","has_more","versions"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListSkillVersionsResponse"},"AgentSkill":{"type":"object","properties":{"skill_id":{"type":"string","pattern":"^skill_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"description":{"type":"string"},"version":{"type":"integer","description":"The version this agent is pinned to, and the one dispatch renders."},"latest_version":{"type":"integer","description":"The version the catalog holds now. Higher than `version` means an upgrade is available."},"enabled":{"type":"boolean","description":"A disabled skill keeps its pin but is not rendered or dispatched."},"instructions":{"type":"string"},"tool_ids":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_base_ids":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"variables":{"type":"object","additionalProperties":{"type":"string"}},"attached_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["skill_id","name","description","version","latest_version","enabled","instructions","tool_ids","knowledge_base_ids","variables","attached_at","updated_at"],"description":"One skill as it is attached to one agent, pinned to a version.","title":"AgentSkill"},"ListAgentSkillsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"skills":{"type":"array","items":{"$ref":"#/components/schemas/AgentSkill"}}},"required":["next_cursor","has_more","skills"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentSkillsResponse"},"AttachSkillRequest":{"type":"object","properties":{"version":{"type":"integer","minimum":1,"description":"Version to pin. Omit to pin whatever is latest at this moment."},"enabled":{"type":"boolean","description":"Defaults to true."}},"title":"AttachSkillRequest"},"UpdateAgentSkillRequest":{"type":"object","properties":{"version":{"type":"integer","minimum":1},"enabled":{"type":"boolean"}},"title":"UpdateAgentSkillRequest"},"Store":{"type":"object","properties":{"id":{"type":"string","pattern":"^store_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"description":{"type":"string"},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project this store belongs to; null when it belongs to none."},"created_at":{"type":"string","format":"date-time"},"used_bytes":{"type":"integer","format":"int64","description":"Bytes the store holds across its documents and files, kept in step with every write."},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","description","project_id","created_at","used_bytes","updated_at"],"description":"A durable document + asset store. Runs write to it through\ntheir store tools; hosted-API routes serve it; your own code reads and\nwrites it directly. Document bodies live in object storage, so a\ngrowing dataset costs nothing on the database.\n","title":"Store"},"ListStoresResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"stores":{"type":"array","items":{"$ref":"#/components/schemas/Store"}}},"required":["next_cursor","has_more","stores"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListStoresResponse"},"CreateStoreRequest":{"type":"object","properties":{"name":{"type":"string","description":"1-128 characters of letters, digits, spaces, or `. _ -`; unique among live stores."},"description":{"type":"string","maxLength":1000},"project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project to create the store in; omit for the caller's default."}},"required":["name"],"title":"CreateStoreRequest"},"UpdateStoreRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string","maxLength":1000}},"title":"UpdateStoreRequest"},"StoreCollection":{"type":"object","properties":{"name":{"type":"string"},"document_count":{"type":"integer","minimum":0}},"required":["name","document_count"],"title":"StoreCollection"},"ListStoreCollectionsResponse":{"type":"object","properties":{"collections":{"type":"array","items":{"$ref":"#/components/schemas/StoreCollection"}}},"required":["collections"],"title":"ListStoreCollectionsResponse"},"StoreDocumentSource":{"type":"object","properties":{"run_id":{"type":"string","pattern":"^arun_[0-9a-hjkmnp-tv-z]{26}$"},"step":{"type":"integer","minimum":1}},"required":["run_id"],"description":"The durable run (and its journal step) that wrote this version. Absent for a direct API write.","title":"StoreDocumentSource"},"StoreDocument":{"type":"object","properties":{"id":{"type":"string"},"collection":{"type":"string"},"version":{"type":"integer","minimum":1,"description":"Incremented on every write to this id."},"size_bytes":{"type":"integer","minimum":0},"source":{"$ref":"#/components/schemas/StoreDocumentSource"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The document body. Absent when the caller asked for index rows only."}},"required":["id","collection","version","size_bytes","created_at","updated_at"],"description":"One document: its index row plus, when bodies were requested, the JSON object itself.","title":"StoreDocument"},"QueryStoreDocumentsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"documents":{"type":"array","items":{"$ref":"#/components/schemas/StoreDocument"}}},"required":["next_cursor","has_more","documents"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"QueryStoreDocumentsResponse"},"WriteStoreDocumentRequest":{"type":"object","properties":{"id":{"type":"string","description":"On `createDocument`, the id to write at (letters, digits, `_ . - : ~ @ +`, at most 200);\nminted when absent. Ignored on `putDocument` / `updateDocument`, where the URL names it.\n"},"data":{"type":"object","additionalProperties":{"description":"Any type"},"description":"The document body (a JSON object, at most 256 KiB). On `updateDocument`, the fields to merge; a null removes a field."}},"required":["data"],"title":"WriteStoreDocumentRequest"},"StoreWhereClauseOp":{"type":"string","enum":["eq","ne","gt","gte","lt","lte","in"],"title":"StoreWhereClauseOp"},"StoreWhereClause":{"type":"object","properties":{"field":{"type":"string","description":"A top-level field name of the documents."},"op":{"$ref":"#/components/schemas/StoreWhereClauseOp"},"value":{"description":"A string, number, boolean or null; an array of those for `in`."}},"required":["field","op","value"],"title":"StoreWhereClause"},"StoreOrderByDirection":{"type":"string","enum":["asc","desc"],"description":"asc when omitted.","title":"StoreOrderByDirection"},"StoreOrderBy":{"type":"object","properties":{"field":{"type":"string"},"direction":{"$ref":"#/components/schemas/StoreOrderByDirection","description":"asc when omitted."}},"required":["field"],"title":"StoreOrderBy"},"StoreDocumentQuery":{"type":"object","properties":{"where":{"type":"array","items":{"$ref":"#/components/schemas/StoreWhereClause"},"description":"Filters, ANDed. Only top-level string / number / boolean / null fields are queryable."},"order_by":{"$ref":"#/components/schemas/StoreOrderBy"},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Page size, 50 when omitted."},"cursor":{"type":"string","description":"The `next_cursor` of the previous page; must belong to the same ordering."},"include_data":{"type":"boolean","description":"Set false to return index rows only; bodies are included when omitted."}},"title":"StoreDocumentQuery"},"StoreBatchWriteOp":{"type":"string","enum":["set","update","delete"],"title":"StoreBatchWriteOp"},"StoreBatchWrite":{"type":"object","properties":{"op":{"$ref":"#/components/schemas/StoreBatchWriteOp"},"id":{"type":"string","description":"The document id; minted for a `set` without one."},"data":{"type":"object","additionalProperties":{"description":"Any type"}}},"required":["op"],"title":"StoreBatchWrite"},"StoreBatchRequest":{"type":"object","properties":{"writes":{"type":"array","items":{"$ref":"#/components/schemas/StoreBatchWrite"}}},"required":["writes"],"title":"StoreBatchRequest"},"StoreBatchResultOp":{"type":"string","enum":["set","update","delete"],"title":"StoreBatchResultOp"},"StoreBatchResult":{"type":"object","properties":{"op":{"$ref":"#/components/schemas/StoreBatchResultOp"},"id":{"type":"string"},"document":{"$ref":"#/components/schemas/StoreDocument"}},"required":["op","id"],"title":"StoreBatchResult"},"StoreBatchResponse":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/StoreBatchResult"}}},"required":["results"],"title":"StoreBatchResponse"},"StoreAsset":{"type":"object","properties":{"id":{"type":"string","pattern":"^asset_[0-9a-hjkmnp-tv-z]{26}$"},"filename":{"type":"string"},"content_type":{"type":"string"},"size_bytes":{"type":"integer","minimum":0},"source":{"$ref":"#/components/schemas/StoreDocumentSource"},"created_at":{"type":"string","format":"date-time"},"bytes_path":{"type":"string","description":"The API path the bytes stream from (prepend the API host)."}},"required":["id","filename","content_type","size_bytes","created_at","bytes_path"],"title":"StoreAsset"},"ListStoreAssetsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"assets":{"type":"array","items":{"$ref":"#/components/schemas/StoreAsset"}}},"required":["next_cursor","has_more","assets"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListStoreAssetsResponse"},"ListAgentStoresResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"stores":{"type":"array","items":{"$ref":"#/components/schemas/Store"}}},"required":["next_cursor","has_more","stores"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListAgentStoresResponse"},"HostedApiAuthMode":{"type":"string","enum":["consumer_key","public"],"description":"`consumer_key`: every request presents a `ck_` bearer minted for this\nAPI. `public`: no credential; only read resolvers may be served, and\nthe per-IP limiter is the only bound.\n","title":"HostedApiAuthMode"},"HostedAPI":{"type":"object","properties":{"id":{"type":"string","pattern":"^api_[0-9a-hjkmnp-tv-z]{26}$"},"slug":{"type":"string"},"hostname":{"type":"string","description":"The public host, `.`; empty where no domain is configured."},"base_url":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"auth_mode":{"$ref":"#/components/schemas/HostedApiAuthMode","description":"`consumer_key`: every request presents a `ck_` bearer minted for this\nAPI. `public`: no credential; only read resolvers may be served, and\nthe per-IP limiter is the only bound.\n"},"cors_origins":{"type":"array","items":{"type":"string"},"description":"Browser origins allowed to call the API (`*` for any). Empty for server-to-server only."},"enabled":{"type":"boolean"},"daily_run_cap":{"type":"integer","minimum":1,"maximum":100000,"description":"Runs the API's run routes may start per UTC day; the spend ceiling behind a leaked consumer key."},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","slug","hostname","base_url","name","description","auth_mode","cors_origins","enabled","daily_run_cap","project_id","created_at","updated_at"],"description":"A customer-assembled API: a slug on the shared hosted-API\ndomain, routes answered by resolvers, and consumer keys. Build product;\nthe customer assembles it, Speechify hosts it.\n","title":"HostedAPI"},"ListHostedAPIsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"apis":{"type":"array","items":{"$ref":"#/components/schemas/HostedAPI"}}},"required":["next_cursor","has_more","apis"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListHostedAPIsResponse"},"CreateHostedApiRequestAuthMode":{"type":"string","enum":["consumer_key","public"],"description":"consumer_key when omitted.","title":"CreateHostedApiRequestAuthMode"},"CreateHostedAPIRequest":{"type":"object","properties":{"slug":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])?$","description":"3-40 lowercase letters, digits or hyphens; a DNS label, unique on the shared domain; immutable."},"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"auth_mode":{"$ref":"#/components/schemas/CreateHostedApiRequestAuthMode","description":"consumer_key when omitted."},"cors_origins":{"type":"array","items":{"type":"string"}},"daily_run_cap":{"type":"integer","minimum":1,"maximum":100000,"description":"Runs the API may start per UTC day through its run routes; 1000 when omitted."},"project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"}},"required":["slug","name"],"title":"CreateHostedAPIRequest"},"UpdateHostedApiRequestAuthMode":{"type":"string","enum":["consumer_key","public"],"title":"UpdateHostedApiRequestAuthMode"},"UpdateHostedAPIRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"auth_mode":{"$ref":"#/components/schemas/UpdateHostedApiRequestAuthMode"},"cors_origins":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean","description":"A paused API answers 503 to every consumer request."},"daily_run_cap":{"type":"integer","minimum":1,"maximum":100000}},"title":"UpdateHostedAPIRequest"},"HostedApiRouteMethod":{"type":"string","enum":["GET","POST"],"title":"HostedApiRouteMethod"},"HostedApiResolverType":{"type":"string","enum":["store_query","store_document","run_latest","run"],"title":"HostedApiResolverType"},"HostedApiResolverWhereItemsOp":{"type":"string","enum":["eq","ne","gt","gte","lt","lte","in"],"title":"HostedApiResolverWhereItemsOp"},"HostedApiResolverWhereItems":{"type":"object","properties":{"field":{"type":"string"},"op":{"$ref":"#/components/schemas/HostedApiResolverWhereItemsOp"},"value":{"description":"A literal, or a `{{query.x}}` / `{{path.x}}` / `{{body.x}}` template; a clause whose template is absent from the request is skipped."}},"required":["field","op","value"],"title":"HostedApiResolverWhereItems"},"HostedApiResolverOrderByDirection":{"type":"string","enum":["asc","desc"],"title":"HostedApiResolverOrderByDirection"},"HostedApiResolverOrderBy":{"type":"object","properties":{"field":{"type":"string"},"direction":{"$ref":"#/components/schemas/HostedApiResolverOrderByDirection"}},"required":["field"],"title":"HostedApiResolverOrderBy"},"HostedAPIResolver":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/HostedApiResolverType"},"store_id":{"type":"string","pattern":"^store_[0-9a-hjkmnp-tv-z]{26}$"},"collection":{"type":"string"},"document_id":{"type":"string","description":"A literal id or a `{{path.x}}` / `{{query.x}}` template."},"where":{"type":"array","items":{"$ref":"#/components/schemas/HostedApiResolverWhereItems"}},"order_by":{"$ref":"#/components/schemas/HostedApiResolverOrderBy"},"limit":{"type":"integer","minimum":0,"maximum":100,"description":"Default page size; the consumer's `limit` query parameter overrides it."},"trigger_id":{"type":"string","pattern":"^trigger_[0-9a-hjkmnp-tv-z]{26}$"},"wait_seconds":{"type":"integer","minimum":0,"maximum":30,"description":"How long a `run` route waits for the run before answering 202 (default 20; 0 answers 202 at once)."}},"required":["type"],"description":"What answers a route. `type` selects the fields that apply:\n`store_query` (store_id, collection, where, order_by, limit),\n`store_document` (store_id, collection, document_id),\n`run_latest` (trigger_id of a schedule trigger),\n`run` (trigger_id of a webhook trigger, wait_seconds).\n","title":"HostedAPIResolver"},"HostedAPIRoute":{"type":"object","properties":{"id":{"type":"string","pattern":"^route_[0-9a-hjkmnp-tv-z]{26}$"},"api_id":{"type":"string","pattern":"^api_[0-9a-hjkmnp-tv-z]{26}$"},"method":{"$ref":"#/components/schemas/HostedApiRouteMethod"},"path":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"resolver":{"$ref":"#/components/schemas/HostedAPIResolver"},"response_schema":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Optional JSON Schema of the response body, rendered into the OpenAPI document."},"cache_ttl_seconds":{"type":"integer","minimum":0,"maximum":3600,"description":"Cache-Control max-age on GET responses; 0 disables caching."},"enabled":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","api_id","method","path","name","description","resolver","cache_ttl_seconds","enabled","created_at","updated_at"],"title":"HostedAPIRoute"},"ListHostedAPIRoutesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"routes":{"type":"array","items":{"$ref":"#/components/schemas/HostedAPIRoute"}}},"required":["next_cursor","has_more","routes"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListHostedAPIRoutesResponse"},"CreateHostedApiRouteRequestMethod":{"type":"string","enum":["GET","POST"],"title":"CreateHostedApiRouteRequestMethod"},"CreateHostedAPIRouteRequest":{"type":"object","properties":{"method":{"$ref":"#/components/schemas/CreateHostedApiRouteRequestMethod"},"path":{"type":"string","description":"Lowercase segments of letters, digits, `. _ -` or a `{param}`; `/openapi.json` and `/_runs` are reserved."},"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"resolver":{"$ref":"#/components/schemas/HostedAPIResolver"},"response_schema":{"type":"object","additionalProperties":{"description":"Any type"}},"cache_ttl_seconds":{"type":"integer","minimum":0,"maximum":3600},"enabled":{"type":"boolean","description":"Enabled when omitted."}},"required":["method","path","resolver"],"title":"CreateHostedAPIRouteRequest"},"UpdateHostedApiRouteRequestMethod":{"type":"string","enum":["GET","POST"],"title":"UpdateHostedApiRouteRequestMethod"},"UpdateHostedAPIRouteRequest":{"type":"object","properties":{"method":{"$ref":"#/components/schemas/UpdateHostedApiRouteRequestMethod"},"path":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","maxLength":1000},"resolver":{"$ref":"#/components/schemas/HostedAPIResolver"},"response_schema":{"type":["object","null"],"additionalProperties":{"description":"Any type"}},"cache_ttl_seconds":{"type":"integer","minimum":0,"maximum":3600},"enabled":{"type":"boolean"}},"title":"UpdateHostedAPIRouteRequest"},"HostedAPIKey":{"type":"object","properties":{"id":{"type":"string","pattern":"^ckey_[0-9a-hjkmnp-tv-z]{26}$"},"api_id":{"type":"string","pattern":"^api_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"key_hint":{"type":"string","description":"The masked secret shown on every read."},"rate_per_minute":{"type":"integer","minimum":0,"description":"Requests per minute this key may make; 0 is unlimited."},"secret":{"type":"string","description":"The plaintext key, present on the create response only."},"revoked_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"required":["id","api_id","name","key_hint","rate_per_minute","created_at"],"title":"HostedAPIKey"},"ListHostedAPIKeysResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"keys":{"type":"array","items":{"$ref":"#/components/schemas/HostedAPIKey"}}},"required":["next_cursor","has_more","keys"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListHostedAPIKeysResponse"},"CreateHostedAPIKeyRequest":{"type":"object","properties":{"name":{"type":"string"},"rate_per_minute":{"type":"integer","minimum":0,"maximum":100000,"description":"Requests per minute; 60 when omitted, 0 for unlimited."}},"required":["name"],"title":"CreateHostedAPIKeyRequest"},"ContactIdentifierKind":{"type":"string","enum":["external_id","phone","email"],"description":"The namespace the value lives in. Values of different kinds never\ndenote the same person by themselves, whatever they look like.\n","title":"ContactIdentifierKind"},"ContactIdentifierAssertedBy":{"type":"string","enum":["api","telephony","unverified"],"description":"Who vouched for this identifier. Server-assigned, never accepted\nfrom a client.\n\n- `api` - your authenticated backend, via this endpoint or a\n server-to-server session. May be linked to an existing contact.\n- `telephony` - carrier ANI on an inbound call. Trustworthy as a\n fact about the call and silent about which of your users it is,\n so it creates a phone-only contact until you say otherwise.\n- `unverified` - a self-asserted claim from a public or embedded\n browser session. Nobody stands behind it, so it never joins a\n verified contact.\n","title":"ContactIdentifierAssertedBy"},"ContactIdentifier":{"type":"object","properties":{"id":{"type":"string","pattern":"^ident_[0-9a-hjkmnp-tv-z]{26}$"},"contact_id":{"type":"string","pattern":"^contact_[0-9a-hjkmnp-tv-z]{26}$"},"kind":{"$ref":"#/components/schemas/ContactIdentifierKind","description":"The namespace the value lives in. Values of different kinds never\ndenote the same person by themselves, whatever they look like.\n"},"value":{"type":"string","description":"The handle itself - an application user id, an E.164 number, an email address."},"asserted_by":{"$ref":"#/components/schemas/ContactIdentifierAssertedBy","description":"Who vouched for this identifier. Server-assigned, never accepted\nfrom a client.\n\n- `api` - your authenticated backend, via this endpoint or a\n server-to-server session. May be linked to an existing contact.\n- `telephony` - carrier ANI on an inbound call. Trustworthy as a\n fact about the call and silent about which of your users it is,\n so it creates a phone-only contact until you say otherwise.\n- `unverified` - a self-asserted claim from a public or embedded\n browser session. Nobody stands behind it, so it never joins a\n verified contact.\n"},"verified_at":{"type":"string","format":"date-time","description":"When an authenticated party asserted this identifier. Absent\nexactly when `asserted_by` is `unverified`, which is what lets you\ntell a vouched-for handle from a self-claim without re-deriving it\nfrom the value's shape.\n"},"created_at":{"type":"string","format":"date-time"}},"required":["id","contact_id","kind","value","asserted_by","created_at"],"description":"One typed handle attached to a contact, with the provenance that\ndecides what may be joined to what. Two identifiers carrying the same\n`value` at different `asserted_by` levels are two different\nidentifiers on two different contacts - collapsing them is exactly the\ncross-caller disclosure the trust model exists to prevent.\n","title":"ContactIdentifier"},"Contact":{"type":"object","properties":{"id":{"type":"string","pattern":"^contact_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire identifier (`contact_<26 char Crockford base32>`).\nURL paths accept only this prefixed form; bare UUIDs are rejected\nwith 404.\n"},"tenant_id":{"type":"string","pattern":"^ws_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace that owns this contact."},"display_name":{"type":"string","description":"Operator-editable name. Absent when nobody has named this person\nand no resolver supplied one.\n"},"metadata":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Customer-controlled JSON blob."},"identifiers":{"type":"array","items":{"$ref":"#/components/schemas/ContactIdentifier"},"description":"The contact's live identifiers. Always present; empty only for a\ncontact whose every link has been detached.\n"},"agent_ids":{"type":"array","items":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The agents that have reached this person, each holding its own\nview of them under `/v1/agents/callers`. Always present; empty for\na contact nobody has spoken to yet.\n"},"first_seen_at":{"type":"string","format":"date-time","description":"Earliest sighting across every channel and agent. A merge widens\nthis to span both records, because the window belongs to the\nperson rather than to whichever record saw them first.\n"},"last_seen_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","tenant_id","metadata","identifiers","agent_ids","first_seen_at","last_seen_at","created_at","updated_at"],"description":"A workspace-scoped person. Identity lives in the `identifiers` set,\nnot in this row: a contact is whoever those handles denote, and each\nhandle records who vouched for it.\n","title":"Contact"},"ListContactsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}},"required":["next_cursor","has_more","contacts"],"description":"Payload for GET /v1/contacts.","title":"ListContactsResponse"},"UpdateContactRequest":{"type":"object","properties":{"display_name":{"type":"string","description":"Operator-editable display name. Empty string clears the column; an explicit `null` is rejected with a 400."},"metadata":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Replacement metadata JSONB. An explicit `null` is rejected with a 400."}},"description":"PATCH payload. Omitted fields are unchanged; present fields overwrite.\nAn empty `display_name` clears the column; `metadata` replaces the\nstored blob in full when supplied.\n\nNeither field is nullable. Clearing is spelled `\"\"` for\n`display_name` and `{}` for `metadata`; an explicit `null` is\nrejected with a 400 naming the field, never accepted as a no-op.\n","title":"UpdateContactRequest"},"DeleteContactResponse":{"type":"object","properties":{"contact_purged":{"type":"integer","description":"1 when this call tombstoned the contact, 0 when it was already gone."},"identifiers_purged":{"type":"integer","description":"Identifiers tombstoned. They stop resolving immediately, so a\nfuture call or session on that channel mints a fresh contact.\n"},"callers_purged":{"type":"integer","description":"Per-agent caller records tombstoned."},"memories_purged":{"type":"integer","description":"Memory rows erased, across every agent and channel - reached by\ncontact, by caller record, and by the stored caller identity, so a\nrow is not missed because of which key it happened to be written\nunder.\n"},"runs_erased":{"type":"integer","description":"Durable runs whose content was scrubbed: the instruction, the\nvariables, the output and every journalled step, which together\ncarry the person verbatim. The run row itself stays, with its\nstatus, timings and usage - what the workspace was billed for is\nnot the person's to erase.\n"}},"required":["contact_purged","identifiers_purged","callers_purged","memories_purged"],"description":"The audit tally of a contact purge. A privacy operator answering an\nerasure request gets the evidence in the response, without re-querying\na corpus they just asked us to stop returning. All zeros means the\ncontact was already purged.\n","title":"DeleteContactResponse"},"AttachContactIdentifierRequestKind":{"type":"string","enum":["external_id","phone","email"],"description":"The namespace this value lives in.","title":"AttachContactIdentifierRequestKind"},"AttachContactIdentifierRequest":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/AttachContactIdentifierRequestKind","description":"The namespace this value lives in."},"value":{"type":"string","minLength":1,"maxLength":512,"description":"The handle - an application user id, an E.164 number, an email address."}},"required":["kind","value"],"description":"The customer's assertion that this handle denotes this person. It\ncarries the handle and nothing else: `asserted_by` and `verified_at`\nare stamped by the server from the fact that the request\nauthenticated, and are not accepted here.\n","title":"AttachContactIdentifierRequest"},"MergeContactsRequest":{"type":"object","properties":{"from_contact_id":{"type":"string","description":"The contact to fold into the one in the URL. It is tombstoned, and\neverything pointing at it is re-pointed at the survivor. Must\ndiffer from the contact in the URL.\n"}},"required":["from_contact_id"],"description":"Names the contact being merged AWAY; the survivor is the one in the URL.","title":"MergeContactsRequest"},"MergeContactsResponseMerged":{"type":"object","properties":{"identifiers":{"type":"integer","description":"Identifiers re-pointed at the survivor."},"callers":{"type":"integer","description":"Per-agent caller projections re-pointed at the survivor."},"conversations":{"type":"integer","description":"Conversations re-pointed at the survivor."},"memories":{"type":"integer","description":"Memory rows re-pointed at the survivor."}},"required":["identifiers","callers","conversations","memories"],"title":"MergeContactsResponseMerged"},"MergeContactsResponse":{"type":"object","properties":{"contact":{"$ref":"#/components/schemas/Contact"},"merged":{"$ref":"#/components/schemas/MergeContactsResponseMerged"}},"required":["contact","merged"],"description":"The surviving contact plus the audit tally of what moved onto it, so\nan operator reviewing a merge sees the blast radius without\nre-querying. A retry reports zeros - that is how a client tells\n\"already applied\" from \"nothing matched\".\n","title":"MergeContactsResponse"},"BillingEntitlementsMaxLlmModelClass":{"type":"string","enum":["managed","standard","premium"],"description":"Highest LLM model access class an agent on this plan may select.\n`managed`: Speechify-served models, included on every plan.\n`standard`: low-cost third-party models (e.g. GPT-4.1 Mini).\n`premium`: flagship third-party models (e.g. GPT-4.1).\n","title":"BillingEntitlementsMaxLlmModelClass"},"BillingEntitlements":{"type":"object","properties":{"phone_number_quota":{"type":"integer","description":"Speechify-managed (purchased) phone numbers allowed."},"concurrent_call_cap":{"type":"integer","description":"Simultaneously-active voice calls allowed."},"max_members":{"type":"integer","description":"Members allowed in a single workspace (owner + invitees)."},"max_call_duration_seconds":{"type":"integer","description":"Per-call wall-clock ceiling, in seconds."},"max_service_accounts":{"type":"integer","description":"Active service accounts allowed. 0 means service accounts are not\nincluded in the plan (creating one returns 402\nservice_accounts_not_in_plan); existing accounts keep working.\n"},"tts_requests_per_second":{"type":"integer","description":"Sustained requests per second across the audio (TTS) endpoints."},"tts_concurrency":{"type":"integer","description":"Simultaneous in-flight requests across the audio (TTS) endpoints."},"agent_requests_per_second":{"type":"integer","description":"Sustained requests per second across the agents API."},"agent_concurrency":{"type":"integer","description":"Simultaneous in-flight requests across the agents API."},"max_llm_model_class":{"$ref":"#/components/schemas/BillingEntitlementsMaxLlmModelClass","description":"Highest LLM model access class an agent on this plan may select.\n`managed`: Speechify-served models, included on every plan.\n`standard`: low-cost third-party models (e.g. GPT-4.1 Mini).\n`premium`: flagship third-party models (e.g. GPT-4.1).\n"},"can_create_workspaces":{"type":"boolean","description":"Whether the workspace may create ADDITIONAL workspaces."},"topup_allowed":{"type":"boolean","description":"Whether prepaid funds may be added beyond the monthly grant."},"voice_cloning":{"type":"boolean","description":"Whether voice cloning is available."},"batch_calls":{"type":"boolean","description":"Whether batch outbound calls are available."},"custom_llm_endpoint":{"type":"boolean","description":"Whether agents may use a bring-your-own custom LLM endpoint."},"waymark_access":{"type":"boolean","description":"Whether Waymark API access is available."},"text_agent_access":{"type":"boolean","description":"Whether the workspace may create and run agents that serve the text\nchannel. When false, enabling the text channel or opening a text\nconversation returns 402 text_channel_not_in_plan.\n"},"durable_runs_access":{"type":"boolean","description":"Whether the workspace may start and read durable async agent runs,\nincluding the approvals they raise and team runs. When false, those\nendpoints return 402 durable_runs_not_in_plan.\n"},"max_run_turns":{"type":"integer","minimum":1,"maximum":50,"description":"The per-run turn ceiling (one turn is one plan-act-observe cycle).\nA run or a trigger fire asking for more is clamped to it, and the\nrun echoes the budget it got in `input.max_turns`; plan against\nthis value.\n"},"slack_channel_access":{"type":"boolean","description":"Whether the workspace may connect a Slack front door. Off on every\nplan; granted per workspace. A Slack turn runs on the async-turn\nruntime and meters as a text message, so `text_agent_access` is\nrequired alongside it.\n"},"hosted_apis_access":{"type":"boolean","description":"Whether stores and hosted APIs are available. Off on every plan; granted per workspace."},"max_store_documents":{"type":"integer","description":"Documents one store may hold; 0 means uncapped."},"max_store_bytes":{"type":"integer","format":"int64","description":"Bytes one store may hold across its documents and files; 0 means uncapped."},"skills_access":{"type":"boolean","description":"Whether the skill catalog and per-agent attachment surface are available."},"max_skills":{"type":"integer","description":"Skills one workspace may hold; 0 means uncapped."},"max_skills_per_agent":{"type":"integer","description":"Skills one agent may attach; 0 means uncapped. Not only an abuse\nceiling: every enabled skill renders its instructions into the\nagent's system prompt, so this is what bounds prompt growth on a\nlatency-sensitive turn.\n"},"enterprise_sso":{"type":"boolean","description":"Whether enterprise SAML/OIDC SSO is available."},"phone_relay":{"type":"boolean","description":"Whether Agent Phone (a number whose brain is your webhook) is available."},"phone_sms":{"type":"boolean","description":"Whether SMS on Agent Phone numbers is available (also requires 10DLC)."},"zero_data_retention":{"type":"boolean","description":"Whether zero data retention is enabled for this workspace on the\nText to Speech API. Read-only: Speechify sets it against a signed\nagreement, never self-serve, and that agreement defines the scope.\n"},"voice_cloning_consent_exempt":{"type":"boolean","description":"Whether `POST /v1/voices` accepts a clone from this workspace\nwithout a consent challenge and verification recording, because the\nworkspace captures and holds speaker consent under its own\nagreement with Speechify. Read-only: Speechify sets it against that\nagreement, never self-serve. It does not grant cloning - the\n`voice_cloning` entitlement still applies.\n"},"max_manual_top_up_usd":{"type":"integer","description":"Largest single manual prepaid top-up accepted, in whole US dollars.\n0 means adding prepaid credit is not included in the plan. This is\nthe enforced amount, so a top-up of exactly this value succeeds; a\nclient may offer it directly without applying further bounds.\n"},"max_auto_recharge_charge_usd":{"type":"integer","description":"Largest single automatic recharge, in whole US dollars. 0 means\nauto-recharge is not included in the plan.\n"}},"required":["phone_number_quota","concurrent_call_cap","max_members","max_call_duration_seconds","max_service_accounts","tts_requests_per_second","tts_concurrency","agent_requests_per_second","agent_concurrency","max_llm_model_class","can_create_workspaces","topup_allowed","voice_cloning","batch_calls","custom_llm_endpoint","waymark_access","text_agent_access","durable_runs_access","max_run_turns","slack_channel_access","hosted_apis_access","max_store_documents","max_store_bytes","skills_access","max_skills","max_skills_per_agent","enterprise_sso","phone_relay","phone_sms","zero_data_retention","voice_cloning_consent_exempt","max_manual_top_up_usd","max_auto_recharge_charge_usd"],"description":"Customer-facing per-tier limits + feature flags, including the API\nrate limits the plan enforces. Numeric fields are caps; booleans gate\nfeatures.\n","title":"BillingEntitlements"},"EntitlementsResponse":{"type":"object","properties":{"catalog":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/BillingEntitlements"},"description":"Per-tier floor entitlements, keyed by tier string."},"current":{"$ref":"#/components/schemas/BillingEntitlements"}},"required":["catalog","current"],"description":"`catalog` is every sellable tier's FLOOR entitlements, keyed by tier\n(`free`/`starter`/`pro`/`scale`/`enterprise`) for the upgrade cards.\n`current` is the caller's workspace RESOLVED entitlements (tier floor\ncomposed with its per-tenant override), so a custom/Enterprise customer\nsees their actual caps - not just the tier floor.\n","title":"EntitlementsResponse"},"ContactResolver":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"The HTTPS endpoint Speechify POSTs the identifier to."},"disabled":{"type":"boolean","description":"When true the endpoint is kept but never called, which is how you\npark an integration without losing its secret.\n"},"secret":{"type":"string","description":"The HMAC signing key, present ONLY in the response that mints it\n(first configuration, or a rotation). Verify\n`Speechify-Signature` with it exactly as you would a webhook.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["url","disabled","created_at","updated_at"],"description":"The workspace's contact-resolver configuration: the endpoint asked to\nidentify an inbound caller at session start.\n","title":"ContactResolver"},"PutContactResolverRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"HTTPS endpoint that answers `{external_id, display_name?,\nvariables?}`. Must resolve to a public address - loopback,\nprivate-range and cloud-metadata hosts are rejected.\n"},"disabled":{"type":"boolean","default":false,"description":"Park the integration without deleting it. Defaults to false."}},"required":["url"],"description":"Sets the workspace's single resolver. Re-sending the same body is a\nno-op; the signing secret is minted on first configuration only.\n","title":"PutContactResolverRequest"},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","pattern":"^whe_[0-9a-hjkmnp-tv-z]{26}$","description":"Prefixed wire id (`whe_<26 char Crockford base32>`)."},"url":{"type":"string","format":"uri","description":"HTTPS destination Speechify POSTs signed events to."},"project_id":{"type":["string","null"],"pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project whose events this endpoint receives (prefixed\nexternal id). Null means workspace-wide - it receives every\nproject's events. Endpoints have no Default project.\n\nAn event is routed by the project frozen on the row that produced\nit: a conversation's, an API key's, a run's (captured when the run\nwas created, so it does not move if the agent later does). The\nworkspace-level events (`batch_call.*`, `workspace.spend_budget.*`,\n`trigger.fire_failed`) belong to no project and reach workspace-wide\nendpoints only, so a scoped endpoint subscribed to those alone is\nrefused with a 400 naming `enabled_events` rather than accepted\nand never delivered to. A scoped endpoint records no delivery for\nanother project's events, so read its deliveries list next to the\nproject's own runs or conversations list.\n"},"enabled_events":{"type":"array","items":{"type":"string"},"description":"The events this endpoint receives: a list of catalog event names\n(see `WebhookEventType`) or `[\"*\"]` for every event, current and\nfuture.\n"},"include":{"type":"array","items":{"type":"string"},"description":"Per-event payload shaping. Deliveries are lean by default:\n`data.object` carries only the resource GET snapshot. List heavy\ncollections here to have them appended under the event's `data`\nalongside `object`, so receivers behind hard request-size caps stay\nlean unless they opt in. Recognised keys (conversation events only):\n`messages` (the full transcript) and `evaluations`. Empty = lean.\n"},"api_version":{"type":"string","format":"date","description":"The dated payload shape this endpoint receives (`YYYY-MM-DD`), the\nsame versioning vocabulary the REST API uses. Every delivery is\nrendered back to this version and carries it in the\n`Speechify-Version` header and the payload's top-level `version`\nfield. Defaults to your workspace's current version at creation;\nchange it to opt into a newer shape.\n"},"description":{"type":["string","null"],"description":"Optional human-readable label for the endpoint."},"disabled":{"type":"boolean","description":"When true, Speechify stops delivering to this endpoint."},"secret":{"type":"string","description":"The HMAC-SHA256 signing secret (`whsec_\u2026`) used to verify the\n`Speechify-Signature` header. Returned ONLY when the endpoint is\ncreated or its secret is rotated \u2014 it is never shown again.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","url","enabled_events","include","api_version","disabled","created_at","updated_at"],"description":"A workspace webhook endpoint: a destination URL, the events it\nsubscribes to, and a server-minted HMAC signing secret. `secret` is\nreturned ONLY in the create and rotate-secret responses \u2014 store it\nthen; every other read omits it.\n\nAn endpoint is either scoped to one project (`project_id`) or\nworkspace-wide, and a workspace-wide endpoint receives events from\nevery project.\n","title":"WebhookEndpoint"},"ListWebhookEndpointsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"endpoints":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}},"required":["next_cursor","has_more","endpoints"],"description":"Payload for GET /v1/webhooks/endpoints.","title":"ListWebhookEndpointsResponse"},"CreateWebhookEndpointRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"HTTPS destination for event deliveries. Must be a publicly\nreachable host: loopback, private, link-local, and cloud-metadata\naddresses (and reserved hostnames like `localhost`) are rejected.\n"},"project_id":{"type":["string","null"],"description":"Optionally scope the endpoint to one project (prefixed\n`proj_...` id): a scoped endpoint receives only that project's\nevents. Omit (or null) for workspace-wide - it receives every\nproject's events. An unknown id returns 404 project_not_found.\nA project-pinned API key creates into its own project and\ncannot name the workspace-wide tier.\n"},"enabled_events":{"type":"array","items":{"type":"string"},"description":"Catalog event names to subscribe to, or `[\"*\"]` for all events."},"include":{"type":"array","items":{"type":"string"},"description":"Optional payload-shaping keys (see `WebhookEndpoint.include`):\n`messages`, `evaluations`. Omit for the lean default.\n"},"api_version":{"type":"string","format":"date","description":"Optionally pin the endpoint's payload shape to a dated version\n(`YYYY-MM-DD`, see `WebhookEndpoint.api_version`). Omit to use the\nworkspace's current version. An unknown version is rejected.\n"},"description":{"type":["string","null"]}},"required":["url","enabled_events"],"title":"CreateWebhookEndpointRequest"},"UpdateWebhookEndpointRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"HTTPS destination for event deliveries. Must be a publicly\nreachable host: loopback, private, link-local, and cloud-metadata\naddresses (and reserved hostnames like `localhost`) are rejected.\n"},"project_id":{"type":["string","null"],"description":"Re-scope the endpoint: a `proj_...` id narrows it to that\nproject's events, an explicit null makes it workspace-wide\n(every project's events), omitted leaves it unchanged. The\nsigning secret and delivery history are untouched, so\nre-scoping never requires redeploying your receiver. An\nunknown id returns 404 project_not_found. A project-pinned API\nkey may only scope an endpoint to its own project.\n"},"enabled_events":{"type":"array","items":{"type":"string"}},"include":{"type":"array","items":{"type":"string"},"description":"Payload-shaping keys (see `WebhookEndpoint.include`). Send `[]` to\nclear back to the lean default.\n"},"api_version":{"type":"string","format":"date","description":"Opt the endpoint into a different (typically newer) payload shape\n(`YYYY-MM-DD`, see `WebhookEndpoint.api_version`). Omit to leave it\nunchanged. An unknown version is rejected.\n"},"description":{"type":["string","null"]},"disabled":{"type":"boolean"}},"description":"Partial update; omitted fields are left unchanged.","title":"UpdateWebhookEndpointRequest"},"WebhookEndpointDelivery":{"type":"object","properties":{"id":{"type":"string","description":"Prefixed delivery id (`whd_<26 char Crockford base32>`)."},"webhook_endpoint_id":{"type":"string","description":"The endpoint this delivery targeted (`whe_\u2026`)."},"event":{"type":"string","description":"The event type delivered (see `WebhookEventType`)."},"url":{"type":"string","description":"The destination URL at delivery time."},"status":{"$ref":"#/components/schemas/WebhookDeliveryStatus"},"attempt_count":{"type":"integer"},"last_attempt_at":{"type":"string","format":"date-time"},"last_status_code":{"type":"integer"},"last_error":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"request_body":{"type":"string","description":"The verbatim payload the `Speechify-Signature` HMAC was computed over\n(as `.`).\n"},"request_headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Headers sent, including `Speechify-Signature` / `Speechify-Event` / `Speechify-Delivery-Id`."},"last_response_body":{"type":"string","description":"Your server's response body on the most recent attempt (truncated to 8 KiB)."},"last_response_headers":{"type":"object","additionalProperties":{"type":"string"}}},"required":["id","webhook_endpoint_id","event","url","status","attempt_count","created_at"],"description":"One row of an endpoint's delivery log. One row per (endpoint, event,\nresource); updated in place across retry attempts. Each row records the\nexact request payload + signed headers Speechify sent and the response\nyour server returned, so you can verify the signature and debug failures.\n","title":"WebhookEndpointDelivery"},"ListWebhookEndpointDeliveriesResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpointDelivery"}}},"required":["next_cursor","has_more","deliveries"],"description":"Payload for GET /v1/webhooks/endpoints/{webhook_endpoint_id}/deliveries.","title":"ListWebhookEndpointDeliveriesResponse"},"Project":{"type":"object","properties":{"archived_at":{"type":["string","null"],"format":"date-time","description":"When the project was archived; `null` while it is live. While\nset, nothing new starts or bills inside the project and every\nsuch attempt answers `409 project_archived`.\n\nAbsent rather than null while a console runs ahead of an API\nthat predates archiving, which is why it is not required:\nread absent and null alike.\n"},"purged_at":{"type":"string","format":"date-time","description":"When this project was purged, present only on the rows returned\nby `include_purged=true`. A purged project is kept for 30 days\nfrom this stamp and permanently deleted after that, so the\nrestore deadline is this value plus the window; it is absent on\nevery project that still exists. Restore it with\n`POST /v1/projects/{project_id}/restore`.\n"},"max_concurrent_calls":{"type":"integer","minimum":1,"description":"The most voice-agent calls this project may have active at\nonce, present only when set. Checked after the workspace's own\nactive-call cap on every call start (web session, outbound\ncall, batch dial, inbound SIP), keyed on the project the\ncall's agent lives in: a call over the ceiling is refused with\nthe same `429 concurrency_limit_reached` the workspace cap\nanswers (an inbound caller hears the busy message), while\nsibling projects keep their headroom. Never higher than the\nworkspace's cap: a project can narrow the workspace's\ncapacity, not raise it.\n"},"max_concurrent_runs":{"type":"integer","minimum":1,"description":"The most durable agent runs this project may have queued or\nrunning at once, present only when set. Checked after the\nworkspace's own run ceiling on every run start (a request, a\nteam run, or an unattended trigger fire), keyed on the project\nthe run is ATTRIBUTED to - its agent's project, captured at\ncreation - so a workspace-wide key starting a run in this\nproject counts against it. A run over the ceiling is refused\nwith the same `429 concurrency_limit_reached` the workspace\nceiling answers, carrying `Retry-After`, while sibling projects\nkeep their headroom. A run parked on a human approval or on its\nchildren holds no slot and does not count. Never higher than the\nworkspace's own run ceiling: a project can narrow the\nworkspace's capacity, not raise it.\n"},"max_requests_per_minute":{"type":"integer","minimum":1,"description":"The most API requests per minute credentials pinned to this\nproject may make across every surface, present only when set.\nChecked after the workspace's own request-rate limit, in one\nbucket per project: a request over the ceiling is refused with\nthe same `429 rate_limited` the workspace limit answers, while\nother projects and unpinned credentials are untouched. Never\nhigher than the workspace's widest per-surface rate over a\nminute: a project can narrow the workspace's capacity, not\nraise it. Console sessions and unpinned keys carry no project\nand are never subject to it.\n"},"monthly_budget":{"type":"number","format":"double","maximum":1000000000,"exclusiveMinimum":0,"description":"The project's monthly spend limit in US dollars, present only\nwhen one is set. New billable work attributed to this project is\nrefused with the coded `402 project_spend_limit_exceeded` once\n`monthly_spend` reaches it; the limit resets at the calendar-month\nboundary (UTC).\n\nSpend is attributed the same way it is billed: work from a\nproject-pinned API key counts against that key's project, and a\nvoice-agent conversation counts against its agent's project. The\nimplicit Default project cannot carry a limit \u2014 it has no project\nrecord \u2014 so spend there is bounded by the workspace's\n`monthly_budget` instead.\n"},"monthly_spend":{"type":"number","format":"double","minimum":0,"description":"The project's billed month-to-date spend in US dollars, present\nwhenever the billing plane answered - regardless of whether a\nspend limit is set.\n"},"id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"Workspace-scoped project identifier (prefixed external id)."},"name":{"type":"string","description":"Human-readable label, unique per workspace (case-insensitive).\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","created_at","updated_at"],"description":"A workspace project: resource grouping that a member grant or a\ncredential pin can also scope. Resources\n(agents, knowledge bases, phone numbers, tools, audio) reference a\nproject by id; a resource with no `project_id` lives in the\nimplicit Default project. A project can carry member grants, which\nscope a granted member to the projects they hold, and the invoice\nstays workspace-level with no billing split. A project may\ncarry a monthly spend LIMIT, which bounds work without splitting\nthe bill, capacity CEILINGS (`max_concurrent_calls`,\n`max_requests_per_minute`, `max_concurrent_runs`) that sit below\nthe workspace's own plan limits so one project cannot consume the\nwhole allowance, and it may be ARCHIVED, which suspends all work\nand spend inside it while everything stays readable.\n\nAn application building on this platform models one of its OWN\nbusiness customers as a project: it is the level that both\nattributes cost (`monthly_spend`, grouped per project on the usage\nbreakdown) and ENFORCES it (`monthly_budget` and the three capacity\nceilings). The person an agent is dealing with is NOT a project -\nthat is `user_identity` on a conversation or a run. How many\nprojects a workspace may hold is the workspace's own ceiling; a\ncreate past it answers `409 project_limit_reached`, and deleting an\nunused project frees a slot.\n","title":"Project"},"ListProjectsResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}}},"required":["next_cursor","has_more","projects"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ListProjectsResponse"},"CreateProjectRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":120,"description":"Project name; unique per workspace (case-insensitive),\nsurrounding whitespace is trimmed.\n"}},"required":["name"],"description":"Create a project in the caller's workspace.","title":"CreateProjectRequest"},"UpdateProjectRequest":{"type":"object","properties":{"max_concurrent_runs":{"type":["integer","null"],"minimum":1,"description":"Sets the project's concurrent-run ceiling; `null` removes it.\nMust be a positive integer at or below the workspace's own run\nceiling, otherwise the request is refused with\n`400 validation_failed` naming the field and the ceiling.\nRequires the `billing.manage` permission. Takes effect on the\nnext run start attributed to the project.\n"},"max_concurrent_calls":{"type":["integer","null"],"minimum":1,"description":"Sets the project's active-call ceiling; `null` removes it.\nMust be a positive integer at or below the workspace's own\nactive-call cap, otherwise the request is refused with\n`400 validation_failed` naming the field and the ceiling: a\nproject can only narrow the workspace's capacity, never raise\nit. Requires the `billing.manage` permission, like the spend\nlimit beside it. Takes effect on the next call start.\n"},"max_requests_per_minute":{"type":["integer","null"],"minimum":1,"description":"Sets the project's request-rate ceiling in requests per\nminute; `null` removes it. Must be a positive integer at or\nbelow the workspace's widest per-surface request rate over a\nminute, otherwise the request is refused with\n`400 validation_failed` naming the field and the ceiling.\nRequires the `billing.manage` permission. Takes effect on the\nnext request from a credential pinned to the project.\n"},"name":{"type":"string","maxLength":120,"description":"New project name; unique per workspace (case-insensitive),\nsurrounding whitespace is trimmed.\n"},"monthly_budget":{"type":"number","format":"double","minimum":0,"maximum":1000000000,"multipleOf":0.01,"description":"Edits the project's MONTHLY spend limit in US dollars: omit to\nleave it unchanged, send a positive value to set or change it, or\nan explicit `0` to remove it. Amounts are whole cents written as a\nplain decimal; a finer value, or exponent notation, is refused\nrather than rounded. Requires the\n`billing.manage`\npermission (owners/admins), like the workspace budget \u2014 a\nspend ceiling is a billing control, not a grouping edit. Once the\nproject's billed spend within the current calendar month (UTC)\nreaches the limit, new billable work attributed to that project is\nrefused with the coded `402 project_spend_limit_exceeded` until\nthe month resets or the limit is raised.\n"}},"description":"Edits a project's display name, its monthly spend limit, and/or its\ncapacity ceilings; at least one field must be present. The project\nid is immutable, so a rename never re-points the resources grouped\nunder it. The limit fields are merge-patched: omit one to leave it\nunchanged; the capacity ceilings clear on an explicit `null`, the\nspend limit on `0`.\n","title":"UpdateProjectRequest"},"DeleteProjectRequestMode":{"type":"string","enum":["detach","purge"],"default":"detach","description":"`detach` removes the grouping row only; `purge` removes the\nproject with its contents.\n","title":"DeleteProjectRequestMode"},"DeleteProjectRequest":{"type":"object","properties":{"mode":{"$ref":"#/components/schemas/DeleteProjectRequestMode","default":"detach","description":"`detach` removes the grouping row only; `purge` removes the\nproject with its contents.\n"},"confirm":{"type":"string","description":"Required for `purge`: the project's name, exactly as returned by\nGET. A mismatch answers 400 `validation_failed` naming this field.\n"}},"description":"Optional body for DELETE /v1/projects/{project_id}. Omit it, or send\n`mode: detach`, for the default delete that keeps every resource and\nmoves it to the Default project. Send `mode: purge` with `confirm`\nequal to the project's exact name to remove the project with its\ncontents.\n","title":"DeleteProjectRequest"},"ProjectRestoredCounts":{"type":"object","properties":{"agents":{"type":"integer"},"agent_tests":{"type":"integer"},"knowledge_bases":{"type":"integer"},"tools":{"type":"integer"},"audio_assets":{"type":"integer"}},"description":"What a restore brought back, by kind. Every field is a real count,\nincluding zero.\n","title":"ProjectRestoredCounts"},"ProjectStillRevokedCounts":{"type":"object","properties":{"api_keys":{"type":"integer"},"service_accounts":{"type":"integer"},"credentials":{"type":"integer"},"webhook_endpoints":{"type":"integer"}},"description":"What the restore deliberately left revoked: the credentials the purge\nkilled. Every field is a real count, including zero.\n","title":"ProjectStillRevokedCounts"},"ProjectRestore":{"type":"object","properties":{"id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"purged_at":{"type":"string","format":"date-time","description":"When the purge this call walked back happened."},"archived_at":{"type":["string","null"],"format":"date-time","description":"When the project was archived. Always set on a restore: a project\ncomes back suspended, never live.\n"},"restored":{"$ref":"#/components/schemas/ProjectRestoredCounts"},"still_revoked":{"$ref":"#/components/schemas/ProjectStillRevokedCounts"},"not_restored_note":{"type":"string","description":"A plain-language statement of what a restore does not bring back.\nConstant text, meant to be shown to a person; branch on\n`still_revoked` rather than parsing it.\n"}},"description":"The outcome of restoring a purged project: what came back, and what\ndeliberately did not.\n\n`restored` counts the resources the purge removed and this call\nbrought back. `still_revoked` counts the credentials the purge revoked\nand this call left revoked, because re-issuing a credential someone\nkilled would hand back access rather than restore a project. Member\ngrants and pending-invite scopes are in that second group too, but\ntheir rows were removed by the purge, so they cannot be counted and\nappear only in `not_restored_note`.\n\nThe project comes back ARCHIVED, the state it was purged from, so\n`archived_at` is always set: nothing dispatches or bills inside it\nuntil you unarchive it on purpose.\n","title":"ProjectRestore"},"ProjectTeardownBlockerKind":{"type":"string","enum":["api_key","service_account","credential","webhook_endpoint","project_member","invite","phone_number"],"title":"ProjectTeardownBlockerKind"},"ProjectTeardownBlockerBlocksItems":{"type":"string","enum":["detach","purge"],"title":"ProjectTeardownBlockerBlocksItems"},"ProjectTeardownBlocker":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/ProjectTeardownBlockerKind"},"id":{"type":"string","pattern":"^(?:key|svc|cred|whe|invite|phone)_[0-9a-hjkmnp-tv-z]{26}$|^user_[a-z2-7]+$","description":"The row's prefixed external id. The kind decides which prefix:\n`key_` (API key), `svc_` (service account), `cred_` (vault\ncredential), `whe_` (webhook endpoint), `invite_` (pending\ninvite) and `phone_` (phone number) are Crockford base32 uuidv7\nids, while `user_` (member grant) wraps the workspace user's\nopaque identifier, exactly as the project members surface returns\nit. Read `kind` to know which shape to expect.\n"},"name":{"type":"string"},"blocks":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTeardownBlockerBlocksItems"},"description":"The delete modes this row refuses."}},"required":["kind","id","name","blocks"],"description":"One row whose scope holds the project open, and the delete modes it\nrefuses. A pinned API key, a service account, a scoped vault\ncredential, a scoped webhook endpoint, a member grant and a pending\ninvite carrying the project refuse a detach (detaching would widen\nthem); a phone number refuses a purge (its release is an external\nside effect); a member grant that is the member's only one, and a\nlive invite whose only project is this one, refuse both (clearing\neither would widen that person to the whole workspace, the invite one\nacceptance earlier).\n","title":"ProjectTeardownBlocker"},"ProjectTeardownGroup":{"type":"object","properties":{"kind":{"type":"string"},"count":{"type":"integer"},"sample":{"type":"array","items":{"type":"string"}}},"required":["kind","count","sample"],"description":"One resource kind a purge would remove or keep: a live count and, for\nremoved kinds, a bounded sample of names. Retained kinds carry no\nsample, because a caller identity or a memory is personal data.\n","title":"ProjectTeardownGroup"},"ProjectTeardown":{"type":"object","properties":{"id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$"},"name":{"type":"string"},"archived":{"type":"boolean","description":"Whether the project is archived. A purge is only available from\nthe archived state, so this is the precondition to check before\noffering a teardown.\n"},"purge_available":{"type":"boolean","description":"Whether `mode: purge` would be accepted right now: the project is\narchived and nothing blocks a purge. A courtesy read for the UI,\nnever the authority - the delete re-decides both halves under the\nproject's row lock, so a stale `true` still answers 409.\n"},"blocker_count":{"type":"integer","description":"How many rows hold this project open in total. `blockers` lists at\nmost 50 of them, so a project with more says so here; a delete is\nrefused on this number, never on the length of the list.\n"},"blockers":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTeardownBlocker"}},"removes":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTeardownGroup"}},"retains":{"type":"array","items":{"$ref":"#/components/schemas/ProjectTeardownGroup"}}},"required":["id","name","archived","purge_available","blocker_count","blockers","removes","retains"],"description":"What deleting a project would do, in either mode, with nothing\nmutated. `blockers` is a bounded sample (at most 50) of the rows\nholding the project open, with the modes each refuses, and\n`blocker_count` is how many there are in total. `removes` is what a purge deletes or revokes, one\nentry per kind in a fixed order (zero counts included). `retains` is\nthe operational records a purge keeps (a conversation with its frozen\nattribution, the rest moved to the Default project).\n","title":"ProjectTeardown"},"ProjectResourceCounts":{"type":"object","properties":{"agents":{"type":"integer","minimum":0,"description":"Number of agents in the project."},"knowledge_bases":{"type":"integer","minimum":0,"description":"Number of knowledge bases in the project."},"tools":{"type":"integer","minimum":0,"description":"Number of tools in the project."},"phone_numbers":{"type":"integer","minimum":0,"description":"Number of phone numbers in the project."},"audio_assets":{"type":"integer","minimum":0,"description":"Number of audio assets in the project."},"conversations":{"type":"integer","minimum":0,"description":"Number of conversations attributed to the project. Attribution is\nfrozen at the conversation's creation time and does not follow\nits agent to a later project.\n"},"callers":{"type":"integer","minimum":0,"description":"Number of callers attributed to the project, frozen the same way\nas conversations.\n"},"agent_tests":{"type":"integer","minimum":0,"description":"Number of agent tests attributed to the project, frozen the same\nway as conversations.\n"},"batch_calls":{"type":"integer","minimum":0,"description":"Number of batch calls attributed to the project, frozen the same\nway as conversations.\n"},"agent_test_suite_runs":{"type":"integer","minimum":0,"description":"Number of agent test suite runs attributed to the project, frozen\nthe same way as conversations.\n"},"memories":{"type":"integer","minimum":0,"description":"Number of live caller memories attributed to the project, frozen\nthe same way as conversations. A count only; the extracted facts\nthemselves are never returned here.\n"}},"required":["agents","knowledge_bases","tools","phone_numbers","audio_assets","conversations","callers","agent_tests","batch_calls","agent_test_suite_runs","memories"],"description":"Live counts of every resource kind grouped under one project. Each\ncount is zero, never absent, when the project holds none of that\nkind.\n","title":"ProjectResourceCounts"},"ProjectStats":{"type":"object","properties":{"resource_counts":{"$ref":"#/components/schemas/ProjectResourceCounts"}},"required":["resource_counts"],"description":"What a project contains, as live resource counts.\n\nCredentials pinned to the project are deliberately not included.\nListing API keys and service accounts is an account-level operation\nwith its own authentication, so use those endpoints directly rather\nthan reading credential names through a project read.\n","title":"ProjectStats"},"PromoteProjectRequest":{"type":"object","properties":{"target_project_id":{"type":"string","pattern":"^proj_[0-9a-hjkmnp-tv-z]{26}$","description":"The project to copy into. Must differ from the source and belong to the same workspace."}},"required":["target_project_id"],"title":"PromoteProjectRequest"},"PromoteCreated":{"type":"object","properties":{"agents":{"type":"array","items":{"type":"string","pattern":"^agent_[0-9a-hjkmnp-tv-z]{26}$"}},"tools":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}},"knowledge_bases":{"type":"array","items":{"type":"string","pattern":"^kb_[0-9a-hjkmnp-tv-z]{26}$"}},"audio_assets":{"type":"array","items":{"type":"string","pattern":"^audio_[0-9a-hjkmnp-tv-z]{26}$"}},"webhook_endpoints":{"type":"array","items":{"type":"string","pattern":"^whe_[0-9a-hjkmnp-tv-z]{26}$"}},"agent_tests":{"type":"array","items":{"type":"string","pattern":"^test_[0-9a-hjkmnp-tv-z]{26}$"}},"import_jobs":{"type":"array","items":{"type":"string","pattern":"^import_[0-9a-hjkmnp-tv-z]{26}$"},"description":"The URL re-imports queued in the target's knowledge bases; poll each for completion."}},"required":["agents","tools","knowledge_bases","audio_assets","webhook_endpoints","agent_tests","import_jobs"],"description":"Ids minted in the target, by kind. Each list is empty, never absent, when nothing of that kind was copied.","title":"PromoteCreated"},"PromoteReused":{"type":"object","properties":{"tools":{"type":"array","items":{"type":"string","pattern":"^tool_[0-9a-hjkmnp-tv-z]{26}$"}}},"required":["tools"],"description":"Target resources bound instead of copied because an identical definition already existed there.","title":"PromoteReused"},"PromoteAttentionKind":{"type":"string","enum":["agent","tool","knowledge_base","audio_asset","webhook_endpoint"],"description":"The kind of resource the entry names.","title":"PromoteAttentionKind"},"PromoteAttentionReason":{"type":"string","enum":["credential_not_attached","webhook_secret_reminted","webhook_secret_not_copied","renamed","phone_number_not_copied","documents_not_copied","import_not_requeued","audio_asset_not_copied"],"description":"Stable code a client can branch on. `credential_not_attached`: the\nsource referenced a vault credential, which never travels.\n`webhook_secret_reminted`: a fresh signing secret was minted; rotate\nto read it. `webhook_secret_not_copied`: the agent's customer-set\npost-call secret was cleared. `renamed`: the copy took a suffixed\nname. `phone_number_not_copied`: the source agent had numbers bound.\n`documents_not_copied`: the knowledge base held uploaded or pasted\ndocuments. `import_not_requeued`: URL sources could not be queued.\n`audio_asset_not_copied`: no asset storage is configured here.\n","title":"PromoteAttentionReason"},"PromoteAttention":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/PromoteAttentionKind","description":"The kind of resource the entry names."},"id":{"type":"string","pattern":"^[a-z][a-z_]*_[0-9a-hjkmnp-tv-z]{26}$","description":"The resource's prefixed external id; a target id except for `audio_asset_not_copied`, which names the source asset."},"reason":{"$ref":"#/components/schemas/PromoteAttentionReason","description":"Stable code a client can branch on. `credential_not_attached`: the\nsource referenced a vault credential, which never travels.\n`webhook_secret_reminted`: a fresh signing secret was minted; rotate\nto read it. `webhook_secret_not_copied`: the agent's customer-set\npost-call secret was cleared. `renamed`: the copy took a suffixed\nname. `phone_number_not_copied`: the source agent had numbers bound.\n`documents_not_copied`: the knowledge base held uploaded or pasted\ndocuments. `import_not_requeued`: URL sources could not be queued.\n`audio_asset_not_copied`: no asset storage is configured here.\n"},"message":{"type":"string","description":"Human-readable detail for the entry."}},"required":["kind","id","reason","message"],"description":"One thing the promote could not finish on its own.","title":"PromoteAttention"},"PromoteProjectResponse":{"type":"object","properties":{"created":{"$ref":"#/components/schemas/PromoteCreated"},"reused":{"$ref":"#/components/schemas/PromoteReused"},"needs_attention":{"type":"array","items":{"$ref":"#/components/schemas/PromoteAttention"}}},"required":["created","reused","needs_attention"],"description":"The manifest of one promote. Every id is a resource created in the\ntarget, except `reused`, which names target resources bound instead of\ncopied. `needs_attention` is what a person must finish by hand.\n","title":"PromoteProjectResponse"},"ProjectAuditEventType":{"type":"string","enum":["access_granted","access_revoked","access_widened_to_workspace","archived","unarchived"],"description":"What changed. `access_widened_to_workspace` is the revoke that\nremoved the subject's LAST grant anywhere: a member with no grants\nreaches the whole workspace, so that revoke widened them rather\nthan narrowing them.\n","title":"ProjectAuditEventType"},"ProjectAuditEvent":{"type":"object","properties":{"time":{"type":"string","format":"date-time","description":"When the change was recorded."},"type":{"$ref":"#/components/schemas/ProjectAuditEventType","description":"What changed. `access_widened_to_workspace` is the revoke that\nremoved the subject's LAST grant anywhere: a member with no grants\nreaches the whole workspace, so that revoke widened them rather\nthan narrowing them.\n"},"subject_user_id":{"type":["string","null"],"pattern":"^user_[a-z2-7]+$","description":"The member whose access changed. Null on a lifecycle change, which is done to the project rather than to a person."},"actor_user_id":{"type":["string","null"],"pattern":"^user_[a-z2-7]+$","description":"The member who made the change. Null when the change was system-initiated."},"actor_email":{"type":["string","null"],"description":"The actor's email at the time of the change. Null when the change carried no email."},"actor_role":{"type":["string","null"],"description":"The workspace role the actor held at the time. A role read back today cannot answer what they held when they acted. Null when the change carried no role."},"impersonator_email":{"type":["string","null"],"description":"The Speechify support admin acting on the workspace's behalf. Null unless the change came through support."},"remaining_grants":{"type":["integer","null"],"description":"How many projects the subject held after the change. Null on a lifecycle change; zero is what makes a revoke a widening to the whole workspace."}},"required":["time","type"],"description":"One change to this project's access or lifecycle state. The subject is\nwhose access changed; the actor is who changed it.\n\nEvery field below is always present. A field the event did not carry is\n`null`, which is a different fact from the key being absent altogether:\n`null` means this event had no such party, an absent key would mean the\nentry predates the platform recording that party at all. Audit entries\nare never backfilled, so the two are kept distinct.\n","title":"ProjectAuditEvent"},"ProjectAuditResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"events":{"type":"array","items":{"$ref":"#/components/schemas/ProjectAuditEvent"}}},"required":["next_cursor","has_more","events"],"description":"One page of a project's audit trail, newest change first.","title":"ProjectAuditResponse"},"ProjectMember":{"type":"object","properties":{"user_id":{"type":"string","pattern":"^user_[a-z2-7]+$","description":"The member's prefixed user id, joinable to the workspace members list."},"created_at":{"type":"string","format":"date-time"},"granted_by":{"type":"string","pattern":"^user_[a-z2-7]+$","description":"The prefixed user id of whoever made the grant. Attribution only."}},"required":["user_id","created_at","granted_by"],"description":"One project grant - a workspace member who may reach this project.","title":"ProjectMember"},"ProjectMembersResponse":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Opaque keyset cursor for the next page. Pass back as the\n`cursor` request parameter. `null` when the caller has\nreached the end of the list (`has_more` is also `false`\nin that case).\n"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page."},"members":{"type":"array","items":{"$ref":"#/components/schemas/ProjectMember"}}},"required":["next_cursor","has_more","members"],"description":"Shared pagination metadata composed into every cursor-paginated\nlist response via `allOf`. Ships `has_more` alongside `next_cursor`\nas two equivalent end-of-pages signals (defense-in-depth).\n","title":"ProjectMembersResponse"},"GrantProjectMemberRequest":{"type":"object","properties":{"user_id":{"type":"string","pattern":"^user_[a-z2-7]+$","description":"The prefixed user id of the workspace member to grant."}},"required":["user_id"],"title":"GrantProjectMemberRequest"}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'."}}}}