feat: validate OpenAPI spec using Skooma (#13623)

Adds Skooma-based OpenAPI validation so SDK-facing request specs can
assert that documented request and response contracts match real Rails
behavior. This also upgrades the spec to OpenAPI 3.1 and fixes contract
drift uncovered while validating core application and platform
resources.

Closes
None

Why
We want CI to catch OpenAPI drift before it reaches SDK consumers. While
wiring validation in, this PR surfaced several mismatches between the
documented contract and what the Rails endpoints actually accept or
return.

What this change does
- Adds Skooma-backed OpenAPI validation to the request spec flow and a
dedicated OpenAPI validation spec.
- Migrates nullable schema definitions to OpenAPI 3.1-compatible unions.
- Updates core SDK-facing schemas and payloads across accounts,
contacts, conversations, inboxes, messages, teams, reporting events, and
platform account resources.
- Documents concrete runtime cases that were previously missing or
inaccurate, including nested `profile` update payloads, multipart avatar
uploads, required profile update bodies, nullable inbox feature flags,
and message sender types that include both `Captain::Assistant` and
senderless activity-style messages.
- Regenerates the committed Swagger JSON and tag-group artifacts used by
CI sync checks.

Validation
- `bundle exec rake swagger:build`
- `bundle exec rspec spec/swagger/openapi_spec.rb`

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Shivam Mishra
2026-03-11 07:03:55 +05:30
committed by GitHub
parent dc0e5eb465
commit 9a9398b386
54 changed files with 3216 additions and 1192 deletions

View File

@@ -93,8 +93,8 @@ jobs:
exit 1
fi
mkdir -p ~/tmp
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.3.0/openapi-generator-cli-6.3.0.jar > ~/tmp/openapi-generator-cli-6.3.0.jar
java -jar ~/tmp/openapi-generator-cli-6.3.0.jar validate -i swagger/swagger.json
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.19.0/openapi-generator-cli-7.19.0.jar > ~/tmp/openapi-generator-cli-7.19.0.jar
java -jar ~/tmp/openapi-generator-cli-7.19.0.jar validate -i swagger/swagger.json
# Bundle audit
- run:

View File

@@ -268,6 +268,7 @@ group :development, :test do
gem 'seed_dump'
gem 'shoulda-matchers'
gem 'simplecov', '>= 0.21', require: false
gem 'skooma'
gem 'spring'
gem 'spring-watcher-listen'
end

View File

@@ -473,6 +473,12 @@ GEM
hana (~> 1.3)
regexp_parser (~> 2.0)
uri_template (~> 0.7)
json_skooma (0.2.5)
bigdecimal
hana (~> 1.3)
regexp_parser (~> 2.0)
uri-idna (~> 0.2)
zeitwerk (~> 2.6)
judoscale-rails (1.8.2)
judoscale-ruby (= 1.8.2)
railties
@@ -910,6 +916,9 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4)
skooma (0.3.7)
json_skooma (~> 0.2.5)
zeitwerk (~> 2.6)
slack-ruby-client (2.7.0)
faraday (>= 2.0.1)
faraday-mashify
@@ -970,6 +979,7 @@ GEM
unicode-emoji (4.0.4)
uniform_notifier (1.17.0)
uri (1.1.1)
uri-idna (0.3.1)
uri_template (0.7.0)
valid_email2 (5.2.6)
activemodel (>= 3.2)
@@ -1143,6 +1153,7 @@ DEPENDENCIES
sidekiq_alive
simplecov (>= 0.21)
simplecov_json_formatter
skooma
slack-ruby-client (~> 2.7.0)
spring
spring-watcher-listen

View File

@@ -25,6 +25,7 @@ RSpec.describe 'Agents API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.parsed_body.size).to eq(account.users.count)
end
@@ -122,6 +123,7 @@ RSpec.describe 'Agents API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(other_agent.reload.name).to eq(params[:name])
end
@@ -171,6 +173,7 @@ RSpec.describe 'Agents API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.parsed_body['email']).to eq(params[:email])
expect(account.users.last.name).to eq('NewUser')
end

