{
  "openapi": "3.1.0",
  "info": {
    "title": "Chambr API",
    "version": "1.0.0",
    "description": "Send finished sales calls to Chambr to be scored. The API returns an id, a status and an `app_url`; scores are viewed in Chambr and never returned.\n\n**Base URL** `https://api.staging.chambr.ai/v1` (alias `https://app.staging.chambr.ai/api/v1`, identical)\n\n**Auth** `Authorization: Bearer chambr_sk_...`. A Chambr admin creates keys under Real Call Scoring, API access.\n\n**Idempotency** `(provider, external_id)`: retrying `POST /v1/calls` is always safe.\n\n**Versioning** v1 changes are additive. Handle unknown fields and values.\n\nGuides, examples and error codes: https://developers.staging.chambr.ai",
    "license": {
      "name": "Proprietary"
    }
  },
  "externalDocs": {
    "description": "Guides and examples",
    "url": "https://developers.staging.chambr.ai"
  },
  "servers": [
    {
      "url": "https://api.staging.chambr.ai/v1",
      "description": "Chambr API"
    },
    {
      "url": "https://app.staging.chambr.ai/api/v1",
      "description": "Alias: the same API on the Chambr app host. Kept permanently; prefer the first server."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Calls",
      "description": "Send calls to be scored and read their status.",
      "externalDocs": {
        "url": "https://developers.staging.chambr.ai/guides/send-calls"
      }
    },
    {
      "name": "Recordings",
      "description": "Optional call recordings for playback in Chambr.",
      "externalDocs": {
        "url": "https://developers.staging.chambr.ai/guides/recordings"
      }
    }
  ],
  "paths": {
    "/calls": {
      "post": {
        "operationId": "createCall",
        "tags": [
          "Calls"
        ],
        "summary": "Send a call to be scored",
        "description": "Creates a call from a transcript; scoring is asynchronous. Idempotent on (provider, external_id); use a client timeout of at least 60 s.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCallRequest"
              },
              "examples": {
                "withRep": {
                  "summary": "Rep named by email",
                  "value": {
                    "external_id": "00T5f00000ABCdE",
                    "provider": "salesforce",
                    "occurred_at": "2026-09-28T15:02:14Z",
                    "call_type": "discovery",
                    "rep": {
                      "email": "sarah.reid@yourcompany.com",
                      "name": "Sarah Reid"
                    },
                    "participants": [
                      {
                        "role": "rep",
                        "name": "Sarah Reid",
                        "email": "sarah.reid@yourcompany.com"
                      },
                      {
                        "role": "customer",
                        "name": "Dana Whitfield",
                        "company": "Acme Freight"
                      }
                    ],
                    "transcript": {
                      "segments": [
                        {
                          "speaker": "Dana Whitfield",
                          "text": "Morning. I have about twenty minutes.",
                          "start_ms": 0
                        },
                        {
                          "speaker": "Sarah Reid",
                          "text": "Understood, I will keep it tight. What prompted the call?",
                          "start_ms": 4100
                        },
                        {
                          "speaker": "Dana Whitfield",
                          "text": "Our carrier costs went up twelve percent this quarter.",
                          "start_ms": 9800
                        },
                        {
                          "speaker": "Sarah Reid",
                          "text": "Which lanes are hurting the most?",
                          "start_ms": 15200
                        }
                      ]
                    },
                    "metadata": {
                      "salesforce": {
                        "task_id": "00T5f00000ABCdE",
                        "opportunity_id": "0065f00000XyZ"
                      }
                    }
                  }
                },
                "withoutRep": {
                  "summary": "No rep",
                  "value": {
                    "external_id": "00T5f00000ABCdF",
                    "provider": "salesforce",
                    "occurred_at": "2026-09-28T16:10:00Z",
                    "call_type": "discovery",
                    "participants": [
                      {
                        "role": "rep",
                        "name": "Sarah Reid"
                      },
                      {
                        "role": "customer",
                        "name": "Dana Whitfield",
                        "company": "Acme Freight"
                      }
                    ],
                    "transcript": {
                      "segments": [
                        {
                          "speaker": "Dana Whitfield",
                          "text": "Thanks for calling back."
                        },
                        {
                          "speaker": "Sarah Reid",
                          "text": "Of course. Did the lane numbers come through?"
                        },
                        {
                          "speaker": "Dana Whitfield",
                          "text": "They did, and they look better than I expected."
                        },
                        {
                          "speaker": "Sarah Reid",
                          "text": "Great. Shall we walk your CFO through them next week?"
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already received: the same provider + external_id with the same transcript, or the same transcript for the same rep. Nothing new was created.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallIngestResult"
                },
                "example": {
                  "id": "6f1c2b0e-8a4d-4c1e-9d55-0d6c7b1e2a90",
                  "object": "call",
                  "external_id": "00T5f00000ABCdE",
                  "provider": "salesforce",
                  "status": "queued",
                  "created": false,
                  "attribution": {
                    "rep_source": "rep_email",
                    "speaker_source": "name_match"
                  },
                  "recording_attached": false,
                  "app_url": "https://app.chambr.ai/t/3a1d9c52-7e0b-4f6a-9b21-5c8e4d7f0a13/real-calls/6f1c2b0e-8a4d-4c1e-9d55-0d6c7b1e2a90"
                }
              }
            }
          },
          "202": {
            "description": "Created and queued for scoring.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Location": {
                "description": "The call's absolute API URL, on the official API base.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallIngestResult"
                },
                "example": {
                  "id": "6f1c2b0e-8a4d-4c1e-9d55-0d6c7b1e2a90",
                  "object": "call",
                  "external_id": "00T5f00000ABCdE",
                  "provider": "salesforce",
                  "status": "queued",
                  "created": true,
                  "attribution": {
                    "rep_source": "rep_email",
                    "speaker_source": "name_match"
                  },
                  "recording_attached": false,
                  "app_url": "https://app.chambr.ai/t/3a1d9c52-7e0b-4f6a-9b21-5c8e4d7f0a13/real-calls/6f1c2b0e-8a4d-4c1e-9d55-0d6c7b1e2a90"
                }
              }
            }
          },
          "400": {
            "description": "Error: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error: invalid_api_key, api_key_revoked, api_key_owner_inactive.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "Missing, malformed or unknown key",
                    "value": {
                      "error": {
                        "code": "invalid_api_key",
                        "message": "This API key is not valid.",
                        "request_id": "req_7c1d2e3f4a5b6c7d8e9f0a1b"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error: insufficient_scope, feature_not_enabled, account_suspended, payment_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error: recording_already_used, external_id_conflict.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "external_id_conflict": {
                    "summary": "Same provider and external_id, different transcript",
                    "value": {
                      "error": {
                        "code": "external_id_conflict",
                        "message": "A call with this provider and external_id was already received with a different transcript. Calls cannot be updated in v1; send a new external_id for a different call.",
                        "request_id": "req_0a1b2c3d4e5f6a7b8c9d0e1f"
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Error: payload_too_large.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Error: unsupported_media_type.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error: validation_failed, unknown_field, transcript_unlabeled, transcript_too_long, rep_not_found, rep_inactive, rep_ambiguous, rep_no_team, rep_pending_activation, rep_speaker_not_found, rep_speaker_ambiguous, recording_not_found, recording_too_large, recording_type_not_supported, scorecard_not_found, scorecard_not_configured, team_not_configured, rep_speaker_undetermined.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "validation_failed": {
                    "summary": "Schema errors",
                    "value": {
                      "error": {
                        "code": "validation_failed",
                        "message": "The request body is not valid.",
                        "request_id": "req_1b2c3d4e5f6a7b8c9d0e1f2a",
                        "fields": [
                          {
                            "field": "occurred_at",
                            "message": "Must be an RFC 3339 date-time with Z or a +HH:MM offset."
                          }
                        ]
                      }
                    }
                  },
                  "rep_not_found": {
                    "summary": "rep.email is not a Chambr user in this account",
                    "value": {
                      "error": {
                        "code": "rep_not_found",
                        "message": "No Chambr user with this email is in your account. rep.email must be the rep's Chambr login email; invite them in Chambr first, or omit rep to let Chambr attribute the call.",
                        "request_id": "req_2c3d4e5f6a7b8c9d0e1f2a3b",
                        "fields": [
                          {
                            "field": "rep.email",
                            "message": "No matching Chambr user."
                          }
                        ]
                      }
                    }
                  },
                  "rep_speaker_undetermined": {
                    "summary": "The rep's speaker could not be determined",
                    "value": {
                      "error": {
                        "code": "rep_speaker_undetermined",
                        "message": "Chambr could not determine which speaker is the rep from this transcript. Send transcript.rep_speaker (one of: \"Speaker 1\", \"Speaker 2\").",
                        "request_id": "req_3d4e5f6a7b8c9d0e1f2a3b4c",
                        "fields": [
                          {
                            "field": "transcript.rep_speaker",
                            "message": "Rep speaker could not be determined."
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Error: rate_limited. Retry after Retry-After.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests",
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "Rate limit exceeded for this API key. Retry after the number of seconds in Retry-After.",
                        "request_id": "req_4e5f6a7b8c9d0e1f2a3b4c5d"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error: internal_error. Safe to retry with backoff.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error: inference_unavailable. Retry after Retry-After, or send transcript.rep_speaker.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "inference_unavailable": {
                    "summary": "Speaker inference failed or timed out",
                    "value": {
                      "error": {
                        "code": "inference_unavailable",
                        "message": "Chambr could not work out which speaker is the rep right now. Retry after Retry-After, or send transcript.rep_speaker (one of: \"Speaker 1\", \"Speaker 2\") to skip inference.",
                        "request_id": "req_5f6a7b8c9d0e1f2a3b4c5d6e"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/calls/{id}": {
      "get": {
        "operationId": "getCall",
        "tags": [
          "Calls"
        ],
        "summary": "Get a call's status",
        "description": "Status and in-app link for any call in your account, including calls uploaded in Chambr. Never returns scores or transcript text.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id returned by POST /v1/calls.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The call.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Call"
                },
                "example": {
                  "id": "6f1c2b0e-8a4d-4c1e-9d55-0d6c7b1e2a90",
                  "object": "call",
                  "external_id": "00T5f00000ABCdE",
                  "provider": "salesforce",
                  "status": "scored",
                  "skip_reason": null,
                  "rep": {
                    "email": "sarah.reid@yourcompany.com"
                  },
                  "call_type": "discovery",
                  "has_recording": false,
                  "attribution": {
                    "rep_source": "rep_email",
                    "speaker_source": "name_match"
                  },
                  "occurred_at": "2026-09-28T15:02:14.000Z",
                  "created_at": "2026-09-28T15:40:02.000Z",
                  "app_url": "https://app.chambr.ai/t/3a1d9c52-7e0b-4f6a-9b21-5c8e4d7f0a13/real-calls/6f1c2b0e-8a4d-4c1e-9d55-0d6c7b1e2a90"
                }
              }
            }
          },
          "401": {
            "description": "Error: invalid_api_key, api_key_revoked, api_key_owner_inactive.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "Missing, malformed or unknown key",
                    "value": {
                      "error": {
                        "code": "invalid_api_key",
                        "message": "This API key is not valid.",
                        "request_id": "req_7c1d2e3f4a5b6c7d8e9f0a1b"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error: insufficient_scope, feature_not_enabled, account_suspended, payment_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error: not_found, route_not_found.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error: rate_limited. Retry after Retry-After.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests",
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "Rate limit exceeded for this API key. Retry after the number of seconds in Retry-After.",
                        "request_id": "req_4e5f6a7b8c9d0e1f2a3b4c5d"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error: internal_error. Safe to retry with backoff.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/recordings": {
      "post": {
        "operationId": "createRecordingUpload",
        "tags": [
          "Recordings"
        ],
        "summary": "Get an upload URL for a call recording",
        "description": "Returns a presigned upload URL valid for 15 minutes. PUT the bytes with exactly upload_headers and no Authorization header, then send recording_id on POST /v1/calls.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRecordingRequest"
              },
              "example": {
                "content_type": "audio/mpeg",
                "size_bytes": 4821300,
                "filename": "call-00T5f00000ABCdE.mp3"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload URL issued.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecordingUpload"
                },
                "example": {
                  "recording_id": "rec_9b2f0c4e7d1a4e5f8a6b3c2d1e0f9a8b_mp3",
                  "object": "recording_upload",
                  "upload_url": "https://storage.example/recordings/...?signature=...",
                  "upload_method": "PUT",
                  "upload_headers": {
                    "Content-Type": "audio/mpeg",
                    "x-ms-blob-type": "BlockBlob",
                    "x-ms-blob-content-type": "audio/mpeg"
                  },
                  "expires_at": "2026-09-28T16:10:00.000Z",
                  "max_size_bytes": 209715200
                }
              }
            }
          },
          "400": {
            "description": "Error: invalid_json.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error: invalid_api_key, api_key_revoked, api_key_owner_inactive.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "Missing, malformed or unknown key",
                    "value": {
                      "error": {
                        "code": "invalid_api_key",
                        "message": "This API key is not valid.",
                        "request_id": "req_7c1d2e3f4a5b6c7d8e9f0a1b"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Error: insufficient_scope, feature_not_enabled, account_suspended, payment_required.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Error: payload_too_large.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Error: unsupported_media_type.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error: validation_failed, unknown_field, transcript_unlabeled, transcript_too_long, rep_not_found, rep_inactive, rep_ambiguous, rep_no_team, rep_pending_activation, rep_speaker_not_found, rep_speaker_ambiguous, recording_not_found, recording_too_large, recording_type_not_supported, scorecard_not_found, scorecard_not_configured, team_not_configured, rep_speaker_undetermined.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "validation_failed": {
                    "summary": "Schema errors",
                    "value": {
                      "error": {
                        "code": "validation_failed",
                        "message": "The request body is not valid.",
                        "request_id": "req_1b2c3d4e5f6a7b8c9d0e1f2a",
                        "fields": [
                          {
                            "field": "occurred_at",
                            "message": "Must be an RFC 3339 date-time with Z or a +HH:MM offset."
                          }
                        ]
                      }
                    }
                  },
                  "rep_not_found": {
                    "summary": "rep.email is not a Chambr user in this account",
                    "value": {
                      "error": {
                        "code": "rep_not_found",
                        "message": "No Chambr user with this email is in your account. rep.email must be the rep's Chambr login email; invite them in Chambr first, or omit rep to let Chambr attribute the call.",
                        "request_id": "req_2c3d4e5f6a7b8c9d0e1f2a3b",
                        "fields": [
                          {
                            "field": "rep.email",
                            "message": "No matching Chambr user."
                          }
                        ]
                      }
                    }
                  },
                  "rep_speaker_undetermined": {
                    "summary": "The rep's speaker could not be determined",
                    "value": {
                      "error": {
                        "code": "rep_speaker_undetermined",
                        "message": "Chambr could not determine which speaker is the rep from this transcript. Send transcript.rep_speaker (one of: \"Speaker 1\", \"Speaker 2\").",
                        "request_id": "req_3d4e5f6a7b8c9d0e1f2a3b4c",
                        "fields": [
                          {
                            "field": "transcript.rep_speaker",
                            "message": "Rep speaker could not be determined."
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Error: rate_limited. Retry after Retry-After.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "Too many requests",
                    "value": {
                      "error": {
                        "code": "rate_limited",
                        "message": "Rate limit exceeded for this API key. Retry after the number of seconds in Retry-After.",
                        "request_id": "req_4e5f6a7b8c9d0e1f2a3b4c5d"
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Error: internal_error. Safe to retry with backoff.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiDocument",
        "summary": "This document",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key created by an admin in Chambr (Real Call Scoring, API access). Send Authorization: Bearer chambr_sk_..."
      }
    },
    "headers": {
      "RequestId": {
        "description": "Unique id for this request. Quote it when contacting support.",
        "schema": {
          "type": "string"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "schemas": {
      "CreateCallRequest": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Your stable id for this call (for example the Salesforce Task id). (provider, external_id) is unique per Chambr account and is the idempotency key."
          },
          "provider": {
            "type": "string",
            "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$",
            "description": "Which system the call comes from, as a short slug (letters, digits, - and _; lower-cased by Chambr), e.g. \"salesforce\"."
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the call happened, ISO 8601 with a UTC offset (e.g. 2026-09-28T15:02:14Z). Between 2000-01-01 and one day from now."
          },
          "call_type": {
            "description": "Recommended. When omitted, Chambr classifies the call type from the transcript (slower; falls back to discovery).",
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "discovery",
              "cold_call",
              "warm_call",
              "customer_success",
              "cross_selling",
              "evaluation",
              "negotiation",
              "gatekeeper",
              "inbound_call",
              null
            ]
          },
          "scorecard_id": {
            "description": "The scorecard to score against (visible in the scorecard's URL in Chambr). When omitted, the team's scorecard rules decide.",
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "rep": {
            "description": "Optional. The rep this call is scored for. Omit it to let Chambr attribute the call: it is filed under the API key's team, owned by the admin who created the key, until a manager reassigns it in Chambr. A rep that is sent but cannot be resolved is refused (rep_*), never silently reassigned.",
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "email": {
                "type": "string",
                "maxLength": 320,
                "format": "email",
                "description": "The rep's Chambr login email. Required when rep is sent: this is who the call is scored for."
              },
              "name": {
                "description": "Used, with the email, to find the rep's speaker label.",
                "type": [
                  "string",
                  "null"
                ],
                "minLength": 1,
                "maxLength": 200
              }
            },
            "required": [
              "email"
            ],
            "additionalProperties": false
          },
          "participants": {
            "maxItems": 50,
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "role": {
                  "type": "string",
                  "enum": [
                    "rep",
                    "customer",
                    "other"
                  ]
                },
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "email": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "maxLength": 320,
                  "format": "email"
                },
                "company": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "minLength": 1,
                  "maxLength": 200
                },
                "speaker_label": {
                  "description": "This participant's speaker label in the transcript, when it differs from their name.",
                  "type": [
                    "string",
                    "null"
                  ],
                  "minLength": 1,
                  "maxLength": 200
                }
              },
              "required": [
                "role",
                "name"
              ],
              "additionalProperties": false
            }
          },
          "transcript": {
            "type": "object",
            "properties": {
              "segments": {
                "description": "Preferred. Ordered speaker-labelled segments. Consecutive segments from the same speaker are merged into one turn.",
                "minItems": 1,
                "maxItems": 10000,
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "type": "object",
                  "properties": {
                    "speaker": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 200,
                      "description": "The speaker label exactly as your transcript names it, e.g. \"Sarah Reid\"."
                    },
                    "text": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 20000
                    },
                    "start_ms": {
                      "description": "Offset of this segment from the start of the call, in milliseconds.",
                      "type": [
                        "integer",
                        "null"
                      ],
                      "minimum": 0,
                      "maximum": 9007199254740991
                    }
                  },
                  "required": [
                    "speaker",
                    "text"
                  ],
                  "additionalProperties": false
                }
              },
              "text": {
                "description": "Alternative to segments: a plain-text transcript with one speaker label per turn, e.g. \"Sarah Reid: Thanks for joining\" or \"[00:12] Sarah Reid\" header lines.",
                "type": [
                  "string",
                  "null"
                ],
                "minLength": 1,
                "maxLength": 600000
              },
              "rep_speaker": {
                "description": "The speaker label that is the rep. Optional when the rep's name or email matches exactly one speaker label; required otherwise.",
                "type": [
                  "string",
                  "null"
                ],
                "minLength": 1,
                "maxLength": 200
              }
            },
            "additionalProperties": false,
            "description": "The call transcript. Exactly one of `segments` or `text` is required.",
            "oneOf": [
              {
                "required": [
                  "segments"
                ]
              },
              {
                "required": [
                  "text"
                ]
              }
            ]
          },
          "recording_id": {
            "description": "Optional. The id returned by POST /v1/recordings after you uploaded the file. For playback in Chambr only; the recording is not transcribed.",
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 100
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ],
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {},
            "description": "Free-form JSON object for your own reference (for example Salesforce record ids). Stored with the call, at most 16 KB serialized and 5 levels deep. Not shown in Chambr in v1 and never used for scoring. May contain personal data, so send only what you need; it lives as long as the call."
          }
        },
        "required": [
          "external_id",
          "provider",
          "occurred_at",
          "transcript"
        ],
        "additionalProperties": false
      },
      "CallIngestResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The call id in Chambr."
          },
          "object": {
            "type": "string",
            "const": "call"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "provider": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "description": "Open enum: handle values not listed here. Known values: `queued`, `scoring`, `scored`, `skipped`, `failed`."
          },
          "created": {
            "type": "boolean",
            "description": "true when this request created the call (202); false when it already existed (200)."
          },
          "attribution": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "rep_source": {
                "type": "string",
                "description": "Who the call belongs to: `rep_email` (the rep you sent) or `api_key_owner` (no rep sent: the key's creator, in the key's team, until a manager reassigns it). Open enum: handle values not listed here."
              },
              "speaker_source": {
                "type": "string",
                "description": "How the rep's speaker was found: `rep_speaker` (you sent it), `name_match` (rep or rep participant name/email), `inferred` (read from the dialogue). Open enum: handle values not listed here."
              }
            },
            "required": [
              "rep_source",
              "speaker_source"
            ],
            "description": "How the call was attributed; null for calls that did not come through the API."
          },
          "recording_attached": {
            "type": "boolean",
            "description": "Whether the call has a recording attached."
          },
          "app_url": {
            "type": "string",
            "description": "Where the call can be viewed in Chambr (needs a Chambr login with access to the rep's team)."
          }
        },
        "required": [
          "id",
          "object",
          "external_id",
          "provider",
          "status",
          "created",
          "attribution",
          "recording_attached",
          "app_url"
        ]
      },
      "Call": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "const": "call"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "provider": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "description": "Open enum: handle values not listed here. Known values: `queued`, `scoring`, `scored`, `skipped`, `failed`."
          },
          "skip_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set only when status is `skipped`. Open enum: handle values not listed here. Known values: `too_short`, `no_scorecard`, `empty_scorecard`, `all_criteria_na`."
          },
          "rep": {
            "type": "object",
            "properties": {
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "email"
            ]
          },
          "call_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "has_recording": {
            "type": "boolean"
          },
          "attribution": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "rep_source": {
                "type": "string",
                "description": "Who the call belongs to: `rep_email` (the rep you sent) or `api_key_owner` (no rep sent: the key's creator, in the key's team, until a manager reassigns it). Open enum: handle values not listed here."
              },
              "speaker_source": {
                "type": "string",
                "description": "How the rep's speaker was found: `rep_speaker` (you sent it), `name_match` (rep or rep participant name/email), `inferred` (read from the dialogue). Open enum: handle values not listed here."
              }
            },
            "required": [
              "rep_source",
              "speaker_source"
            ],
            "description": "How the call was attributed; null for calls that did not come through the API."
          },
          "occurred_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "app_url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "object",
          "external_id",
          "provider",
          "status",
          "skip_reason",
          "rep",
          "call_type",
          "has_recording",
          "attribution",
          "occurred_at",
          "created_at",
          "app_url"
        ]
      },
      "CreateRecordingRequest": {
        "type": "object",
        "properties": {
          "content_type": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "The file's MIME type. Supported: audio/mpeg, audio/mp3, audio/wav, audio/x-wav, audio/mp4, audio/x-m4a, audio/aac, audio/webm, audio/ogg, audio/flac, video/mp4, video/webm, video/quicktime, video/x-matroska, video/x-m4v."
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991,
            "description": "The file size in bytes. At most 209715200 (200 MB)."
          },
          "filename": {
            "description": "Informational only.",
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 255
          }
        },
        "required": [
          "content_type",
          "size_bytes"
        ],
        "additionalProperties": false
      },
      "RecordingUpload": {
        "type": "object",
        "properties": {
          "recording_id": {
            "type": "string",
            "description": "Opaque id. Pass it as recording_id on POST /v1/calls after the upload succeeds."
          },
          "object": {
            "type": "string",
            "const": "recording_upload"
          },
          "upload_url": {
            "type": "string",
            "description": "Presigned URL. PUT the file bytes here. Do not add an Authorization header."
          },
          "upload_method": {
            "type": "string",
            "const": "PUT"
          },
          "upload_headers": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "type": "string"
            },
            "description": "Send every one of these headers on the PUT, exactly as given."
          },
          "expires_at": {
            "type": "string",
            "description": "The upload URL stops working after this time."
          },
          "max_size_bytes": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "recording_id",
          "object",
          "upload_url",
          "upload_method",
          "upload_headers",
          "expires_at",
          "max_size_bytes"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable error code. Open enum: handle values not listed here. Known values: `invalid_json`, `invalid_api_key`, `api_key_revoked`, `api_key_owner_inactive`, `insufficient_scope`, `feature_not_enabled`, `account_suspended`, `payment_required`, `not_found`, `route_not_found`, `recording_already_used`, `external_id_conflict`, `payload_too_large`, `unsupported_media_type`, `validation_failed`, `unknown_field`, `transcript_unlabeled`, `transcript_too_long`, `rep_not_found`, `rep_inactive`, `rep_ambiguous`, `rep_no_team`, `rep_pending_activation`, `rep_speaker_not_found`, `rep_speaker_ambiguous`, `recording_not_found`, `recording_too_large`, `recording_type_not_supported`, `scorecard_not_found`, `scorecard_not_configured`, `team_not_configured`, `rep_speaker_undetermined`, `rate_limited`, `internal_error`, `inference_unavailable`."
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation. May change; do not parse it."
              },
              "request_id": {
                "type": "string"
              },
              "fields": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "field",
                    "message"
                  ]
                },
                "description": "Present on some 422 responses: which request fields are wrong."
              }
            },
            "required": [
              "code",
              "message",
              "request_id"
            ]
          }
        },
        "required": [
          "error"
        ],
        "example": {
          "error": {
            "code": "rep_speaker_not_found",
            "message": "No transcript speaker matches the rep. Speakers found: \"Dana Whitfield\", \"S. Reid\". Send transcript.rep_speaker to say which one is the rep.",
            "request_id": "req_4f0c8d1e2a9b7c6d5e4f3a2b",
            "fields": [
              {
                "field": "transcript.rep_speaker",
                "message": "Rep speaker not identified."
              }
            ]
          }
        }
      }
    }
  }
}