View File

@@ -45,6 +45,7 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
response_body = response.parsed_body
contact_emails = response_body['payload'].pluck('email')
contact_inboxes_source_ids = response_body['payload'].flat_map { |c| c['contact_inboxes'].pluck('source_id') }
@@ -331,6 +332,7 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(contact2.email)
expect(response.body).not_to include(contact1.email)
end
@@ -443,6 +445,7 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(contact2.email)
expect(response.body).to include(contact1.email)
end
@@ -497,6 +500,7 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(contact.name)
end
end
@@ -620,6 +624,7 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(contact.reload.name).to eq('Test Blub')
# custom attributes are merged properly without overwriting existing ones
expect(contact.custom_attributes).to eq({ 'test' => 'new test', 'test1' => 'test1', 'test2' => 'test2' })

View File

@@ -31,6 +31,7 @@ RSpec.describe 'Conversation Messages API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(conversation.messages.count).to eq(1)
expect(conversation.messages.first.content).to eq(params[:content])
end
@@ -182,6 +183,7 @@ RSpec.describe 'Conversation Messages API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:meta][:contact][:id]).to eq(conversation.contact_id)
end
end

View File

@@ -27,6 +27,7 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
body = JSON.parse(response.body, symbolize_names: true)
expect(body[:data][:meta][:all_count]).to eq(1)
expect(body[:data][:meta].keys).to include(:all_count, :mine_count, :assigned_count, :unassigned_count)
@@ -165,6 +166,7 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
response_data = JSON.parse(response.body, symbolize_names: true)
expect(response_data.count).to eq(2)
end
@@ -234,6 +236,7 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:id]).to eq(conversation.display_id)
end
@@ -282,6 +285,7 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:priority]).to eq('high')
end
@@ -342,6 +346,7 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
response_data = JSON.parse(response.body, symbolize_names: true)
expect(response_data[:additional_attributes]).to eq(additional_attributes)
end
@@ -449,9 +454,11 @@ RSpec.describe 'Conversations API', type: :request do
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
headers: agent.create_new_auth_token,
params: { status: 'open' },
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(conversation.reload.status).to eq('open')
end

View File

@@ -32,6 +32,7 @@ RSpec.describe 'Inboxes API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:payload].size).to eq(2)
end
@@ -95,6 +96,7 @@ RSpec.describe 'Inboxes API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:id]).to eq(inbox.id)
end
@@ -383,6 +385,7 @@ RSpec.describe 'Inboxes API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include('test.com')
end
@@ -478,6 +481,7 @@ RSpec.describe 'Inboxes API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(inbox.reload.enable_auto_assignment).to be_falsey
expect(inbox.reload.portal_id).to eq(portal.id)
expect(response.parsed_body['name']).to eq 'new test inbox'

View File

@@ -22,6 +22,7 @@ RSpec.describe 'Teams API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.parsed_body.first['id']).to eq(account.teams.first.id)
end
end
@@ -45,6 +46,7 @@ RSpec.describe 'Teams API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.parsed_body['id']).to eq(team.id)
end
end
@@ -83,6 +85,7 @@ RSpec.describe 'Teams API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(Team.count).to eq(2)
end
end
@@ -121,6 +124,7 @@ RSpec.describe 'Teams API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(team.reload.name).to eq('new-team')
end
end

View File

@@ -149,6 +149,7 @@ RSpec.describe 'Accounts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(account.name)
expect(response.body).to include(account.locale)
expect(response.body).to include(account.domain)
@@ -184,22 +185,22 @@ RSpec.describe 'Accounts API', type: :request do
end
end
describe 'PUT /api/v1/accounts/{account.id}' do
describe 'PATCH /api/v1/accounts/{account.id}' do
let(:account) { create(:account) }
let(:agent) { create(:user, account: account, role: :agent) }
let(:admin) { create(:user, account: account, role: :administrator) }
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
put "/api/v1/accounts/#{account.id}"
patch "/api/v1/accounts/#{account.id}"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an unauthorized user' do
it 'returns unauthorized' do
put "/api/v1/accounts/#{account.id}",
headers: agent.create_new_auth_token
patch "/api/v1/accounts/#{account.id}",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
@@ -219,11 +220,20 @@ RSpec.describe 'Accounts API', type: :request do
company_size: '1-10'
}
it 'returns a valid schema' do
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to conform_schema(200)
end
it 'modifies an account' do
put "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(account.reload.name).to eq(params[:name])
@@ -242,19 +252,19 @@ RSpec.describe 'Accounts API', type: :request do
it 'updates onboarding step to invite_team if onboarding step is present in account custom attributes' do
account.update(custom_attributes: { onboarding_step: 'account_update' })
put "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(account.reload.custom_attributes['onboarding_step']).to eq('invite_team')
end
it 'will not update onboarding step if onboarding step is not present in account custom attributes' do
put "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(account.reload.custom_attributes['onboarding_step']).to be_nil
end
@@ -262,10 +272,10 @@ RSpec.describe 'Accounts API', type: :request do
it 'Throws error 422' do
params[:name] = 'test' * 999
put "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
json_response = response.parsed_body

View File

@@ -21,6 +21,7 @@ RSpec.describe 'Profile API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
json_response = response.parsed_body
expect(json_response['id']).to eq(agent.id)
expect(json_response['email']).to eq(agent.email)
@@ -50,6 +51,7 @@ RSpec.describe 'Profile API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
json_response = response.parsed_body
agent.reload
expect(json_response['id']).to eq(agent.id)
@@ -64,6 +66,7 @@ RSpec.describe 'Profile API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
agent.reload
expect(agent.custom_attributes['phone_number']).to eq('+123456789')
@@ -91,6 +94,7 @@ RSpec.describe 'Profile API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(agent.reload.valid_password?('Test1234!')).to be true
end

View File

@@ -144,6 +144,7 @@ RSpec.describe 'Platform Accounts API', type: :request do
headers: { api_access_token: platform_app.access_token.token }, as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(account.name)
end
end

View File

@@ -75,6 +75,10 @@ RSpec.configure do |config|
config.include ActiveSupport::Testing::TimeHelpers
config.include ActionCable::TestHelper
config.include ActiveJob::TestHelper
# OpenAPI response validation via Skooma
path_to_openapi = Rails.root.join('swagger/swagger.json')
config.include Skooma::RSpec[path_to_openapi], type: :request
end
Shoulda::Matchers.configure do |config|

View File

@@ -0,0 +1,7 @@
require 'rails_helper'
RSpec.describe 'OpenAPI document', type: :request do
it 'is valid against the OpenAPI 3.1.0 meta-schema' do
expect(skooma_openapi_schema).to be_valid_document
end
end

View File

@@ -18,20 +18,23 @@ properties:
example: 'support@example.com'
# Settings parameters (stored in settings JSONB column)
auto_resolve_after:
type: integer
type:
- integer
- 'null'
minimum: 10
maximum: 1439856
nullable: true
description: Auto resolve conversations after specified minutes
example: 1440
auto_resolve_message:
type: string
nullable: true
type:
- string
- 'null'
description: Message to send when auto resolving
example: "This conversation has been automatically resolved due to inactivity"
auto_resolve_ignore_waiting:
type: boolean
nullable: true
type:
- boolean
- 'null'
description: Whether to ignore waiting conversations for auto resolve
example: false
# Custom attributes parameters (stored in custom_attributes JSONB column)

View File

@@ -1,7 +1,6 @@
type: object
required:
- source_id
- inbox_id
properties:
source_id:
type: string

View File

@@ -26,9 +26,7 @@ properties:
type: object
description: Cache keys for the account
features:
type: array
items:
type: string
type: object
description: Enabled features for the account
settings:
type: object
@@ -48,16 +46,24 @@ properties:
description: Custom attributes of the account
properties:
plan_name:
type: string
type:
- string
- 'null'
description: Subscription plan name
subscribed_quantity:
type: number
type:
- number
- 'null'
description: Subscribed quantity
subscription_status:
type: string
type:
- string
- 'null'
description: Subscription status
subscription_ends_on:
type: string
type:
- string
- 'null'
format: date
description: Subscription end date
industry:

View File

@@ -3,7 +3,9 @@ allOf:
- type: object
properties:
latest_chatwoot_version:
type: string
type:
- string
- 'null'
description: Latest version of Chatwoot available
example: "3.0.0"
subscribed_features:

View File

@@ -31,5 +31,7 @@ properties:
type: string
description: The thumbnail of the agent
custom_role_id:
type: integer
type:
- integer
- 'null'
description: The custom role id of the agent

View File

@@ -38,8 +38,9 @@ properties:
type: integer
description: Version number of the audit log entry
comment:
type: string
nullable: true
type:
- string
- 'null'
description: Optional comment associated with the audit log entry
request_uuid:
type: string
@@ -48,6 +49,7 @@ properties:
type: integer
description: Unix timestamp when the audit log entry was created
remote_address:
type: string
nullable: true
type:
- string
- 'null'
description: IP address from which the action was performed

View File

@@ -31,9 +31,10 @@ properties:
type: string
description: Status of the message
source_id:
type: string
type:
- string
- 'null'
description: Source ID of the message
nullable: true
content_type:
type: string
description: Type of the content
@@ -41,13 +42,15 @@ properties:
type: object
description: Attributes of the content
sender_type:
type: string
type:
- string
- 'null'
description: Type of the sender
nullable: true
sender_id:
type: integer
type:
- integer
- 'null'
description: ID of the sender
nullable: true
external_source_ids:
type: object
description: External source IDs
@@ -55,9 +58,10 @@ properties:
type: object
description: Additional attributes of the message
processed_message_content:
type: string
type:
- string
- 'null'
description: Processed message content
nullable: true
sentiment:
type: object
description: Sentiment analysis of the message
@@ -66,9 +70,10 @@ properties:
description: Conversation details
properties:
assignee_id:
type: integer
type:
- integer
- 'null'
description: ID of the assignee
nullable: true
unread_count:
type: integer
description: Count of unread messages

View File

@@ -11,7 +11,9 @@ properties:
type: string
description: Country of the contact
country_code:
type: string
type:
- string
- 'null'
description: Country code of the contact
created_at_ip:
type: string
@@ -26,16 +28,18 @@ properties:
type: integer
description: The ID of the contact
identifier:
type: string
type:
- string
- 'null'
description: The identifier of the contact
nullable: true
name:
type: string
description: The name of the contact
phone_number:
type: string
type:
- string
- 'null'
description: The phone number of the contact
nullable: true
thumbnail:
type: string
description: The thumbnail of the contact

View File

@@ -22,6 +22,7 @@ properties:
type: string
description: Type of channel
provider:
type: string
description: Provider of the inbox
nullable: true
type:
- string
- 'null'
description: Provider of the inbox

View File

@@ -11,7 +11,9 @@ properties:
type: string
description: Country of the contact
country_code:
type: string
type:
- string
- 'null'
description: Country code of the contact
created_at_ip:
type: string
@@ -21,9 +23,10 @@ properties:
description: Availability status of the contact
enum: ["online", "offline"]
email:
type: string
type:
- string
- 'null'
description: The email address of the contact
nullable: true
id:
type: integer
description: The ID of the contact
@@ -31,16 +34,18 @@ properties:
type: string
description: The name of the contact
phone_number:
type: string
type:
- string
- 'null'
description: The phone number of the contact
nullable: true
blocked:
type: boolean
description: Whether the contact is blocked
identifier:
type: string
type:
- string
- 'null'
description: The identifier of the contact
nullable: true
thumbnail:
type: string
description: The thumbnail of the contact
@@ -48,9 +53,10 @@ properties:
type: object
description: The custom attributes of the contact
last_activity_at:
type: integer
type:
- integer
- 'null'
description: Timestamp of last activity
nullable: true
created_at:
type: integer
description: Timestamp when contact was created

View File

@@ -4,5 +4,7 @@ properties:
type: integer
description: Total number of contacts
current_page:
type: string
description: Current page number
type:
- string
- integer
description: Current page number

View File

@@ -43,7 +43,9 @@ properties:
type: boolean
description: Whether the conversation is muted
snoozed_until:
type: number
type:
- number
- 'null'
description: The time at which the conversation will be unmuted
status:
type: string
@@ -56,29 +58,38 @@ properties:
type: number
description: The time at which conversation was updated
timestamp:
type: string
type: number
description: The time at which conversation was created
first_reply_created_at:
type: number
type:
- number
- 'null'
description: The time at which the first reply was created
unread_count:
type: number
description: The number of unread messages
last_non_activity_message:
type: object
$ref: '#/components/schemas/message'
oneOf:
- $ref: '#/components/schemas/message'
- type: 'null'
description: The last non activity message
last_activity_at:
type: number
description: The last activity at of the conversation
priority:
type: string
type:
- string
- 'null'
description: The priority of the conversation
waiting_since:
type: number
type:
- number
- 'null'
description: The time at which the conversation was waiting
sla_policy_id:
type: number
type:
- number
- 'null'
description: The ID of the SLA policy
applied_sla:
type: object

View File

@@ -51,10 +51,12 @@ properties:
description: The agent assigned to the conversation
nullable: true
agent_last_seen_at:
type: string
type:
- string
- 'null'
description: Timestamp when the agent last saw the conversation
nullable: true
assignee_last_seen_at:
type: string
description: Timestamp when the assignee last saw the conversation
nullable: true
type:
- string
- 'null'
description: Timestamp when the assignee last saw the conversation

View File

@@ -13,7 +13,9 @@ properties:
type: string
description: The availability status of the sender
email:
type: string
type:
- string
- 'null'
description: The email of the sender
id:
type: number
@@ -22,16 +24,22 @@ properties:
type: string
description: The name of the sender
phone_number:
type: string
type:
- string
- 'null'
description: The phone number of the sender
blocked:
type: boolean
description: Whether the sender is blocked
identifier:
type: string
type:
- string
- 'null'
description: The identifier of the sender
thumbnail:
type: string
type:
- string
- 'null'
description: Avatar URL of the contact
custom_attributes:
type: object

View File

@@ -28,16 +28,22 @@ properties:
type: string
description: Script used to load the website widget
welcome_title:
type: string
type:
- string
- 'null'
description: Welcome title to be displayed on the widget
welcome_tagline:
type: string
type:
- string
- 'null'
description: Welcome tagline to be displayed on the widget
greeting_enabled:
type: boolean
description: The flag which shows whether greeting is enabled
greeting_message:
type: string
type:
- string
- 'null'
description: A greeting message when the user starts the conversation
channel_id:
type: number
@@ -55,7 +61,9 @@ properties:
type: object
description: Configuration settings for auto assignment
out_of_office_message:
type: string
type:
- string
- 'null'
description: Message to show when agents are out of office
working_hours:
type: array
@@ -70,16 +78,24 @@ properties:
type: boolean
description: Whether the inbox is closed for the entire day
open_hour:
type: number
type:
- number
- 'null'
description: Hour when inbox opens (0-23)
open_minutes:
type: number
type:
- number
- 'null'
description: Minutes of the hour when inbox opens (0-59)
close_hour:
type: number
type:
- number
- 'null'
description: Hour when inbox closes (0-23)
close_minutes:
type: number
type:
- number
- 'null'
description: Minutes of the hour when inbox closes (0-59)
open_all_day:
type: boolean
@@ -88,7 +104,9 @@ properties:
type: string
description: Timezone configuration for the inbox
callback_webhook_url:
type: string
type:
- string
- 'null'
description: Webhook URL for callbacks
allow_messages_after_resolved:
type: boolean
@@ -100,26 +118,38 @@ properties:
type: string
description: Type of sender name to display (e.g., friendly)
business_name:
type: string
type:
- string
- 'null'
description: Business name associated with the inbox
hmac_mandatory:
type: boolean
description: Whether HMAC verification is mandatory
selected_feature_flags:
type: object
type:
- array
- 'null'
description: Selected feature flags for the inbox
items:
type: string
reply_time:
type: string
description: Expected reply time
messaging_service_sid:
type: string
type:
- string
- 'null'
description: Messaging service SID for SMS providers
phone_number:
type: string
type:
- string
- 'null'
description: Phone number associated with the inbox
medium:
type: string
description: Medium of communication (e.g., sms, email)
provider:
type: string
type:
- string
- 'null'
description: Provider of the channel

View File

@@ -17,37 +17,49 @@ properties:
description: The ID of the conversation
message_type:
type: integer
enum: [0, 1, 2]
enum: [0, 1, 2, 3]
description: The type of the message
created_at:
type: integer
description: The time at which message was created
updated_at:
type: integer
type:
- integer
- string
description: The time at which message was updated
private:
type: boolean
description: The flags which shows whether the message is private or not
status:
type: string
enum: ["sent", "delivered", "read", "failed"]
type:
- string
- 'null'
enum: ["sent", "delivered", "read", "failed", null]
description: The status of the message
source_id:
type: string
type:
- string
- 'null'
description: The source ID of the message
content_type:
type: string
enum: ["text", "input_select", "cards", "form"]
type:
- string
- 'null'
enum: ["text", "input_text", "input_textarea", "input_email", "input_select", "cards", "form", "article", "incoming_email", "input_csat", "integrations", "sticker", "voice_call", null]
description: The type of the template message
content_attributes:
type: object
description: The content attributes for each content_type
sender_type:
type: string
enum: ["contact", "agent", "agent_bot"]
type:
- string
- 'null'
enum: ["Contact", "User", "AgentBot", "Captain::Assistant", null]
description: The type of the sender
sender_id:
type: number
type:
- number
- 'null'
description: The ID of the sender
external_source_ids:
type: object
@@ -56,16 +68,24 @@ properties:
type: object
description: The additional attributes of the message
processed_message_content:
type: string
type:
- string
- 'null'
description: The processed message content
sentiment:
type: object
type:
- object
- 'null'
description: The sentiment of the message
conversation:
type: object
type:
- object
- 'null'
description: The conversation object
attachment:
type: object
type:
- object
- 'null'
description: The file object attached to the image
sender:
type: object

View File

@@ -18,7 +18,7 @@ properties:
description: "The type of the message (0: incoming, 1: outgoing, 2: activity, 3: template)"
content_type:
type: string
enum: ["text", "input_select", "cards", "form", "input_csat"]
enum: ["text", "input_text", "input_textarea", "input_email", "input_select", "cards", "form", "article", "incoming_email", "input_csat", "integrations", "sticker", "voice_call"]
description: The type of the message content
status:
type: string
@@ -29,13 +29,15 @@ properties:
description: The content attributes for each content_type
properties:
in_reply_to:
type: string
type:
- string
- 'null'
description: ID of the message this is replying to
nullable: true
echo_id:
type: string
type:
- string
- 'null'
description: The echo ID of the message, used for deduplication
nullable: true
created_at:
type: integer
description: The timestamp when message was created
@@ -43,9 +45,10 @@ properties:
type: boolean
description: The flag which shows whether the message is private or not
source_id:
type: string
type:
- string
- 'null'
description: The source ID of the message
nullable: true
sender:
$ref: '#/components/schemas/contact_detail'
description: The sender of the message (only for incoming messages)
@@ -76,4 +79,4 @@ properties:
description: The thumbnail URL of the attached file
file_size:
type: number
description: The size of the attached file in bytes
description: The size of the attached file in bytes

View File

@@ -4,16 +4,19 @@ properties:
type: integer
description: Total number of articles
archived_articles_count:
type: integer
nullable: true
type:
- integer
- 'null'
description: Number of archived articles
published_count:
type: integer
nullable: true
type:
- integer
- 'null'
description: Number of published articles
draft_articles_count:
type: integer
nullable: true
type:
- integer
- 'null'
description: Number of draft articles
categories_count:
type: integer

View File

@@ -26,16 +26,19 @@ properties:
type: number
description: ID of the account
conversation_id:
type: number
nullable: true
type:
- number
- 'null'
description: ID of the conversation
inbox_id:
type: number
nullable: true
type:
- number
- 'null'
description: ID of the inbox
user_id:
type: number
nullable: true
type:
- number
- 'null'
description: ID of the user/agent
created_at:
type: string

View File

@@ -13,16 +13,19 @@ items:
type: number
description: Number of conversations resolved by the agent during the date range
avg_resolution_time:
type: number
nullable: true
type:
- number
- 'null'
description: Average time (in seconds) to resolve conversations. Null if no data available.
avg_first_response_time:
type: number
nullable: true
type:
- number
- 'null'
description: Average time (in seconds) for the first response. Null if no data available.
avg_reply_time:
type: number
nullable: true
type:
- number
- 'null'
description: Average time (in seconds) between replies. Null if no data available.
example:
- id: 1

View File

@@ -13,16 +13,19 @@ items:
type: number
description: Number of conversations resolved in the inbox during the date range
avg_resolution_time:
type: number
nullable: true
type:
- number
- 'null'
description: Average time (in seconds) to resolve conversations. Null if no data available.
avg_first_response_time:
type: number
nullable: true
type:
- number
- 'null'
description: Average time (in seconds) for the first response. Null if no data available.
avg_reply_time:
type: number
nullable: true
type:
- number
- 'null'
description: Average time (in seconds) between replies. Null if no data available.
example:
- id: 1

View File

@@ -13,16 +13,19 @@ items:
type: number
description: Number of conversations resolved by the team during the date range
avg_resolution_time:
type: number
nullable: true
type:
- number
- 'null'
description: Average time (in seconds) to resolve conversations. Null if no data available.
avg_first_response_time:
type: number
nullable: true
type:
- number
- 'null'
description: Average time (in seconds) for the first response. Null if no data available.
avg_reply_time:
type: number
nullable: true
type:
- number
- 'null'
description: Average time (in seconds) between replies. Null if no data available.
example:
- id: 1

View File

@@ -7,7 +7,9 @@ properties:
type: string
description: The name of the team
description:
type: string
type:
- string
- 'null'
description: The description about the team
allow_auto_assign:
type: boolean

View File

@@ -13,17 +13,21 @@ properties:
confirmed:
type: boolean
display_name:
type: string
nullable: true
type:
- string
- 'null'
message_signature:
type: string
nullable: true
type:
- string
- 'null'
email:
type: string
hmac_identifier:
type: string
inviter_id:
type: number
type:
- number
- 'null'
name:
type: string
provider:
@@ -38,8 +42,9 @@ properties:
uid:
type: string
type:
type: string
nullable: true
type:
- string
- 'null'
custom_attributes:
type: object
description: Available for users who are created through platform APIs and has custom attributes associated.
@@ -55,7 +60,9 @@ properties:
status:
type: string
active_at:
type: string
type:
- string
- 'null'
format: date-time
role:
type: string
@@ -71,8 +78,10 @@ properties:
auto_offline:
type: boolean
custom_role_id:
type: number
nullable: true
type:
- number
- 'null'
custom_role:
type: object
nullable: true
type:
- object
- 'null'

View File

@@ -1,4 +1,4 @@
openapi: '3.0.4'
openapi: '3.1.0'
info:
title: Chatwoot
description: This is the API documentation for Chatwoot server.

View File

@@ -38,10 +38,14 @@ responses:
assignee:
$ref: '#/components/schemas/agent'
agent_last_seen_at:
type: string
type:
- string
- 'null'
format: date-time
assignee_last_seen_at:
type: string
type:
- string
- 'null'
format: date-time
payload:
type: array

View File

@@ -25,6 +25,10 @@ requestBody:
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/conversation'
'401':
description: Unauthorized
content:

View File

@@ -33,3 +33,38 @@ get:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
post:
tags:
- Inboxes
operationId: inboxCreation
summary: Create an inbox
description: You can create more than one website inbox in each account
security:
- userApiKey: []
parameters:
- $ref: '#/components/parameters/account_id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/inbox_create_payload'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/inbox'
'404':
description: Inbox not found
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
'403':
description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'

View File

@@ -1,3 +1,38 @@
get:
tags:
- Inboxes
operationId: GetInbox
summary: Get an inbox
security:
- userApiKey: []
description: Get an inbox available in the current account
parameters:
- $ref: '#/components/parameters/account_id'
- name: id
in: path
schema:
type: number
description: ID of the inbox
required: true
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/inbox'
'404':
description: Inbox not found
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
'403':
description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
patch:
tags:
- Inboxes
@@ -26,8 +61,6 @@ patch:
content:
application/json:
schema:
type: object
description: 'Updated inbox object'
$ref: '#/components/schemas/inbox'
'404':
description: Inbox not found

View File

@@ -167,7 +167,7 @@
# ------------ Application API routes ------------#
# Accounts
/api/v1/accounts/{id}:
/api/v1/accounts/{account_id}:
parameters:
- $ref: '#/components/parameters/account_id'
get:
@@ -413,10 +413,6 @@
# Inboxes
/api/v1/accounts/{account_id}/inboxes:
$ref: ./application/inboxes/index.yml
/api/v1/accounts/{account_id}/inboxes/{id}/:
$ref: ./application/inboxes/show.yml
/api/v1/accounts/{account_id}/inboxes/:
$ref: ./application/inboxes/create.yml
/api/v1/accounts/{account_id}/inboxes/{id}:
$ref: ./application/inboxes/update.yml
/api/v1/accounts/{account_id}/inboxes/{id}/agent_bot:

View File

@@ -19,3 +19,80 @@ get:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
put:
tags:
- Profile
operationId: updateProfile
summary: Update user profile
description: Update the user profile details
security:
- userApiKey: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- profile
properties:
profile:
type: object
properties:
name:
type: string
email:
type: string
display_name:
type: string
message_signature:
type: string
phone_number:
type: string
current_password:
type: string
password:
type: string
password_confirmation:
type: string
ui_settings:
type: object
multipart/form-data:
schema:
type: object
required:
- profile
properties:
profile:
type: object
properties:
name:
type: string
email:
type: string
display_name:
type: string
message_signature:
type: string
phone_number:
type: string
current_password:
type: string
password:
type: string
password_confirmation:
type: string
avatar:
type: string
format: binary
ui_settings:
type: object
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/user'
'401':
description: Unauthorized

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
openapi: '3.0.4'
openapi: '3.1.0'
info:
title: Chatwoot - Application API
description: Application API endpoints for Chatwoot

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
openapi: '3.0.4'
openapi: '3.1.0'
info:
title: Chatwoot - Client API
description: Client API endpoints for Chatwoot

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
openapi: '3.0.4'
openapi: '3.1.0'
info:
title: Chatwoot - Other APIs
description: Other API endpoints for Chatwoot

View File

@@ -1,4 +1,4 @@
openapi: '3.0.4'
openapi: '3.1.0'
info:
title: Chatwoot - Platform API
description: Platform API endpoints for Chatwoot

File diff suppressed because it is too large Load Diff